Skip to content

Commit

Permalink
Merge pull request #228 from AxonFramework/bugfix/shutdown-order
Browse files Browse the repository at this point in the history
[#215] Fix: Reverse Component Deregistration Order in AxonServerTenantProvider
  • Loading branch information
schananas authored Oct 2, 2024
2 parents 1611ed5 + 6aa2ac0 commit 50184c3
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,29 @@ public void registerLifecycleHandlers(@Nonnull LifecycleRegistry lifecycle) {
lifecycle.onShutdown(Phase.INSTRUCTION_COMPONENTS + 10, this::shutdown);
}

private void shutdown() {
registrationMap.forEach((tenant, registrationList) -> {
registrationList.forEach(Registration::cancel);
/**
* Shuts down the AxonServerTenantProvider by deregistering all subscribed components.
* This method is designed to be invoked by a lifecycle handler (e.g., a shutdown hook)
* to ensure proper cleanup when the application is shutting down.
* <p>
* The shutdown process involves the following steps:
* <ol>
* <li>Iterates through all registered components for each tenant.</li>
* <li>Reverses the order of registrations for each tenant to ensure
* last-registered components are deregistered first.</li>
* <li>Invokes the cancel method on each registration, effectively
* deregistering the component from the tenant.</li>
* </ol>
* <p>
* This method ensures that all resources associated with tenant management are properly
* released and that components are given the opportunity to perform any necessary cleanup
* in the reverse order of their registration.
*/
public void shutdown() {
registrationMap.values().forEach(registrationList -> {
ArrayList<Registration> reversed = new ArrayList<>(registrationList);
Collections.reverse(reversed);
reversed.forEach(Registration::cancel);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.axoniq.axonserver.grpc.admin.ContextUpdateType;
import io.axoniq.axonserver.grpc.admin.ReplicationGroupOverview;
import org.axonframework.axonserver.connector.AxonServerConnectionManager;
import org.axonframework.common.Registration;
import org.axonframework.extensions.multitenancy.components.MultiTenantAwareComponent;
import org.axonframework.extensions.multitenancy.components.TenantConnectPredicate;
import org.axonframework.extensions.multitenancy.components.TenantDescriptor;
Expand Down Expand Up @@ -264,4 +265,40 @@ public Optional<Throwable> getError() {
return Optional.ofNullable(error);
}
}

@Test
void whenShutdownThenDeregisterComponentsInReverseOrder() {
testSubject = new AxonServerTenantProvider("default, tenant-1, tenant-2",
tenantConnectPredicate,
axonServerConnectionManager);
testSubject.start();

MultiTenantAwareComponent component1 = mock(MultiTenantAwareComponent.class);
MultiTenantAwareComponent component2 = mock(MultiTenantAwareComponent.class);
MultiTenantAwareComponent component3 = mock(MultiTenantAwareComponent.class);

// Create mocked Registration objects
Registration registration1 = mock(Registration.class);
Registration registration2 = mock(Registration.class);
Registration registration3 = mock(Registration.class);

// Setup the mocks to return the registrations
when(component1.registerTenant(any())).thenReturn(() -> registration1.cancel());
when(component2.registerTenant(any())).thenReturn(() -> registration2.cancel());
when(component3.registerTenant(any())).thenReturn(() -> registration3.cancel());

// Subscribe components in order
testSubject.subscribe(component1);
testSubject.subscribe(component2);
testSubject.subscribe(component3);

// Shutdown the provider
testSubject.shutdown();

// Verify that cancellations happened in reverse order
InOrder inOrder = inOrder(registration3, registration2, registration1);
inOrder.verify(registration3).cancel();
inOrder.verify(registration2).cancel();
inOrder.verify(registration1).cancel();
}
}

0 comments on commit 50184c3

Please sign in to comment.