Fix Dialogue client memory leak when using generateErrorParameterFormatRespectingDialogueInterfaces#3024
Conversation
…ngDialogueInterfaces
Generate changelog in
|
generateErrorParameterFormatRespectingDialogueInterfaces
| private record ExceptionDeserializerCacheKey<T>(ExceptionDeserializerArgs<T> args, DeserializerType type) { | ||
| private record ExceptionDeserializerCacheKey( | ||
| // We use Type rather than TypeMarker here, because `new TypeMarker<T>() {}` retains references | ||
| // to the dialogue clients which construct them, which causes memory to leak when stored in the cache | ||
| Type returnType, | ||
| ImmutableMap<String, ErrorExceptionPair<?, ?>> errorNameToExceptionTypeMarkers, | ||
| DeserializerType type) { |
There was a problem hiding this comment.
Couple of things regarding this:
ExceptionDeserializerArgswas implementingtoString,equalsandhashCodebecause it was used as the cache key. This is now unnecessary afaict and could probably be removed, but would be a behavior break.- If we were to add more elements to
ExceptionDeserializerArgsin the future, the cache key wouldn't take them into account by default. I've debated instead adding aExceptionDeserializerArgs.CacheKeyclass directly inExceptionDeserializerArgs, which would make the behavior/requirement more obvious. ExceptionDeserializerArgsitself was probably a mistake, and we should instead have- built
Map<String, ErrorExceptionPair<?, ?>> errorNameToExceptionTypeMarkersdirectly in the dialogue clients (which we already effectively do, but end up doing once per endpoint, which is inefficient when it's always the same) - made
deserializer(TypeMarker<T> token, Map<String, ErrorExceptionPair<?, ?>> errorNameToExceptionTypeMarkers)(which would have constructed its key then)
- built
Unfortunately, this is now part of the API and every generated dialogue client, so we can't simply break it. We could possibly still improve future codegen, but palantir/conjure-java#2869 may be better anyway
There was a problem hiding this comment.
If we were to add more elements to ExceptionDeserializerArgs in the future, the cache key wouldn't take them into account by default.
This actually seems fine to me. The arguments to deserializer and the key used to cache some values internally may not necessarily be the same.
It's sort of impossible to mess this up. If a value is necessary for constructing the deserializer, then it will have to be made part of the cache key so it's accessible to the deserializer.
ExceptionDeserializerArgs itself was probably a mistake
Not sure I agree with this.
Having a single "request" parameter like ExceptionDeserializerArgs allows us to add additional "parameters" without causing ABI breaks.
There was a problem hiding this comment.
If a value is necessary for constructing the deserializer, then it will have to be made part of the cache key so it's accessible to the deserializer.
Ah yeah, that makes sense 👍
Having a single "request" parameter like ExceptionDeserializerArgs allows us to add additional "parameters" without causing ABI breaks.
That's a fair point, but we still can only add new optional fields regardless, which could also have been done by adding method overloads instead. Not saying overloads are ideal either, and I agree there are some nice aspects to ExceptionDeserializerArgs. Ultimately, the problem is more so about bad usage of that class (duplicating map creation, as well as using it as cache keys), whereas it's more effective as a pure argument-passing class (just wrapping other objects that are created separately, and shared where sensible), and shouldn't live beyond that
✅ Successfully generated changelog entry!Need to regenerate?Simply interact with the changelog bot comment again to regenerate these entries. 📋Changelog Preview🐛 Fixes
|
| conjure { | ||
| java { | ||
| generateErrorParameterFormatRespectingDialogueInterfaces = true | ||
| } | ||
| } |
There was a problem hiding this comment.
What's the purpose of this? Just to exercise this code path?
There was a problem hiding this comment.
Yes - it doesn't get exercised otherwise.
There was a problem hiding this comment.
This is also the desired default as of a few months ago, and thus it makes sense to update the tests to test against it
|
Released 6.30.0 This creates a tag and GitHub release. Additional build or deployment steps may still be in progress. |
Before this PR
When you set
generateErrorParameterFormatRespectingDialogueInterfacesto true, theDialogueClientsDnsIntegrationTest#nonReloadableClientConfigand correspondingnonReloadableServiceConfigstart failing at the last step, which verifies that when you orphan the dialogue client, the corresponding metric gets cleaned up.This actually does not work when using generateErrorParameterFormatRespectingDialogueInterfaces because it generates code like
The
new TypeMarker<Void>() {}is the issue here, becauseConjureBodySerde'sexceptionDeserializerscache (since it's part of the key, and it doesn't use weak keys)This means that dialogue clients constructed with
generateErrorParameterFormatRespectingDialogueInterfacescannot get GC'ed, which in general isn't a problem since they're meant to be long-lived, but could actually result in a memory leak when using sticky clients, which tend to create new clients every timeEdit: This is actually unlikely to be a big problem, since two instances of the same client would end up hitting the same cache keys, due to
TypeMarkerchecking equality on the underlyingType, and since we don't use weak keys, so only the first created instance would be incorrectly retained. Still not ideal though, and worth a simple fix.After this PR
==COMMIT_MSG==
Fix Dialogue client memory leak when using
generateErrorParameterFormatRespectingDialogueInterfaces==COMMIT_MSG==
The fix here is to not use the
ExceptionDeserializerArgsobjects directly as the cache key, but instead extract its information to build the actual cache key.Possible downsides?