Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions scripts/conformance/server-parity/legacy-marker-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function checkLegacyMarkerArtifacts(fixture, artifacts) {
assert.equal(spec.cancel_unsent_poll_on_shutdown, true, 'explicit locally cancelled, unforwarded poll policy');
assert.deepEqual(spec.poll_capacity, {status: 429, poll_status: 'long_poll_capacity_exhausted',
retry_after_seconds: 1, kinds: ['workflow', 'activity', 'query'], recovery: 'same_authority_and_options',
allow_query_shutdown_after_commit: true}, 'explicit published no-task poll admission policy');
allow_shutdown_after_commit: ['workflow', 'activity', 'query']}, 'explicit published no-task poll admission policy');
for (const selected of [spec.producer, spec.consumer]) {
assert(['python', 'rust'].includes(selected.language));
const installed = artifacts.published_sdk_artifacts?.[selected.language];
Expand Down Expand Up @@ -268,8 +268,12 @@ export function checkLegacyMarkerDeployment(fixture, observation, workflowId) {
&& item.response.worker_id === worker);
const committed = phase.requests.slice(0, withdrawal).some(item => item.status === 200
&& [...completions(phase), ...activities(phase)].includes(item) && item.request.lease_owner === worker);
assert(recovered || kind === 'query' && spec.poll_capacity.allow_query_shutdown_after_commit && withdrawal > index && committed,
'poll admission recovers with identical authority/options, or an idle query worker withdraws after its own accepted commit');
// A refusal leases nothing, so an idle poller of a worker that then
// withdraws, after its own accepted commit, loses no work: the
// published SDKs stop their workflow, activity and query pollers
// together at shutdown. The outcome counts above guard the work.
assert(recovered || spec.poll_capacity.allow_shutdown_after_commit.includes(kind) && withdrawal > index && committed,
'poll admission recovers with identical authority/options, or an idle worker withdraws after its own accepted commit');
} else if (request.status === 503) {
assert.equal(request.method, 'POST');
assert(['/api/worker/workflow-tasks/poll', '/api/worker/activity-tasks/poll'].includes(request.path));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
"cancel_poll_on_shutdown": ["workflow", "activity", "query"],
"cancel_unsent_poll_on_shutdown": true,
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_query_shutdown_after_commit": true}
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_shutdown_after_commit": ["workflow", "activity", "query"]}
},
"expected_events": [
"StartAccepted",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
"cancel_poll_on_shutdown": ["workflow", "activity", "query"],
"cancel_unsent_poll_on_shutdown": true,
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_query_shutdown_after_commit": true}
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_shutdown_after_commit": ["workflow", "activity", "query"]}
},
"expected_events": [
"StartAccepted",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
"cancel_poll_on_shutdown": ["workflow", "activity", "query"],
"cancel_unsent_poll_on_shutdown": true,
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_query_shutdown_after_commit": true}
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_shutdown_after_commit": ["workflow", "activity", "query"]}
},
"expected_events": [
"StartAccepted",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
"cancel_poll_on_shutdown": ["workflow", "activity", "query"],
"cancel_unsent_poll_on_shutdown": true,
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_query_shutdown_after_commit": true}
"poll_capacity": {"status": 429, "poll_status": "long_poll_capacity_exhausted", "retry_after_seconds": 1, "kinds": ["workflow", "activity", "query"], "recovery": "same_authority_and_options", "allow_shutdown_after_commit": ["workflow", "activity", "query"]}
},
"expected_events": [
"StartAccepted",
Expand Down
26 changes: 18 additions & 8 deletions tests/Unit/ServerParityLegacyMarkerContractTest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,6 @@ for (const id of ['legacy-two-markers-pending', 'legacy-two-markers-completed',
for (const [name, mutate] of [
['cancelled HTTP response', x => x.refusal.client_cancelled = true],
['transport failure', x => x.refusal.transport_error = 'connection reset'],
['activity capacity', x => x.refusal.path = '/api/worker/activity-tasks/poll'],
['workflow capacity', x => x.refusal.path = '/api/worker/workflow-tasks/poll'],
['completion capacity', x => x.refusal.path = '/api/worker/query-tasks/task/complete'],
['HTTP authentication refusal', x => x.refusal.status = 401],
['leased task', x => x.refusal.response.task = {task_id: 'lost-task'}],
Expand Down Expand Up @@ -191,18 +189,30 @@ for (const id of ['legacy-two-markers-pending', 'legacy-two-markers-completed',
['refusal has another reason', x => x.refusal.response.reason = 'unauthorized'],
]) test(`${id} ${phase} ${kind}: rejects capacity ${name}`, () => {
const state = capacityRecoveryModel();
// Disable the separate query-shutdown alternative by placing withdrawal
// Disable the separate idle-shutdown alternative by placing withdrawal
// before refusal, so these mutations isolate recovery evidence.
if (kind === 'query') {
state.requests.splice(state.requests.indexOf(state.withdrawal), 1);
state.requests.splice(state.requests.indexOf(state.refusal), 0, state.withdrawal);
}
state.requests.splice(state.requests.indexOf(state.withdrawal), 1);
state.requests.splice(state.requests.indexOf(state.refusal), 0, state.withdrawal);
mutate(state);
assert.throws(() => checkLegacyMarkerDeployment(fixture, state.raw, 'test'));
});
if (kind !== 'query') test(`${id} ${phase} ${kind}: rejects withdrawal without an admitted replacement poll`, () => {
function idleShutdownModel() {
const state = capacityRecoveryModel();
state.requests.splice(state.requests.indexOf(state.recovery), 1);
return state;
}
test(`${id} ${phase} ${kind}: accepts an idle refusal then the worker's own withdrawal after its commit`, () => {
checkLegacyMarkerDeployment(fixture, idleShutdownModel().raw, 'test');
});
for (const [name, mutate] of [
['no withdrawal after the refusal', x => {
x.requests.splice(x.requests.indexOf(x.withdrawal), 1);
x.requests.splice(x.requests.indexOf(x.refusal), 0, x.withdrawal);
}],
['withdrawal before its own commit', x => {x.requests.splice(x.requests.indexOf(x.withdrawal), 1); x.requests.unshift(x.withdrawal);}],
['another worker withdraws', x => x.withdrawal.response.worker_id = 'other-worker'],
]) test(`${id} ${phase} ${kind}: rejects idle refusal with ${name}`, () => {
const state = idleShutdownModel(); mutate(state);
assert.throws(() => checkLegacyMarkerDeployment(fixture, state.raw, 'test'));
});
}
Expand Down
Loading