import { assert, assertEquals } from "@std/assert";
import {
Cause,
Clock as EffectClock,
Deferred,
Duration,
Effect,
Exit,
Fiber,
Option,
Schema,
} from "effect";
import { TestClock } from "effect/testing";
import { InvalidPolicy, JobFailure, type Policy } from "./contracts.ts";
import { makeDriver } from "./driver.ts";
import { nativeWorker } from "./reference.ts";
import { runWorker } from "./worker.ts";
const policy: Policy = { runs: 2, delayMillis: 20, retries: 1, retryMillis: 5 };
const failure = (exit: Exit.Exit): E => {
assert(Exit.isFailure(exit));
const error = Cause.findErrorOption(exit.cause);
assert(Option.isSome(error));
return error.value;
};
const run = (effect: Effect.Effect) =>
Effect.runPromise(effect.pipe(Effect.timeout("5 seconds")));
Deno.test("source worker matches native repetition and retry under virtual time, including Effect replay", async () => {
for (const worker of [runWorker, nativeWorker]) {
await run(
Effect.gen(function* () {
const first = yield* Deferred.make();
const times: number[] = [];
const driver = makeDriver({
execute: (id) =>
Effect.gen(function* () {
times.push(yield* EffectClock.currentTimeMillis);
yield* Deferred.succeed(first, undefined);
return yield* id % 2 === 1
? Effect.fail(new JobFailure({ kind: "Transient", id }))
: Effect.succeed(id * 10);
}),
});
const program = worker(policy).pipe(Effect.provide(driver.layer));
assertEquals(driver.events, []);
for (let replay = 0; replay < 2; replay++) {
const fiber = yield* Effect.forkChild(program);
yield* Deferred.await(first);
yield* TestClock.adjust(100);
assertEquals(yield* Fiber.join(fiber), {
completed: 2,
last: Option.some((replay + 1) * 40),
});
assertEquals(driver.active.size, 0);
}
assertEquals(times, [0, 5, 25, 30, 100, 105, 125, 130]);
assertEquals(driver.attempts, 8);
const jobs = driver.events.filter((event) => !event.startsWith("wait"));
assertEquals(
jobs,
Array.from({ length: 8 }, (_, n) => [
`open:${n + 1}`,
`acquired:${n + 1}`,
`execute:${n + 1}`,
`close:${n + 1}`,
`released:${n + 1}`,
]).flat(),
);
}).pipe(Effect.provide(TestClock.layer())),
);
}
});
Deno.test("zero jobs are inert and policy validation precedes the zero-job branch", async () => {
const driver = makeDriver();
assertEquals(
await run(
runWorker({ ...policy, runs: 0 }).pipe(Effect.provide(driver.layer)),
),
{ completed: 0, last: Option.none() },
);
for (
const input of [{ ...policy, runs: 0, delayMillis: -1 }, {
...policy,
retries: -1,
}]
) {
const error = failure(
await Effect.runPromiseExit(
runWorker(input).pipe(Effect.provide(driver.layer)),
),
);
assert(error instanceof InvalidPolicy);
}
for (
const input of [{ ...policy, runs: "2" }, { ...policy, retries: 0.5 }, {}]
) {
const error = failure(
await Effect.runPromiseExit(
runWorker(input).pipe(Effect.provide(driver.layer)),
),
);
assert(error instanceof Schema.SchemaError);
}
assertEquals(driver.events, []);
});
Deno.test("zero retries, exhaustion and permanent failures retain the typed failure and close each attempt", async () => {
for (
const [kind, retries, expected] of [["Transient", 0, 1], [
"Transient",
2,
3,
], ["Fatal", 2, 1]] as const
) {
const driver = makeDriver({
execute: (id) => Effect.fail(new JobFailure({ kind, id })),
});
const exit = await run(
Effect.exit(
runWorker({ ...policy, retries, retryMillis: 0 }).pipe(
Effect.provide(driver.layer),
),
),
);
const error = failure(exit);
assert(error instanceof JobFailure);
assertEquals(error.kind, kind);
assertEquals(driver.attempts, expected);
assertEquals(driver.active.size, 0);
assertEquals(
driver.events.filter((event) => event.startsWith("released:")).length,
expected,
);
assert(!driver.events.some((event) => event.startsWith("wait:")));
}
});
Deno.test("acquisition failure never releases an absent resource", async () => {
const driver = makeDriver({
open: (id) => Effect.fail(new JobFailure({ kind: "Fatal", id })),
});
const error = failure(
await run(
Effect.exit(runWorker(policy).pipe(Effect.provide(driver.layer))),
),
);
assert(error instanceof JobFailure);
assertEquals(driver.events, ["open:1"]);
assertEquals(driver.active.size, 0);
});
Deno.test("execution and release defects retain their channel and bypass typed retries", async () => {
for (
const options of [{ execute: () => Effect.die("execution") }, {
close: () => Effect.die("release"),
}]
) {
const driver = makeDriver(options);
const exit = await run(
Effect.exit(runWorker(policy).pipe(Effect.provide(driver.layer))),
);
assert(Exit.isFailure(exit) && Cause.hasDies(exit.cause));
assert(Option.isNone(Cause.findErrorOption(exit.cause)));
assertEquals(driver.opened, 1);
assertEquals(driver.active.size, 0);
assertEquals(driver.events.at(-1), "released:1");
}
});
Deno.test("shutdown during work waits behind a blocked source-owned release", async () => {
const release = Deferred.makeUnsafe();
await run(
Effect.gen(function* () {
const entered = yield* Deferred.make();
const releasing = yield* Deferred.make();
let workClosed = false;
let interrupted = false;
const driver = makeDriver({
execute: () =>
Deferred.succeed(entered, undefined).pipe(
Effect.andThen(Effect.never),
Effect.ensuring(Effect.sync(() => {
workClosed = true;
})),
),
close: () =>
Deferred.succeed(releasing, undefined).pipe(
Effect.andThen(Deferred.await(release)),
),
});
const fiber = yield* Effect.forkChild(
runWorker(policy).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(entered);
const closing = yield* Effect.forkChild(
Fiber.interrupt(fiber).pipe(Effect.andThen(Effect.sync(() => {
interrupted = true;
}))),
);
yield* Deferred.await(releasing);
yield* Effect.yieldNow;
yield* Effect.sync(() => {
assertEquals(
[workClosed, interrupted, driver.active.size, driver.attempts],
[true, false, 1, 1],
);
assertEquals(driver.events.at(-1), "close:1");
}).pipe(Effect.ensuring(Deferred.succeed(release, undefined)));
yield* Fiber.join(closing);
const exit = yield* Fiber.await(fiber);
assert(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause));
assertEquals([interrupted, driver.active.size, driver.attempts], [
true,
0,
1,
]);
assert(!driver.events.some((event) => event.startsWith("wait:")));
}).pipe(Effect.ensuring(Deferred.succeed(release, undefined))),
);
});
Deno.test("shutdown during a repetition delay closes the wait and starts no later job", async () => {
await run(Effect.gen(function* () {
const entered = yield* Deferred.make();
let waitClosed = false;
const driver = makeDriver({
wait: () =>
Deferred.succeed(entered, undefined).pipe(
Effect.andThen(Effect.never),
Effect.ensuring(Effect.sync(() => {
waitClosed = true;
})),
),
});
const fiber = yield* Effect.forkChild(
runWorker(policy).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(entered);
assertEquals(driver.active.size, 0);
yield* Fiber.interrupt(fiber);
assertEquals([waitClosed, driver.attempts, driver.events.at(-1)], [
true,
1,
"wait-closed:20",
]);
}));
});
Deno.test("shutdown during a native retry delay closes its clock operation without another attempt", async () => {
await run(Effect.gen(function* () {
const entered = yield* Deferred.make();
const clock = yield* EffectClock.Clock;
let waitClosed = false;
const observed: EffectClock.Clock = {
currentTimeMillisUnsafe: () => clock.currentTimeMillisUnsafe(),
currentTimeMillis: clock.currentTimeMillis,
currentTimeNanosUnsafe: () => clock.currentTimeNanosUnsafe(),
currentTimeNanos: clock.currentTimeNanos,
monotonicTimeNanosUnsafe: () => clock.monotonicTimeNanosUnsafe(),
monotonicTimeNanos: clock.monotonicTimeNanos,
sleep: (duration) =>
Deferred.succeed(entered, Duration.toMillis(duration)).pipe(
Effect.andThen(Effect.never),
Effect.ensuring(Effect.sync(() => {
waitClosed = true;
})),
),
};
const driver = makeDriver({
execute: (id) => Effect.fail(new JobFailure({ kind: "Transient", id })),
});
const fiber = yield* Effect.forkChild(
runWorker({ ...policy, retryMillis: 1000 }).pipe(
Effect.provide(driver.layer),
Effect.provideService(EffectClock.Clock, observed),
),
);
assertEquals(yield* Deferred.await(entered), 1000);
assertEquals(driver.active.size, 0);
yield* Fiber.interrupt(fiber);
assertEquals([waitClosed, driver.attempts], [true, 1]);
assert(!driver.events.some((event) => event.startsWith("wait:")));
}));
});
Deno.test("a failed attempt finishes release before retrying", async () => {
const release = Deferred.makeUnsafe();
await run(
Effect.gen(function* () {
const releasing = yield* Deferred.make();
const driver = makeDriver({
execute: (id) =>
id === 1
? Effect.fail(new JobFailure({ kind: "Transient", id }))
: Effect.succeed(id * 10),
close: (id) =>
id === 1
? Deferred.succeed(releasing, undefined).pipe(
Effect.andThen(Deferred.await(release)),
)
: Effect.void,
});
const fiber = yield* Effect.forkChild(
runWorker({ ...policy, runs: 1, retryMillis: 0 }).pipe(
Effect.provide(driver.layer),
),
);
yield* Deferred.await(releasing);
yield* Effect.yieldNow;
assertEquals([driver.opened, driver.attempts, driver.active.size], [
1,
1,
1,
]);
yield* Deferred.succeed(release, undefined);
assertEquals(yield* Fiber.join(fiber), {
completed: 1,
last: Option.some(20),
});
assertEquals(driver.active.size, 0);
assertEquals(
driver.events.indexOf("released:1") < driver.events.indexOf("open:2"),
true,
);
}).pipe(Effect.ensuring(Deferred.succeed(release, undefined))),
);
});
Deno.test("worker test assertion failures unblock release before joining children", async () => {
for (const retrying of [false, true]) {
for (const stage of ["started", "releasing"]) {
const release = Deferred.makeUnsafe();
const entered = Deferred.makeUnsafe();
const releasing = Deferred.makeUnsafe();
const driver = makeDriver({
execute: (id) =>
Deferred.succeed(entered, undefined).pipe(
Effect.andThen(
retrying
? Effect.fail(new JobFailure({ kind: "Transient", id }))
: Effect.never,
),
),
close: () =>
Deferred.succeed(releasing, undefined).pipe(
Effect.andThen(Deferred.await(release)),
),
});
let timer: ReturnType | undefined;
try {
const exit = await Promise.race([
Effect.runPromiseExit(
Effect.gen(function* () {
const fiber = yield* Effect.forkChild(
runWorker(policy).pipe(Effect.provide(driver.layer)),
);
yield* Deferred.await(entered);
if (stage === "started") assert(false, "injected assertion");
if (!retrying) yield* Effect.forkChild(Fiber.interrupt(fiber));
yield* Deferred.await(releasing);
assert(false, "injected assertion");
}).pipe(Effect.ensuring(Deferred.succeed(release, undefined))),
),
new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error("cleanup hung")), 1000);
}),
]);
assert(Exit.isFailure(exit) && Cause.hasDies(exit.cause));
assert(Cause.pretty(exit.cause).includes("injected assertion"));
assertEquals(driver.active.size, 0);
assertEquals(driver.events.at(-1), "released:1");
} finally {
clearTimeout(timer);
await Effect.runPromise(Deferred.succeed(release, undefined));
}
}
}
});