-
Notifications
You must be signed in to change notification settings - Fork 24.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix test memory leak #88362
Fix test memory leak #88362
Conversation
Pinging @elastic/es-core-infra (Team:Core/Infra) |
Is there a way we can avoid making production code mutable just for tests? |
|
||
/** | ||
* ILM Provider implementation for the {@link ImmutableClusterStateHandlerProvider} service interface | ||
*/ | ||
public class ILMImmutableStateHandlerProvider implements ImmutableClusterStateHandlerProvider { | ||
private static final Set<ImmutableClusterStateHandler<?>> handlers = ConcurrentHashMap.newKeySet(); | ||
private static volatile Collection<ImmutableClusterStateHandler<?>> handlers = Collections.emptyList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static volatile Collection<ImmutableClusterStateHandler<?>> handlers = Collections.emptyList(); | |
private static final SetOnce<Collection<ImmutableClusterStateHandler<?>>> handlers = new SetOnce<>(); |
I wonder if this should be a SetOnce<Collection<ImmutableClusterStateHandler<?>>>
, then when registerHandlers
is called trySet()
can be used to atomically update the collection?
|
||
@Override | ||
public Collection<ImmutableClusterStateHandler<?>> handlers() { | ||
return handlers; | ||
} | ||
|
||
public static void registerHandlers(ImmutableClusterStateHandler<?>... stateHandlers) { | ||
handlers.addAll(Arrays.asList(stateHandlers)); | ||
handlers = List.of(stateHandlers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handlers = List.of(stateHandlers); | |
handlers.trySet(List.of(stateHandlers)); |
Since this completely changes the stored values, it may be good to rename registerHandlers
to better indicate that it won't set the values if they are already set.
I think the issue is the use of statics. We don't use them in other places for such registration. These handlers should be captured in instance variables specific to the node, so that many node instances (in tests) can be created. |
So I flipped this around a bit and I removed the use of statics. I also added an example code of how this is intended to work in ActionModule. Essentially, the SPI creates the service provides independently of the plugin constructor, so if we want to avoid any statics, we need a way to pass down what the handlers need to be constructed with. However, the best place to construct the handlers is during the plugin constructor (or createComponents), since then we have all what we'd need to make the handler work. To avoid passing a superset of what we might need for construction of the immutable state handlers, I did the following:
|
Collection<ImmutableClusterStateHandler<?>> handlers(); | ||
Collection<ImmutableClusterStateHandler<?>> handlers(Collection<? extends Plugin> loadedPlugins); | ||
|
||
Collection<Class<? extends Plugin>> providedPlugins(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to add docs here.
@Override | ||
public Collection<ImmutableClusterStateHandler<?>> handlers() { | ||
return handlers; | ||
public Collection<ImmutableClusterStateHandler<?>> handlers(Collection<? extends Plugin> loadedPlugins) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The provider receives a reference back of its related plugins, which will supply it with what handlers they want to expose.
I think also, maybe I don't need to worry about a collection of plugins here. It should be probably be only one per plugin. |
@Override | ||
public Collection<ImmutableClusterStateHandler<?>> handlers() { | ||
return handlers; | ||
public ILMImmutableStateHandlerProvider() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declaring this constructor to make sure module-info compiles properly, since SPI requires that we use a no-arg constructor. If we switch to using the META-INF/services file, this problem goes away, however our build 'validateModule' task fails, requiring that we also expose this service provider in our module provides section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a couple asks on how this looks with Node/ActionModule. LGTM if you agree on the points I left.
|
||
handlers.add(new ImmutableClusterSettingsAction(clusterSettings)); | ||
pluginHandlers.forEach(h -> handlers.addAll(h.handlers())); | ||
// Initialize the controller when merged |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two collections here seem to be locals. Shouldn't they be captured somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is unused, I brought in the code to demonstrate how we'd use these when I had the earlier approach. I'll remove this code altogether. The values are locals because the initialization of the controller is not here yet, as in the other PR: https://github.com/elastic/elasticsearch/pull/88224/files#diff-b56bed42f5b0025886eb243ecb48678ac048bfba0ef047545dbb6504e357edf2R915.
I agree with your comments, so I'll refactor the code in the other PR to apply your suggestions on how to initialize the immutable handlers in the other PR.
@@ -1055,6 +1055,8 @@ protected Node( | |||
|
|||
logger.debug("initializing HTTP handlers ..."); | |||
actionModule.initRestHandlers(() -> clusterService.state().nodesIfRecovered()); | |||
logger.debug("initializing operator handlers ..."); | |||
actionModule.initImmutableClusterStateHandlers(pluginsService); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like the right place in node construction to init these. The action module is here in the line above for other reasons (and can actually be moved up, it only needs the cluster service created). I would init these when the action module is created, as part of the ctor.
public void initImmutableClusterStateHandlers(PluginsService pluginsService) { | ||
List<ImmutableClusterStateHandler<?>> handlers = new ArrayList<>(); | ||
|
||
List<? extends ImmutableClusterStateHandlerProvider> pluginHandlers = pluginsService.loadServiceProviders( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would rather see loadServiceProviders called in Node, and pass in the List of providers to ActionModule. That better matches the other calls in Node to eg filterPlugins. In general, we try to avoid passing the plugin service itself.
@@ -531,6 +531,10 @@ public TestExtension() {} | |||
public TestExtension(TestPlugin plugin) { | |||
|
|||
} | |||
|
|||
public TestExtension(TestPlugin plugin, String anotherArg) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I added a constructor to make the test check that we disallow a service provider with more than 2 constructors. Previously this class was testing for existence of more than 1.
@elasticmachine run elasticsearch-ci/part-2 |
* upstream/master: Pass IndexMetadata to AllocationDecider.can_remain (elastic#88453) [TSDB] Cache rollup bucket timestamp to reduce rounding cost (elastic#88420) Correct some typos/mistakes in comments/docs (elastic#88446) Make ClusterInfo use immutable maps in all cases (elastic#88447) Reduce map lookups (elastic#88418) Don't index geo_shape field in AbstractBuilderTestCase (elastic#88437) Remove usages of TestGeoShapeFieldMapperPlugin from enrich module (elastic#88440) Fix test memory leak (elastic#88362) Improve error when sorting on incompatible types (elastic#88399) Remove usages of BucketCollector#getLeafCollector(LeafReaderContext) (elastic#88414) Mute ReactiveStorageIT::testScaleWhileShrinking (elastic#88431) Clarify snapshot docs on archive indices (elastic#88417) [Stack Monitoring] Switch cgroup memory fields to keyword (elastic#88260) Fix RealmIdentifier XContent parser (elastic#88410) Make LoggedExec gradle task configuration cache compatible (elastic#87621) Update CorruptedFileIT so that it passes with new allocation strategy (elastic#88314) Update RareClusterStateIT to work with the new shards allocator (elastic#87922) Ensure CreateApiKey always creates a new document (elastic#88413) # Conflicts: # x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/v2/RollupShardIndexer.java
I had incorrectly made the assumption that the createComponents will only be called once for ILM plugin initialization. It appears that this isn't the case when we run tests.
This PR fixes how we initialize the ILM immutable state handlers, if the LifecyclePlugin gets re created, only the latest state is reflected in the immutable state handler provider.
Fixes #88337