test: use Effect runtime in runner deadlock case (#25045)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { Deferred, Effect, Exit, Fiber, Ref, Scope } from "effect"
|
import { Deferred, Effect, Exit, Fiber, Ref, Scope } from "effect"
|
||||||
import { Runner } from "@/effect/runner"
|
import { Runner } from "@/effect/runner"
|
||||||
import { it } from "../lib/effect"
|
import { it } from "../lib/effect"
|
||||||
@@ -198,58 +198,52 @@ describe("Runner", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
test("cancel does not deadlock when replacement work starts before interrupted run exits", async () => {
|
it.live(
|
||||||
function defer() {
|
"cancel does not deadlock when replacement work starts before interrupted run exits",
|
||||||
let resolve!: () => void
|
Effect.gen(function* () {
|
||||||
const promise = new Promise<void>((done) => {
|
const s = yield* Scope.Scope
|
||||||
resolve = done
|
const hit = yield* Deferred.make<void>()
|
||||||
})
|
const hold = yield* Deferred.make<void>()
|
||||||
return { promise, resolve }
|
const done = yield* Deferred.make<void>()
|
||||||
}
|
|
||||||
|
|
||||||
function fail(ms: number, msg: string) {
|
yield* Effect.gen(function* () {
|
||||||
return new Promise<never>((_, reject) => {
|
const runner = Runner.make<string>(s)
|
||||||
setTimeout(() => reject(new Error(msg)), ms)
|
const first = Effect.never.pipe(
|
||||||
})
|
Effect.onInterrupt(() => Deferred.succeed(hit, undefined)),
|
||||||
}
|
Effect.ensuring(Deferred.await(hold)),
|
||||||
|
Effect.as("first"),
|
||||||
|
)
|
||||||
|
|
||||||
const s = await Effect.runPromise(Scope.make())
|
const a = yield* runner.ensureRunning(first).pipe(Effect.exit, Effect.forkChild)
|
||||||
const hit = defer()
|
yield* Effect.sleep("10 millis")
|
||||||
const hold = defer()
|
|
||||||
const done = defer()
|
const stop = yield* runner.cancel.pipe(Effect.forkChild)
|
||||||
try {
|
yield* Deferred.await(hit).pipe(Effect.timeout("250 millis"))
|
||||||
const runner = Runner.make<string>(s)
|
|
||||||
const first = Effect.never.pipe(
|
const b = yield* runner.ensureRunning(Deferred.await(done).pipe(Effect.as("second"))).pipe(Effect.forkChild)
|
||||||
Effect.onInterrupt(() => Effect.sync(() => hit.resolve())),
|
yield* Effect.yieldNow
|
||||||
Effect.ensuring(Effect.promise(() => hold.promise)),
|
expect(runner.busy).toBe(true)
|
||||||
Effect.as("first"),
|
|
||||||
|
yield* Deferred.succeed(hold, undefined)
|
||||||
|
const stopExit = yield* Fiber.await(stop).pipe(Effect.timeout("250 millis"))
|
||||||
|
expect(Exit.isSuccess(stopExit)).toBe(true)
|
||||||
|
|
||||||
|
expect(runner.busy).toBe(true)
|
||||||
|
yield* Deferred.succeed(done, undefined)
|
||||||
|
expect(yield* Fiber.join(b).pipe(Effect.timeout("250 millis"))).toBe("second")
|
||||||
|
expect(runner.busy).toBe(false)
|
||||||
|
|
||||||
|
const exit = yield* Fiber.join(a)
|
||||||
|
expect(Exit.isFailure(exit)).toBe(true)
|
||||||
|
}).pipe(
|
||||||
|
Effect.ensuring(
|
||||||
|
Effect.all([Deferred.succeed(hold, undefined), Deferred.succeed(done, undefined)], { discard: true }).pipe(
|
||||||
|
Effect.ignore,
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
const a = Effect.runPromiseExit(runner.ensureRunning(first))
|
)
|
||||||
await Bun.sleep(10)
|
|
||||||
|
|
||||||
const stop = Effect.runPromise(runner.cancel)
|
|
||||||
await Promise.race([hit.promise, fail(250, "cancel did not interrupt running work")])
|
|
||||||
|
|
||||||
const b = Effect.runPromise(runner.ensureRunning(Effect.promise(() => done.promise).pipe(Effect.as("second"))))
|
|
||||||
expect(runner.busy).toBe(true)
|
|
||||||
|
|
||||||
hold.resolve()
|
|
||||||
await Promise.race([stop, fail(250, "cancel deadlocked while replacement run was active")])
|
|
||||||
|
|
||||||
expect(runner.busy).toBe(true)
|
|
||||||
done.resolve()
|
|
||||||
expect(await b).toBe("second")
|
|
||||||
expect(runner.busy).toBe(false)
|
|
||||||
|
|
||||||
const exit = await a
|
|
||||||
expect(Exit.isFailure(exit)).toBe(true)
|
|
||||||
} finally {
|
|
||||||
hold.resolve()
|
|
||||||
done.resolve()
|
|
||||||
await Promise.race([Effect.runPromise(Scope.close(s, Exit.void)), fail(1000, "runner scope did not close")])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// --- shell semantics ---
|
// --- shell semantics ---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user