tardie 0.22.1 → 0.22.2-rc.258
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/package.json +2 -2
- package/src/bun/host.ts +7 -2
- package/src/cloudflare/host.ts +8 -4
- package/src/core/interaction/timeout.ts +45 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tardie",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2-rc.258",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Clavia, Inc.",
|
|
6
6
|
"description": "A durable agent harness. State is a pure function of the log.",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"tardigrade": {
|
|
19
|
-
"sourceTree": "
|
|
19
|
+
"sourceTree": "a86f7a006f44eb41fcc97c3ebae698b8fc0db60a"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src",
|
package/src/bun/host.ts
CHANGED
|
@@ -19,7 +19,7 @@ import type { ThreadAllocation } from "tardie/core/actor/allocation"
|
|
|
19
19
|
import { formatThreadAddress, parseThreadAddress, type ThreadAddress, type ProviderEndpoint } from "tardie/core/transport/endpoint"
|
|
20
20
|
import type { Link } from "tardie/core/transport/link"
|
|
21
21
|
import { actorEventKeyOf, actorThreadsOf, type ActorThreadRecord, type ThreadRegistered, type ThreadRequested } from "tardie/core/actor"
|
|
22
|
-
import { alarmFired, earliestDeadlineOf } from "tardie/core/interaction/timeout"
|
|
22
|
+
import { alarmFired, deadlineCancellationEventsAt, earliestDeadlineOf } from "tardie/core/interaction/timeout"
|
|
23
23
|
import { methodIngressKeyOf } from "tardie/core/interaction/invocation"
|
|
24
24
|
import { type ActorMethods } from "tardie/core/actor/method"
|
|
25
25
|
import {
|
|
@@ -595,7 +595,12 @@ export const createBunHost = async <R = never>(options: BunHostOptions<R>): Prom
|
|
|
595
595
|
const active = await runtimeOf(thread)
|
|
596
596
|
if (active.alarm?.deadlineAt !== deadlineAt) return
|
|
597
597
|
delete active.alarm
|
|
598
|
-
|
|
598
|
+
// synchronizeAlarm commits each alarm with its crossed deadline cancellations (host.test.ts, "an alarm commits its deadline cancellation atomically").
|
|
599
|
+
const log = await active.runtime.runPromise(active.store.read)
|
|
600
|
+
await appendTo(thread, [
|
|
601
|
+
alarmFired({ scheduledFor: deadlineAt, at }),
|
|
602
|
+
...(methods === undefined ? [] : deadlineCancellationEventsAt(log, methods, at))
|
|
603
|
+
])
|
|
599
604
|
driver.mark(thread)
|
|
600
605
|
await drive()
|
|
601
606
|
})
|
package/src/cloudflare/host.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { ThreadAllocator, reserveRootThread } from "tardie/core/actor/allocation
|
|
|
12
12
|
import { initializingThreadAllocator } from "tardie/host/allocation"
|
|
13
13
|
import { formatThreadAddress, type ThreadAddress, type ProviderEndpoint } from "tardie/core/transport/endpoint"
|
|
14
14
|
import type { Link } from "tardie/core/transport/link"
|
|
15
|
-
import { alarmFired, earliestDeadlineOf } from "tardie/core/interaction/timeout"
|
|
15
|
+
import { alarmFired, deadlineCancellationEventsAt, earliestDeadlineOf } from "tardie/core/interaction/timeout"
|
|
16
16
|
import { methodIngressKeyOf } from "tardie/core/interaction/invocation"
|
|
17
17
|
import { type ActorMethods } from "tardie/core/actor/method"
|
|
18
18
|
import {
|
|
@@ -230,12 +230,16 @@ export async function createCloudflareThreadHost<R = never>(options: CloudflareT
|
|
|
230
230
|
const nextMethodDeadline = async (): Promise<number | undefined> => {
|
|
231
231
|
return earliestDeadlineOf(await Effect.runPromise(events.read), methods)
|
|
232
232
|
}
|
|
233
|
+
// recordAlarm commits each alarm with its crossed deadline cancellations (test/actor.workers.ts, "an alarm commits its deadline cancellation atomically").
|
|
233
234
|
const recordAlarm = async (at: number): Promise<void> => {
|
|
234
|
-
const
|
|
235
|
+
const log = await Effect.runPromise(events.read)
|
|
236
|
+
const deadline = earliestDeadlineOf(log, methods)
|
|
235
237
|
if (deadline !== undefined && deadline <= at) {
|
|
236
|
-
const result = await Effect.runPromise(
|
|
238
|
+
const result = await Effect.runPromise(store.append([
|
|
239
|
+
alarmFired({ scheduledFor: deadline, at }),
|
|
240
|
+
...(methods === undefined ? [] : deadlineCancellationEventsAt(log, methods, at))
|
|
241
|
+
]))
|
|
237
242
|
if (result.appended > 0) driver.mark(options.thread)
|
|
238
|
-
await Effect.runPromise(syncCommit(result))
|
|
239
243
|
} else {
|
|
240
244
|
await options.storage.sync()
|
|
241
245
|
}
|
|
@@ -10,7 +10,7 @@ import { type ActorInvocationContext, invocationKey, sameInvocation, type Invoca
|
|
|
10
10
|
|
|
11
11
|
import { recordedDispatchOf, terminalInvocationRefOf, terminalStorageKey, type RecordedDispatch } from "./records-compat"
|
|
12
12
|
|
|
13
|
-
import { cancellationStateOf, initialMethodStates, reduceMethodStates } from "./state"
|
|
13
|
+
import { cancellationStateOf, initialMethodStates, reduceMethodStates, type ActorMethodView } from "./state"
|
|
14
14
|
import { type ActorMethods } from "../actor/method"
|
|
15
15
|
|
|
16
16
|
export interface AlarmFiredFields {
|
|
@@ -142,6 +142,42 @@ const deadlineCancellationTransition = (invocation: InvocationRef, deadlineAt: n
|
|
|
142
142
|
})]
|
|
143
143
|
})
|
|
144
144
|
|
|
145
|
+
// deadlineCancellationsAt selects crossed deadlines for running cancellable invocations (timeout.test.ts, "cancellation selection projects one cancellation from an eligible invocation").
|
|
146
|
+
const deadlineCancellationsAt = (
|
|
147
|
+
log: ReadonlyArray<Event>,
|
|
148
|
+
methods: ActorMethods,
|
|
149
|
+
at: number
|
|
150
|
+
): ReadonlyArray<InvocationDeadline> => {
|
|
151
|
+
const views = new Map<string, ActorMethodView<unknown>>()
|
|
152
|
+
return invocationDeadlinesOf(log).flatMap(({ invocation, deadlineAt }) => {
|
|
153
|
+
if (deadlineAt > at) return []
|
|
154
|
+
const method = methods[invocation.method]
|
|
155
|
+
if (method === undefined || method.cancellation === undefined || invocationSettled(log, invocation)) return []
|
|
156
|
+
let view = views.get(invocation.method)
|
|
157
|
+
if (view === undefined) {
|
|
158
|
+
view = replayProjection(method.projection, log)
|
|
159
|
+
views.set(invocation.method, view)
|
|
160
|
+
}
|
|
161
|
+
return cancellationStateOf(method, view, invocation) !== "running" ? [] : [{ invocation, deadlineAt }]
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// deadlineCancellationEventsAt constructs durable requests for selected deadline cancellations (test/actor.workers.ts, "an alarm commits its deadline cancellation atomically").
|
|
166
|
+
export const deadlineCancellationEventsAt = (
|
|
167
|
+
log: ReadonlyArray<Event>,
|
|
168
|
+
methods: ActorMethods,
|
|
169
|
+
at: number
|
|
170
|
+
): ReadonlyArray<Event> =>
|
|
171
|
+
deadlineCancellationsAt(log, methods, at).map(({ invocation, deadlineAt }) =>
|
|
172
|
+
cancellationRequested({
|
|
173
|
+
request: `deadline/${invocation.method}/${invocation.id}/${invocation.epoch}/${deadlineAt}`,
|
|
174
|
+
invocation,
|
|
175
|
+
cause: "deadline",
|
|
176
|
+
deadlineAt,
|
|
177
|
+
at
|
|
178
|
+
})
|
|
179
|
+
)
|
|
180
|
+
|
|
145
181
|
// methodTimeoutDerivation turns alarm facts into method terminals without reading a clock.
|
|
146
182
|
export const methodTimeoutDerivation: CompleteTransitionDerivation = (log) => {
|
|
147
183
|
const terminal = terminalCalls(log)
|
|
@@ -153,18 +189,15 @@ export const methodTimeoutDerivation: CompleteTransitionDerivation = (log) => {
|
|
|
153
189
|
})
|
|
154
190
|
}
|
|
155
191
|
|
|
156
|
-
// methodDeadlineCancellationDerivation
|
|
192
|
+
// methodDeadlineCancellationDerivation repairs missing cancellations from recorded alarms (timeout.test.ts, "legacy recovery derives an alarm's missing cancellation exactly once").
|
|
157
193
|
export const methodDeadlineCancellationDerivation = (methods: ActorMethods): CompleteTransitionDerivation => (log) => {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
cancellationStateOf(method, replayProjection(method.projection, log), invocation) !== "running") return []
|
|
166
|
-
return [deadlineCancellationTransition(invocation, deadlineAt)]
|
|
167
|
-
})
|
|
194
|
+
const latestAlarmAt = alarmsOf(log).reduce<number | undefined>(
|
|
195
|
+
(latest, alarm) => latest === undefined ? alarm.at : Math.max(latest, alarm.at),
|
|
196
|
+
undefined
|
|
197
|
+
)
|
|
198
|
+
if (latestAlarmAt === undefined) return []
|
|
199
|
+
return deadlineCancellationsAt(log, methods, latestAlarmAt)
|
|
200
|
+
.map(({ invocation, deadlineAt }) => deadlineCancellationTransition(invocation, deadlineAt))
|
|
168
201
|
}
|
|
169
202
|
|
|
170
203
|
/** @deprecated Use methodTimeoutDerivation. This compatibility name describes a complete-history transition derivation. */
|