refactor(core): replace background job service (#34559)

This commit is contained in:
Kit Langton
2026-06-29 23:53:35 -04:00
committed by GitHub
parent 6846542115
commit 461a1c3ab4
24 changed files with 593 additions and 673 deletions
+11 -17
View File
@@ -2,7 +2,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { InstanceState } from "@/effect/instance-state"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Runner } from "@/effect/runner"
import { BackgroundJob } from "@/background/job"
import { Job } from "@/job"
import { Effect, Latch, Layer, Scope, Context } from "effect"
import { Session } from "./session"
import { SessionID } from "./schema"
@@ -29,7 +29,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Se
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const background = yield* BackgroundJob.Service
const jobs = yield* Job.Service
const status = yield* SessionStatus.Service
const state = yield* InstanceState.make(
@@ -75,7 +75,7 @@ export const layer = Layer.effect(
})
const cancel = Effect.fn("SessionRunState.cancel")(function* (sessionID: SessionID) {
yield* cancelBackgroundJobs(background, sessionID)
yield* cancelJobs(jobs, sessionID)
const data = yield* InstanceState.get(state)
const existing = data.runners.get(sessionID)
if (!existing) {
@@ -108,31 +108,25 @@ export const layer = Layer.effect(
}),
)
export const defaultLayer = layer.pipe(
Layer.provide(BackgroundJob.defaultLayer),
Layer.provide(SessionStatus.defaultLayer),
)
export const defaultLayer = layer.pipe(Layer.provide(Job.defaultLayer), Layer.provide(SessionStatus.defaultLayer))
const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(function* (
background: BackgroundJob.Interface,
sessionID: SessionID,
) {
const jobs = yield* background.list()
const cancelJobs = Effect.fn("SessionRunState.cancelJobs")(function* (jobs: Job.Interface, sessionID: SessionID) {
const running = yield* jobs.list()
const pending = new Set<string>([sessionID])
const cancelled = new Set<string>()
const matches = (job: BackgroundJob.Info) => {
const matches = (job: Job.Info) => {
if (job.status !== "running") return false
if (cancelled.has(job.id)) return false
if (pending.has(job.id)) return true
if (typeof job.metadata?.sessionId === "string" && pending.has(job.metadata.sessionId)) return true
return typeof job.metadata?.parentSessionId === "string" && pending.has(job.metadata.parentSessionId)
}
let batch = jobs.filter(matches)
let batch = running.filter(matches)
while (batch.length > 0) {
yield* Effect.forEach(
batch,
(job) =>
background.cancel(job.id).pipe(
jobs.cancel(job.id).pipe(
Effect.tap(() =>
Effect.sync(() => {
cancelled.add(job.id)
@@ -143,7 +137,7 @@ const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(f
),
{ concurrency: "unbounded", discard: true },
)
batch = jobs.filter(matches)
batch = running.filter(matches)
}
})
@@ -151,6 +145,6 @@ function busyError(sessionID: SessionID) {
return new Session.BusyError({ sessionID })
}
export const node = LayerNode.make({ service: Service, layer: layer, deps: [BackgroundJob.node, SessionStatus.node] })
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Job.node, SessionStatus.node] })
export * as SessionRunState from "./run-state"
+10 -13
View File
@@ -4,7 +4,7 @@ import { Slug } from "@opencode-ai/core/util/slug"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { serviceUse } from "@opencode-ai/core/effect/service-use"
import path from "path"
import { BackgroundJob } from "@/background/job"
import { Job } from "@/job"
import { Decimal } from "decimal.js"
import type { ProviderMetadata, Usage } from "@opencode-ai/llm"
import { InstallationVersion } from "@opencode-ai/core/installation/version"
@@ -491,13 +491,13 @@ export type Patch = Omit<Partial<Info>, "time" | "share" | "summary" | "revert"
export const layer: Layer.Layer<
Service,
never,
BackgroundJob.Service | RuntimeFlags.Service | Database.Service | EventV2Bridge.Service
Job.Service | RuntimeFlags.Service | Database.Service | EventV2Bridge.Service
> = Layer.effect(
Service,
Effect.gen(function* () {
const { db } = yield* Database.Service
const database = yield* Database.Service
const background = yield* BackgroundJob.Service
const jobs = yield* Job.Service
const events = yield* EventV2Bridge.Service
const flags = yield* RuntimeFlags.Service
@@ -618,7 +618,7 @@ export const layer: Layer.Layer<
Effect.catchCause(() => Effect.succeed(false)),
)
if (hasInstance) yield* cancelBackgroundJobs(background, sessionID)
if (hasInstance) yield* cancelJobs(jobs, sessionID)
const kids = yield* children(sessionID)
for (const child of kids) {
yield* remove(child.id)
@@ -941,7 +941,7 @@ export const layer: Layer.Layer<
)
export const defaultLayer = layer.pipe(
Layer.provide(BackgroundJob.defaultLayer),
Layer.provide(Job.defaultLayer),
Layer.provide(Database.defaultLayer),
Layer.provide(EventV2Bridge.defaultLayer),
Layer.provide(
@@ -953,19 +953,16 @@ export const defaultLayer = layer.pipe(
Layer.provide(RuntimeFlags.defaultLayer),
)
const cancelBackgroundJobs = Effect.fn("Session.cancelBackgroundJobs")(function* (
background: BackgroundJob.Interface,
sessionID: SessionID,
) {
const jobs = yield* background.list()
const cancelJobs = Effect.fn("Session.cancelJobs")(function* (jobs: Job.Interface, sessionID: SessionID) {
const running = yield* jobs.list()
yield* Effect.forEach(
jobs.filter((job) => {
running.filter((job) => {
if (job.status !== "running") return false
if (job.id === sessionID) return true
if (job.metadata?.sessionId === sessionID) return true
return job.metadata?.parentSessionId === sessionID
}),
(job) => background.cancel(job.id),
(job) => jobs.cancel(job.id),
{ concurrency: "unbounded", discard: true },
)
})
@@ -1098,7 +1095,7 @@ export function* listGlobal(input?: {
export const node = LayerNode.make({
service: Service,
layer: layer,
deps: [BackgroundJob.node, RuntimeFlags.node, Database.node, EventV2Bridge.node],
deps: [Job.node, RuntimeFlags.node, Database.node, EventV2Bridge.node],
})
export * as Session from "./session"