-
Notifications
You must be signed in to change notification settings - Fork 10
fix(sync): cancel chain event work during shutdown #2044
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
base: codex/rfc64-m1-cold-historical-binding
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,47 @@ import { FinalizationRuntime } from '../src/finalization-runtime.js'; | |
| import { VmReconcileQueueClosedError } from '../src/vm-reconcile-service.js'; | ||
|
|
||
| describe('DKGAgent outbox shutdown lifecycle', () => { | ||
| it('aborts network waits before awaiting the chain-event poller drain', async () => { | ||
| let releasePoller!: () => void; | ||
| const pollerDrain = new Promise<void>((resolve) => { releasePoller = resolve; }); | ||
| const beginStop = vi.fn(); | ||
| const stopNode = vi.fn(async () => {}); | ||
| const chainPollerStop = vi.fn(async () => { | ||
| expect(beginStop).toHaveBeenCalledOnce(); | ||
| await pollerDrain; | ||
| }); | ||
| const agent = Object.create(DKGAgent.prototype) as any; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Issue: Extract a shutdown-test fixture instead of adding another raw prototype stub What's wrong Example Suggested direction Confidence note For Agents |
||
| Object.assign(agent, { | ||
| started: true, | ||
| syncCapacityRuntime: { stopSampling: vi.fn() }, | ||
| chainPoller: { stop: chainPollerStop }, | ||
| coreHostRecordingsClosed: false, | ||
| drainCoreHostRecordings: vi.fn(async () => {}), | ||
| messenger: { stopOutboxDrain: vi.fn(async () => {}) }, | ||
| clearRandomSamplingBindRetry: vi.fn(), | ||
| clearStorageACKRegistrationRetry: vi.fn(), | ||
| storageACKRegistrationRetryInFlight: false, | ||
| randomSamplingHandle: null, | ||
| inFlightSubstrateFanOutCount: () => 0, | ||
| router: { closePooling: vi.fn(async () => {}) }, | ||
| node: { beginStop, stop: stopNode }, | ||
| finalizationRuntime: new FinalizationRuntime(), | ||
| store: { close: vi.fn(async () => {}) }, | ||
| log: { warn: vi.fn() }, | ||
| }); | ||
|
|
||
| const stopping = agent.stop(); | ||
| await new Promise((resolve) => setTimeout(resolve, 0)); | ||
|
|
||
| expect(beginStop).toHaveBeenCalledOnce(); | ||
| expect(chainPollerStop).toHaveBeenCalledOnce(); | ||
| expect(stopNode).not.toHaveBeenCalled(); | ||
|
|
||
| releasePoller(); | ||
| await stopping; | ||
| expect(stopNode).toHaveBeenCalledOnce(); | ||
| }); | ||
|
|
||
| it('closes reconcile admission and cancels queued jobs before store teardown', async () => { | ||
| let releaseActive!: () => void; | ||
| let queuedStarted = false; | ||
|
|
@@ -23,6 +64,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| const agent = Object.create(DKGAgent.prototype) as any; | ||
| Object.assign(agent, { | ||
| started: true, | ||
| syncCapacityRuntime: { stopSampling: vi.fn() }, | ||
| chainPoller: null, | ||
| vmReconcileDispatcher: dispatcher, | ||
| coreHostRecordingsClosed: false, | ||
|
|
@@ -34,7 +76,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| randomSamplingHandle: null, | ||
| inFlightSubstrateFanOutCount: () => 0, | ||
| router: { closePooling: vi.fn(async () => {}) }, | ||
| node: { stop: stopNode }, | ||
| node: { beginStop: vi.fn(), stop: stopNode }, | ||
| finalizationRuntime: new FinalizationRuntime(), | ||
| store: { close: closeStore }, | ||
| log: { warn: vi.fn() }, | ||
|
|
@@ -75,6 +117,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| const agent = Object.create(DKGAgent.prototype) as any; | ||
| Object.assign(agent, { | ||
| started: true, | ||
| syncCapacityRuntime: { stopSampling: vi.fn() }, | ||
| chainPoller: null, | ||
| vmReconcileDispatcher: dispatcher, | ||
| coreHostRecordingsClosed: false, | ||
|
|
@@ -86,7 +129,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| randomSamplingHandle: null, | ||
| inFlightSubstrateFanOutCount: () => 0, | ||
| router: { closePooling: vi.fn(async () => {}) }, | ||
| node: { stop: stopNode }, | ||
| node: { beginStop: vi.fn(), stop: stopNode }, | ||
| finalizationRuntime: new FinalizationRuntime(), | ||
| store: { close: closeStore }, | ||
| log: { warn }, | ||
|
|
@@ -116,6 +159,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| const agent = Object.create(DKGAgent.prototype) as any; | ||
| Object.assign(agent, { | ||
| started: true, | ||
| syncCapacityRuntime: { stopSampling: vi.fn() }, | ||
| chainPoller: null, | ||
| coreHostRecordingsClosed: false, | ||
| drainCoreHostRecordings: vi.fn(async () => {}), | ||
|
|
@@ -126,7 +170,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| randomSamplingHandle: null, | ||
| inFlightSubstrateFanOutCount: () => 0, | ||
| router: { closePooling: vi.fn(async () => {}) }, | ||
| node: { stop: stopNode }, | ||
| node: { beginStop: vi.fn(), stop: stopNode }, | ||
| finalizationRuntime: new FinalizationRuntime(), | ||
| store: { close: vi.fn(async () => {}) }, | ||
| log: { warn: vi.fn() }, | ||
|
|
@@ -150,6 +194,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| const agent = Object.create(DKGAgent.prototype) as any; | ||
| Object.assign(agent, { | ||
| started: true, | ||
| syncCapacityRuntime: { stopSampling: vi.fn() }, | ||
| chainPoller: null, | ||
| coreHostRecordingsClosed: false, | ||
| drainCoreHostRecordings: vi.fn(async () => {}), | ||
|
|
@@ -160,7 +205,7 @@ describe('DKGAgent outbox shutdown lifecycle', () => { | |
| randomSamplingHandle: null, | ||
| inFlightSubstrateFanOutCount: () => 0, | ||
| router: { closePooling: vi.fn(async () => {}) }, | ||
| node: { stop: stopNode }, | ||
| node: { beginStop: vi.fn(), stop: stopNode }, | ||
| finalizationRuntime: new FinalizationRuntime(), | ||
| store: { close: closeStore }, | ||
| log: { warn }, | ||
|
|
||
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.
🟡 Issue: The KACG abort signal wiring is not covered end to end
What's wrong
The new cancellation behavior depends on the signal being passed through multiple call boundaries. The added abort test verifies the handler when called directly, but not the production wiring that supplies the signal to that handler.
Example
A regression that changes
await this.handleKARegisteredNudge(onChainId, kaId, ctx, signal)back toawait this.handleKARegisteredNudge(onChainId, kaId, ctx)would leave the direct nudge abort test green, while production shutdown could still wait on the unresolved CG resolver.Suggested direction
Add a regression test that drives a
KnowledgeAssetRegisteredToContextGraphevent through the real poller callback boundary and asserts the signal reacheshandleKARegisteredNudgeand becomes aborted duringstop().For Agents
Add a wiring-level test around
ChainEventPollerplus the agent lifecycle callback, or a focused lifecycle test that stubshandleKARegisteredNudgeand proves the callback receives the poller's stop signal. Preserve the existing payload behavior and assert that stopping the poller aborts the same signal observed by the nudge handler.