Fix #120: start and stop each server plugin only once - #397
Conversation
… once initPlugins registers a plugin instance once for every plugin type interface it implements, adding it to the serverPlugins list up to eight times. startPlugins and stopPlugins iterate that list, so a plugin implementing more than one interface (for example both ServicePlugin and ChannelPlugin) had start() and stop() invoked once per interface instead of once per plugin. Route the registrations through a helper that ignores an instance which is already registered. The comparison is by identity so that two distinct instances are still both registered even if the plugin class reports them as equal. Adds tests covering duplicate registration, stop() being called once for a plugin registered for multiple types, and distinct-but-equal instances still being registered separately. Signed-off-by: Giovanni Giannola <giogiannola@globalesm.com>
jonbartels
left a comment
There was a problem hiding this comment.
I left some comments in the code with a simpler strategy for the code, though that strategy is not as easily tested.
I also left some related comments on Issue #120 to better understand the conditions.
I would be inclined to approve this PR if it implemented Set instead of an adder function that required looping over a List. TY for the PR and your first OIE commit!
Addresses review feedback on OpenIntegrationEngine#397. Rather than routing the eight registrations through a helper that scans the List, serverPlugins is now a Set so the de-duplication is the natural behaviour of the collection and the registration calls stay as they were. LinkedHashSet rather than HashSet: initPlugins loads plugins in a deliberate order (descending plugin weight), and that order is preserved when starting and stopping them. getServerPlugins still returns a List so the ExtensionController signature is unchanged for extensions. The unit tests added in the previous commit are removed. They exercised the helper directly, and with a Set the invariant is enforced by the collection type rather than by logic of our own. Signed-off-by: Giovanni Giannola <giogiannola@globalesm.com>
|
Thanks @jonbartels, good call on the Set, that's clearly the right shape. Pushed as Two small deviations worth flagging:
On testing: you were right that it was awkward. The three tests exercised the helper directly, so they went with it. I think that is the better trade: with a Set the invariant is enforced by the collection type rather than by logic of ours, so there is less to regress. Going through Re your question on #120 about making One note: the |
Summary
Fixes #120. A plugin whose class implements several of the plugin type interfaces was registered once per interface, so it was started and stopped multiple times.
Root cause
DefaultExtensionController.initPlugins()checks each loaded plugin against every plugin type in turn, and each of the eight branches adds the instance to the sameserverPluginscollection:Because that collection was an
ArrayList, a class implementing bothServicePluginandChannelPluginends up in it twice.Note the issue title mentions
close(); the methods actually affected arestart()andstop()onServerPlugin.Impact beyond start/stop
The duplicate entries affect every consumer of that collection, not just shutdown:
startPlugins()/stopPlugins()invokestart()andstop()once per implemented interface instead of once per plugin. This is the shutdown noise reported in the issue.AlertWorkerbuilds itsalertActionAcceptorslist by iteratinggetServerPlugins()and filtering oninstanceof AlertActionAcceptor, so a duplicated plugin is added repeatedly and its alert actions run once per implemented interface.DefaultUsageControllerreports plugin usage from the same collection, so counts are inflated for multi-interface plugins.Fix
serverPluginsis now aSet, so a plugin registered for several interfaces is held once and the de-duplication is the natural behaviour of the collection. The eight registration calls are unchanged.LinkedHashSetrather thanHashSet:initPlugins()deliberately loads plugins in descending plugin weight order, and preserving that order means they are started and stopped in the intended sequence.getServerPlugins()still returnsList<ServerPlugin>, so theExtensionControllersignature is unchanged for extensions. It now returns a copy rather than the live internal collection.Net change is 10 insertions and 2 deletions in one file.
On testing
The earlier revision of this PR routed the registrations through a de-duplicating helper and unit tested it. Following review that has been dropped in favour of the
Set, and the tests went with it: they exercised the helper directly, and the invariant is now enforced by the collection type rather than by logic of our own.Testing this through
initPlugins()instead is not really reachable from a unit test, since it requires plugin metadata loading plus database access viasetPluginProperties. If you would like coverage here, I am happy to look at an integration-level test.Verified with
./gradlew :server:test(613 tests, 0 failures) and./gradlew :donkey:test.Note on the failing check
The
FileReceiverTest > testPoll1failure on the previous commit is unrelated to this change, which touches only plugin registration. The assertion atFileReceiverTest.java:133counts files picked up by a poll, and it passes locally both in the full suite and across three consecutive isolated runs. Given the recent "Allow parallel tests" change, this looks like a timing-sensitive flake rather than a real regression.How to verify
Build a plugin whose class implements two
ServerPluginsub-interfaces, for exampleServicePluginandChannelPlugin, and log a line instop(). Before this change the line appears twice on server shutdown; after it, once.