@relaycast/engine 10.1.1 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/node/index.d.ts.map +1 -1
- package/dist/adapters/node/index.js +1 -0
- package/dist/adapters/node/index.js.map +1 -1
- package/dist/adapters/node/realtime.d.ts +4 -0
- package/dist/adapters/node/realtime.d.ts.map +1 -1
- package/dist/adapters/node/realtime.js +9 -1
- package/dist/adapters/node/realtime.js.map +1 -1
- package/dist/db/migrations/0070_agent_liveness_status.sql +16 -0
- package/dist/db/migrations/0071_node_liveness_revision.sql +123 -0
- package/dist/db/schema.d.ts +87 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +22 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/engine/action.d.ts +4 -1
- package/dist/engine/action.d.ts.map +1 -1
- package/dist/engine/action.js +84 -30
- package/dist/engine/action.js.map +1 -1
- package/dist/engine/agent.d.ts +11 -0
- package/dist/engine/agent.d.ts.map +1 -1
- package/dist/engine/agent.js +16 -2
- package/dist/engine/agent.js.map +1 -1
- package/dist/engine/delivery.d.ts.map +1 -1
- package/dist/engine/delivery.js +33 -2
- package/dist/engine/delivery.js.map +1 -1
- package/dist/engine/invocationCompletion.d.ts +2 -0
- package/dist/engine/invocationCompletion.d.ts.map +1 -1
- package/dist/engine/invocationCompletion.js +21 -6
- package/dist/engine/invocationCompletion.js.map +1 -1
- package/dist/engine/node.d.ts +51 -1
- package/dist/engine/node.d.ts.map +1 -1
- package/dist/engine/node.js +155 -33
- package/dist/engine/node.js.map +1 -1
- package/dist/engine/nodeProvider.d.ts +20 -0
- package/dist/engine/nodeProvider.d.ts.map +1 -1
- package/dist/engine/nodeProvider.js +69 -10
- package/dist/engine/nodeProvider.js.map +1 -1
- package/dist/engine/taskInvocation.d.ts +1 -0
- package/dist/engine/taskInvocation.d.ts.map +1 -1
- package/dist/engine/workspaceEvents.d.ts +11 -0
- package/dist/engine/workspaceEvents.d.ts.map +1 -1
- package/dist/engine/workspaceEvents.js +44 -0
- package/dist/engine/workspaceEvents.js.map +1 -1
- package/dist/node-control.d.ts +1 -1
- package/dist/node-control.d.ts.map +1 -1
- package/dist/node-control.js +6 -1
- package/dist/node-control.js.map +1 -1
- package/dist/ports/realtime.d.ts +2 -0
- package/dist/ports/realtime.d.ts.map +1 -1
- package/dist/ports/realtime.js.map +1 -1
- package/package.json +3 -3
package/dist/engine/action.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { and, asc, eq, inArray, isNotNull, isNull, lte, or, sql } from 'drizzle-orm';
|
|
1
|
+
import { and, asc, eq, inArray, isNotNull, isNull, lt, lte, or, sql } from 'drizzle-orm';
|
|
2
2
|
import { actions, actionInvocations, agents, agentNodeBindings, channelMembers, dmParticipants, nodes } from '../db/schema.js';
|
|
3
3
|
import { waitForPendingInvocationRetry } from './invocationRetry.js';
|
|
4
4
|
import { generateId } from './snowflake.js';
|
|
@@ -617,11 +617,13 @@ async function isNodeProviderLive(db, registry, workspaceId, nodeId, providerNam
|
|
|
617
617
|
const provider = await getProvider(db, workspaceId, nodeId, providerName);
|
|
618
618
|
return !!provider && isProviderLive(provider);
|
|
619
619
|
}
|
|
620
|
-
async function failInvocationForUnavailableProvider(db, workspaceId, invocationId) {
|
|
620
|
+
async function failInvocationForUnavailableProvider(db, workspaceId, invocationId, expectedNodeLivenessRevision) {
|
|
621
621
|
await db
|
|
622
622
|
.update(actionInvocations)
|
|
623
623
|
.set({ status: 'failed', error: 'handler_unavailable', completedAt: new Date(), spawnReservedAt: null })
|
|
624
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
624
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(expectedNodeLivenessRevision !== undefined
|
|
625
|
+
? [eq(actionInvocations.nodeLivenessRevision, expectedNodeLivenessRevision)]
|
|
626
|
+
: [])));
|
|
625
627
|
}
|
|
626
628
|
/**
|
|
627
629
|
* Terminally fail one invocation ONLY while it is still open AND was never
|
|
@@ -648,7 +650,7 @@ async function failNeverDispatchedInvocation(db, workspaceId, invocationId, erro
|
|
|
648
650
|
* enclosing transaction commits). A spawn invocation holding a native node
|
|
649
651
|
* reservation releases that capacity, mirroring `completeNodeInvocation`.
|
|
650
652
|
*/
|
|
651
|
-
async function failOpenInvocationRows(db, workspaceId, invocationIds, error) {
|
|
653
|
+
async function failOpenInvocationRows(db, workspaceId, invocationIds, error, expectedNodeLivenessRevision) {
|
|
652
654
|
if (invocationIds.length === 0)
|
|
653
655
|
return [];
|
|
654
656
|
// Capture reservation state before the update clears spawnReservedAt.
|
|
@@ -660,11 +662,15 @@ async function failOpenInvocationRows(db, workspaceId, invocationIds, error) {
|
|
|
660
662
|
spawnReservedAt: actionInvocations.spawnReservedAt,
|
|
661
663
|
})
|
|
662
664
|
.from(actionInvocations)
|
|
663
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), inArray(actionInvocations.id, invocationIds), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
665
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), inArray(actionInvocations.id, invocationIds), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(expectedNodeLivenessRevision !== undefined
|
|
666
|
+
? [eq(actionInvocations.nodeLivenessRevision, expectedNodeLivenessRevision)]
|
|
667
|
+
: [])));
|
|
664
668
|
const failed = await db
|
|
665
669
|
.update(actionInvocations)
|
|
666
670
|
.set({ status: 'failed', error, completedAt: new Date(), spawnReservedAt: null })
|
|
667
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), inArray(actionInvocations.id, invocationIds), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
671
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), inArray(actionInvocations.id, invocationIds), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(expectedNodeLivenessRevision !== undefined
|
|
672
|
+
? [eq(actionInvocations.nodeLivenessRevision, expectedNodeLivenessRevision)]
|
|
673
|
+
: [])))
|
|
668
674
|
.returning();
|
|
669
675
|
const failedIds = new Set(failed.map((row) => row.id));
|
|
670
676
|
for (const row of held) {
|
|
@@ -732,7 +738,7 @@ async function dispatchNodeProviderInvocation(args, options = {}) {
|
|
|
732
738
|
if (options.failIfUnavailable === false) {
|
|
733
739
|
return { accepted: false, pending: false, providerUnavailable: true };
|
|
734
740
|
}
|
|
735
|
-
await failInvocationForUnavailableProvider(args.db, args.workspaceId, args.invocationId);
|
|
741
|
+
await failInvocationForUnavailableProvider(args.db, args.workspaceId, args.invocationId, args.expectedNodeLivenessRevision);
|
|
736
742
|
throw codedError(`Provider "${args.providerName}" is offline for action "${args.action}"`, 'handler_unavailable', 503);
|
|
737
743
|
}
|
|
738
744
|
// The DB is the authoritative offline queue. Do not call sendToProvider
|
|
@@ -749,9 +755,10 @@ async function dispatchNodeProviderInvocation(args, options = {}) {
|
|
|
749
755
|
? { id: expectedActionId, name: args.action }
|
|
750
756
|
: undefined,
|
|
751
757
|
targetActionId: args.actionId ?? undefined,
|
|
758
|
+
expectedNodeLivenessRevision: args.expectedNodeLivenessRevision,
|
|
752
759
|
});
|
|
753
760
|
if (!accepted && args.invocationOrigin === 'registered_action') {
|
|
754
|
-
const settled = await settleDeletedRegisteredAction(args.db, args.workspaceId, args.invocationId);
|
|
761
|
+
const settled = await settleDeletedRegisteredAction(args.db, args.workspaceId, args.invocationId, args.expectedNodeLivenessRevision);
|
|
755
762
|
return { accepted: false, pending: false, settled };
|
|
756
763
|
}
|
|
757
764
|
return { accepted, pending: true };
|
|
@@ -1822,7 +1829,9 @@ async function dispatchNodeAttempt(db, workspaceId, invocationId, nodeId, opts)
|
|
|
1822
1829
|
...(opts.targetActionId ? { actionId: opts.targetActionId } : {}),
|
|
1823
1830
|
...attemptFields,
|
|
1824
1831
|
})
|
|
1825
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(opts.
|
|
1832
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(opts.expectedNodeLivenessRevision !== undefined
|
|
1833
|
+
? [eq(actionInvocations.nodeLivenessRevision, opts.expectedNodeLivenessRevision)]
|
|
1834
|
+
: []), ...(opts.expectedAction ? [
|
|
1826
1835
|
eq(actionInvocations.invocationOrigin, 'registered_action'),
|
|
1827
1836
|
eq(actionInvocations.actionId, opts.expectedAction.id),
|
|
1828
1837
|
sql `EXISTS (
|
|
@@ -1885,7 +1894,9 @@ async function claimRegisteredActionHandoff(db, args) {
|
|
|
1885
1894
|
actionId: claimedActionId,
|
|
1886
1895
|
...attemptFields,
|
|
1887
1896
|
})
|
|
1888
|
-
.where(and(eq(actionInvocations.workspaceId, args.workspaceId), eq(actionInvocations.id, args.invocationId), eq(actionInvocations.invocationOrigin, 'registered_action'), eq(actionInvocations.actionId, args.expectedActionId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES),
|
|
1897
|
+
.where(and(eq(actionInvocations.workspaceId, args.workspaceId), eq(actionInvocations.id, args.invocationId), eq(actionInvocations.invocationOrigin, 'registered_action'), eq(actionInvocations.actionId, args.expectedActionId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(args.expectedNodeLivenessRevision !== undefined
|
|
1898
|
+
? [eq(actionInvocations.nodeLivenessRevision, args.expectedNodeLivenessRevision)]
|
|
1899
|
+
: []), sql `NOT (
|
|
1889
1900
|
${actionInvocations.actionId} = ${claimedActionId}
|
|
1890
1901
|
AND COALESCE(${actionInvocations.dispatchedNodeId}, '') = ${args.nodeId}
|
|
1891
1902
|
AND COALESCE(${actionInvocations.dispatchedProvider}, ${DEFAULT_PROVIDER_NAME}) = ${args.providerName}
|
|
@@ -1922,7 +1933,7 @@ async function bindInvocationToRegisteredNodeAction(db, args) {
|
|
|
1922
1933
|
.returning({ id: actionInvocations.id });
|
|
1923
1934
|
return !!bound;
|
|
1924
1935
|
}
|
|
1925
|
-
async function settleDeletedRegisteredAction(db, workspaceId, invocationId) {
|
|
1936
|
+
async function settleDeletedRegisteredAction(db, workspaceId, invocationId, expectedNodeLivenessRevision) {
|
|
1926
1937
|
// A failed handoff can also mean another dispatcher already advanced the
|
|
1927
1938
|
// same invocation to a different live action. Fail only the FK-null state
|
|
1928
1939
|
// that proves pruning won; never clobber that concurrent dispatch winner.
|
|
@@ -1934,7 +1945,9 @@ async function settleDeletedRegisteredAction(db, workspaceId, invocationId) {
|
|
|
1934
1945
|
completedAt: new Date(),
|
|
1935
1946
|
spawnReservedAt: null,
|
|
1936
1947
|
})
|
|
1937
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), eq(actionInvocations.invocationOrigin, 'registered_action'), isNull(actionInvocations.actionId), or(isNull(actionInvocations.providerAcceptedAttempt), sql `${actionInvocations.providerAcceptedAttempt} <> ${actionInvocations.dispatchAttempts}`), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
1948
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), eq(actionInvocations.invocationOrigin, 'registered_action'), isNull(actionInvocations.actionId), or(isNull(actionInvocations.providerAcceptedAttempt), sql `${actionInvocations.providerAcceptedAttempt} <> ${actionInvocations.dispatchAttempts}`), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(expectedNodeLivenessRevision !== undefined
|
|
1949
|
+
? [eq(actionInvocations.nodeLivenessRevision, expectedNodeLivenessRevision)]
|
|
1950
|
+
: [])))
|
|
1938
1951
|
.returning();
|
|
1939
1952
|
if (failed)
|
|
1940
1953
|
return failed;
|
|
@@ -1952,13 +1965,15 @@ async function settleRegisteredActionSendFailure(db, args) {
|
|
|
1952
1965
|
.returning();
|
|
1953
1966
|
return failed ?? settleDeletedRegisteredAction(db, args.workspaceId, args.invocationId);
|
|
1954
1967
|
}
|
|
1955
|
-
async function snapshotInvocationHandlerNode(db, workspaceId, invocationId, nodeId) {
|
|
1968
|
+
async function snapshotInvocationHandlerNode(db, workspaceId, invocationId, nodeId, expectedNodeLivenessRevision) {
|
|
1956
1969
|
const [updated] = await db
|
|
1957
1970
|
.update(actionInvocations)
|
|
1958
1971
|
.set({
|
|
1959
1972
|
handlerNodeId: sql `COALESCE(${actionInvocations.handlerNodeId}, ${nodeId})`,
|
|
1960
1973
|
})
|
|
1961
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
1974
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.id, invocationId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(expectedNodeLivenessRevision !== undefined
|
|
1975
|
+
? [eq(actionInvocations.nodeLivenessRevision, expectedNodeLivenessRevision)]
|
|
1976
|
+
: [])))
|
|
1962
1977
|
.returning({ id: actionInvocations.id });
|
|
1963
1978
|
return !!updated;
|
|
1964
1979
|
}
|
|
@@ -2038,7 +2053,7 @@ async function dispatchNodeInvocation(args) {
|
|
|
2038
2053
|
// owner performs the last-moment gate below immediately before the send.
|
|
2039
2054
|
const snapshotted = args.agent
|
|
2040
2055
|
? true
|
|
2041
|
-
: await snapshotInvocationHandlerNode(args.db, args.workspaceId, args.invocationId, args.nodeId);
|
|
2056
|
+
: await snapshotInvocationHandlerNode(args.db, args.workspaceId, args.invocationId, args.nodeId, args.expectedNodeLivenessRevision);
|
|
2042
2057
|
if (!snapshotted)
|
|
2043
2058
|
return { accepted: false, pending: false, sent: false };
|
|
2044
2059
|
const connectedBefore = args.registry.isProviderConnected(args.workspaceId, args.nodeId, args.providerName);
|
|
@@ -2061,10 +2076,11 @@ async function dispatchNodeInvocation(args) {
|
|
|
2061
2076
|
retryAfterAt: args.retryAfterAt,
|
|
2062
2077
|
reservationHeld: args.reservationHeld,
|
|
2063
2078
|
skipIncrementAttempts: args.skipIncrementAttempts,
|
|
2079
|
+
expectedNodeLivenessRevision: args.expectedNodeLivenessRevision,
|
|
2064
2080
|
})
|
|
2065
2081
|
: null;
|
|
2066
2082
|
if (!registeredNodeClaim) {
|
|
2067
|
-
const settled = await settleDeletedRegisteredAction(args.db, args.workspaceId, args.invocationId);
|
|
2083
|
+
const settled = await settleDeletedRegisteredAction(args.db, args.workspaceId, args.invocationId, args.expectedNodeLivenessRevision);
|
|
2068
2084
|
return { accepted: false, pending: false, sent: false, settled };
|
|
2069
2085
|
}
|
|
2070
2086
|
}
|
|
@@ -2078,6 +2094,9 @@ async function dispatchNodeInvocation(args) {
|
|
|
2078
2094
|
actionId: args.actionId,
|
|
2079
2095
|
handlerAgentId: args.agent.id,
|
|
2080
2096
|
recordAttempt: !args.skipIncrementAttempts,
|
|
2097
|
+
...(args.expectedNodeLivenessRevision !== undefined
|
|
2098
|
+
? { expectedNodeLivenessRevision: args.expectedNodeLivenessRevision }
|
|
2099
|
+
: {}),
|
|
2081
2100
|
}) ?? false)
|
|
2082
2101
|
: registeredNodeClaim
|
|
2083
2102
|
? await (args.registry.sendAuthorizedActionToProvider?.(args.workspaceId, args.nodeId, args.providerName, frame, {
|
|
@@ -2128,12 +2147,22 @@ async function dispatchNodeInvocation(args) {
|
|
|
2128
2147
|
}
|
|
2129
2148
|
return { accepted: false, pending: false, sent: false };
|
|
2130
2149
|
}
|
|
2131
|
-
// Registered node-action state was committed by the pre-send handoff CAS
|
|
2132
|
-
//
|
|
2133
|
-
//
|
|
2150
|
+
// Registered node-action state was committed by the pre-send handoff CAS;
|
|
2151
|
+
// agent-action state was committed by the socket owner's authorization CAS.
|
|
2152
|
+
// Do not write either generation again after the frame is accepted.
|
|
2134
2153
|
if (registeredNodeClaim) {
|
|
2135
2154
|
return { accepted: true, pending, sent: true };
|
|
2136
2155
|
}
|
|
2156
|
+
if (args.agent && args.actionId) {
|
|
2157
|
+
const [settled] = await args.db
|
|
2158
|
+
.select()
|
|
2159
|
+
.from(actionInvocations)
|
|
2160
|
+
.where(and(eq(actionInvocations.workspaceId, args.workspaceId), eq(actionInvocations.id, args.invocationId)));
|
|
2161
|
+
if (settled && !OPEN_INVOCATION_STATUSES.some((status) => status === settled.status)) {
|
|
2162
|
+
return { accepted: false, pending: false, sent: true, settled };
|
|
2163
|
+
}
|
|
2164
|
+
return { accepted: true, pending, sent: true };
|
|
2165
|
+
}
|
|
2137
2166
|
const finalExpectedActionId = expectedActionId;
|
|
2138
2167
|
const accepted = await dispatchNodeAttempt(args.db, args.workspaceId, args.invocationId, args.nodeId, {
|
|
2139
2168
|
pending,
|
|
@@ -2151,9 +2180,10 @@ async function dispatchNodeInvocation(args) {
|
|
|
2151
2180
|
targetActionId: args.agent || args.actionId
|
|
2152
2181
|
? finalExpectedActionId ?? undefined
|
|
2153
2182
|
: undefined,
|
|
2183
|
+
expectedNodeLivenessRevision: args.expectedNodeLivenessRevision,
|
|
2154
2184
|
});
|
|
2155
2185
|
if (!accepted && args.invocationOrigin === 'registered_action') {
|
|
2156
|
-
const settled = await settleDeletedRegisteredAction(args.db, args.workspaceId, args.invocationId);
|
|
2186
|
+
const settled = await settleDeletedRegisteredAction(args.db, args.workspaceId, args.invocationId, args.expectedNodeLivenessRevision);
|
|
2157
2187
|
return { accepted: false, pending: false, sent: true, settled };
|
|
2158
2188
|
}
|
|
2159
2189
|
if (!accepted && (guardedReleaseHash || guardedReleaseAgentId)) {
|
|
@@ -2351,7 +2381,7 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2351
2381
|
return false;
|
|
2352
2382
|
}
|
|
2353
2383
|
if (isDeletedRegisteredActionInvocation(invocation)) {
|
|
2354
|
-
await failOpenInvocationRows(db, invocation.workspaceId, [invocation.id], 'action_deleted');
|
|
2384
|
+
await failOpenInvocationRows(db, invocation.workspaceId, [invocation.id], 'action_deleted', opts.expectedNodeLivenessRevision);
|
|
2355
2385
|
return false;
|
|
2356
2386
|
}
|
|
2357
2387
|
// Only release capacity the invocation actually holds. A shadowed spawn
|
|
@@ -2368,10 +2398,15 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2368
2398
|
const capability = dispatchActionNameForInvocation(invocation.actionName, recordInput(invocation.input));
|
|
2369
2399
|
const shadow = await fetchNodeAction(db, invocation.workspaceId, invocation.dispatchedNodeId, capability);
|
|
2370
2400
|
if (shadow) {
|
|
2371
|
-
await db
|
|
2401
|
+
const [reset] = await db
|
|
2372
2402
|
.update(actionInvocations)
|
|
2373
2403
|
.set({ status: 'pending', dispatchedAt: null, spawnReservedAt: null, retryAfterAt: null })
|
|
2374
|
-
.where(and(eq(actionInvocations.workspaceId, invocation.workspaceId), eq(actionInvocations.id, invocation.id), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
2404
|
+
.where(and(eq(actionInvocations.workspaceId, invocation.workspaceId), eq(actionInvocations.id, invocation.id), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(opts.expectedNodeLivenessRevision !== undefined
|
|
2405
|
+
? [eq(actionInvocations.nodeLivenessRevision, opts.expectedNodeLivenessRevision)]
|
|
2406
|
+
: [])))
|
|
2407
|
+
.returning({ id: actionInvocations.id });
|
|
2408
|
+
if (!reset)
|
|
2409
|
+
return false;
|
|
2375
2410
|
return false;
|
|
2376
2411
|
}
|
|
2377
2412
|
}
|
|
@@ -2391,6 +2426,7 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2391
2426
|
expectedActionId: invocation.actionId,
|
|
2392
2427
|
invocationOrigin: invocation.invocationOrigin,
|
|
2393
2428
|
retryAfterAt: opts.retryAfterAt ?? null,
|
|
2429
|
+
expectedNodeLivenessRevision: opts.expectedNodeLivenessRevision,
|
|
2394
2430
|
});
|
|
2395
2431
|
return dispatched.accepted;
|
|
2396
2432
|
}
|
|
@@ -2439,6 +2475,7 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2439
2475
|
input,
|
|
2440
2476
|
queue: target.queue,
|
|
2441
2477
|
reservationHeld: false,
|
|
2478
|
+
expectedNodeLivenessRevision: opts.expectedNodeLivenessRevision,
|
|
2442
2479
|
}, { failIfUnavailable: false });
|
|
2443
2480
|
if (dispatched.providerUnavailable) {
|
|
2444
2481
|
providerRejectedNodeIds.add(placement.node.id);
|
|
@@ -2465,9 +2502,10 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2465
2502
|
? { id: expectedActionId, name: actionToSend }
|
|
2466
2503
|
: undefined,
|
|
2467
2504
|
targetActionId: target?.id,
|
|
2505
|
+
expectedNodeLivenessRevision: opts.expectedNodeLivenessRevision,
|
|
2468
2506
|
});
|
|
2469
2507
|
if (!accepted && invocation.invocationOrigin === 'registered_action') {
|
|
2470
|
-
await settleDeletedRegisteredAction(db, invocation.workspaceId, invocation.id);
|
|
2508
|
+
await settleDeletedRegisteredAction(db, invocation.workspaceId, invocation.id, opts.expectedNodeLivenessRevision);
|
|
2471
2509
|
}
|
|
2472
2510
|
return accepted;
|
|
2473
2511
|
}
|
|
@@ -2484,6 +2522,7 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2484
2522
|
invocationOrigin: invocation.invocationOrigin,
|
|
2485
2523
|
input,
|
|
2486
2524
|
reservationHeld,
|
|
2525
|
+
expectedNodeLivenessRevision: opts.expectedNodeLivenessRevision,
|
|
2487
2526
|
});
|
|
2488
2527
|
return dispatched.accepted;
|
|
2489
2528
|
}
|
|
@@ -2494,7 +2533,7 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2494
2533
|
}
|
|
2495
2534
|
}
|
|
2496
2535
|
if (providerRejectedNodeIds.size > 0) {
|
|
2497
|
-
await failInvocationForUnavailableProvider(db, invocation.workspaceId, invocation.id);
|
|
2536
|
+
await failInvocationForUnavailableProvider(db, invocation.workspaceId, invocation.id, opts.expectedNodeLivenessRevision);
|
|
2498
2537
|
return false;
|
|
2499
2538
|
}
|
|
2500
2539
|
await db
|
|
@@ -2507,18 +2546,31 @@ export async function rescheduleNodeInvocation(db, registry, invocation, opts =
|
|
|
2507
2546
|
spawnReservedAt: null,
|
|
2508
2547
|
retryAfterAt: opts.retryAfterAt ?? nextRetryAfter(invocation.dispatchAttempts + 1),
|
|
2509
2548
|
})
|
|
2510
|
-
.where(and(eq(actionInvocations.workspaceId, invocation.workspaceId), eq(actionInvocations.id, invocation.id), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
2549
|
+
.where(and(eq(actionInvocations.workspaceId, invocation.workspaceId), eq(actionInvocations.id, invocation.id), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(opts.expectedNodeLivenessRevision !== undefined
|
|
2550
|
+
? [eq(actionInvocations.nodeLivenessRevision, opts.expectedNodeLivenessRevision)]
|
|
2551
|
+
: [])));
|
|
2511
2552
|
return false;
|
|
2512
2553
|
}
|
|
2513
|
-
export async function rescheduleInvocationsForLostNode(db, registry, workspaceId, nodeId) {
|
|
2554
|
+
export async function rescheduleInvocationsForLostNode(db, registry, workspaceId, nodeId, opts = {}) {
|
|
2514
2555
|
const rows = await db
|
|
2515
2556
|
.select()
|
|
2516
2557
|
.from(actionInvocations)
|
|
2517
|
-
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.dispatchedNodeId, nodeId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES)
|
|
2558
|
+
.where(and(eq(actionInvocations.workspaceId, workspaceId), eq(actionInvocations.dispatchedNodeId, nodeId), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES),
|
|
2559
|
+
// The dispatch trigger stamps the accepting node generation. A reconnect
|
|
2560
|
+
// can dispatch new work while settlement runs, including in the same
|
|
2561
|
+
// timestamp second; only attempts older than this claim are recoverable.
|
|
2562
|
+
...(opts.claimRevision !== undefined
|
|
2563
|
+
? [lt(actionInvocations.nodeLivenessRevision, opts.claimRevision)]
|
|
2564
|
+
: [])));
|
|
2518
2565
|
let rescheduled = 0;
|
|
2519
2566
|
for (const invocation of rows) {
|
|
2520
2567
|
try {
|
|
2521
|
-
if (await rescheduleNodeInvocation(db, registry, invocation, {
|
|
2568
|
+
if (await rescheduleNodeInvocation(db, registry, invocation, {
|
|
2569
|
+
retryAfterAt: nextRetryAfter(invocation.dispatchAttempts + 1),
|
|
2570
|
+
...(opts.claimRevision !== undefined
|
|
2571
|
+
? { expectedNodeLivenessRevision: invocation.nodeLivenessRevision }
|
|
2572
|
+
: {}),
|
|
2573
|
+
})) {
|
|
2522
2574
|
rescheduled++;
|
|
2523
2575
|
}
|
|
2524
2576
|
}
|
|
@@ -2534,7 +2586,9 @@ export async function rescheduleInvocationsForLostNode(db, registry, workspaceId
|
|
|
2534
2586
|
spawnReservedAt: null,
|
|
2535
2587
|
retryAfterAt: nextRetryAfter(invocation.dispatchAttempts + 1),
|
|
2536
2588
|
})
|
|
2537
|
-
.where(eq(actionInvocations.id, invocation.id))
|
|
2589
|
+
.where(and(eq(actionInvocations.workspaceId, invocation.workspaceId), eq(actionInvocations.id, invocation.id), inArray(actionInvocations.status, OPEN_INVOCATION_STATUSES), ...(opts.claimRevision !== undefined
|
|
2590
|
+
? [eq(actionInvocations.nodeLivenessRevision, invocation.nodeLivenessRevision)]
|
|
2591
|
+
: [])));
|
|
2538
2592
|
}
|
|
2539
2593
|
}
|
|
2540
2594
|
return rescheduled;
|