reduce task leaking (#2868)

* reduce task leaking

* fix onLeaveContext
This commit is contained in:
Aiden McClelland
2025-04-16 11:00:46 -06:00
committed by GitHub
parent 03f8b73627
commit 89f3fdc05f
18 changed files with 159 additions and 98 deletions

View File

@@ -1,7 +1,8 @@
export abstract class Drop {
private static weak: { [id: number]: Drop } = {}
private static registry = new FinalizationRegistry((id: number) => {
Drop.weak[id].drop()
const weak = Drop.weak[id]
if (weak) weak.drop()
})
private static idCtr: number = 0
private id: number

View File

@@ -34,10 +34,15 @@ export class GetSslCertificate {
* Watches the SSL Certificate for the given hostnames if permitted. Returns an async iterator that yields whenever the value changes
*/
async *watch() {
while (true) {
let callback: () => void
const resolveCell = { resolve: () => {} }
this.effects.onLeaveContext(() => {
resolveCell.resolve()
})
while (this.effects.isInContext) {
let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => {
callback = resolve
resolveCell.resolve = resolve
})
yield await this.effects.getSslCertificate({
hostnames: this.hostnames,

View File

@@ -4,6 +4,7 @@ import * as cp from "child_process"
import { promisify } from "util"
import { Buffer } from "node:buffer"
import { once } from "../../../base/lib/util/once"
import { Drop } from "./Drop"
export const execFile = promisify(cp.execFile)
const False = () => false
@@ -45,16 +46,8 @@ export interface ExecSpawnable {
* Implements:
* @see {@link ExecSpawnable}
*/
export class SubContainer implements ExecSpawnable {
private static finalizationEffects: { effects?: T.Effects } = {}
private static registry = new FinalizationRegistry((guid: string) => {
if (this.finalizationEffects.effects) {
this.finalizationEffects.effects.subcontainer
.destroyFs({ guid })
.catch((e) => console.error("failed to cleanup SubContainer", guid, e))
}
})
export class SubContainer extends Drop implements ExecSpawnable {
private destroyed = false
private leader: cp.ChildProcess
private leaderExited: boolean = false
private waitProc: () => Promise<null>
@@ -64,8 +57,7 @@ export class SubContainer implements ExecSpawnable {
readonly rootfs: string,
readonly guid: T.Guid,
) {
if (!SubContainer.finalizationEffects.effects)
SubContainer.finalizationEffects.effects = effects
super()
this.leaderExited = false
this.leader = cp.spawn("start-cli", ["subcontainer", "launch", rootfs], {
killSignal: "SIGKILL",
@@ -106,7 +98,6 @@ export class SubContainer implements ExecSpawnable {
name,
})
const res = new SubContainer(effects, imageId, rootfs, guid)
SubContainer.registry.register(res, guid, res)
const shared = ["dev", "sys"]
if (!!sharedRun) {
@@ -212,14 +203,20 @@ export class SubContainer implements ExecSpawnable {
get destroy() {
return async () => {
const guid = this.guid
await this.killLeader()
await this.effects.subcontainer.destroyFs({ guid })
SubContainer.registry.unregister(this)
if (!this.destroyed) {
const guid = this.guid
await this.killLeader()
await this.effects.subcontainer.destroyFs({ guid })
this.destroyed = true
}
return null
}
}
onDrop(): void {
this.destroy()
}
async exec(
command: string[],
options?: CommandOptions & ExecOptions,

View File

@@ -152,7 +152,7 @@ export class FileHelper<A> {
}
private async readConst(effects: T.Effects): Promise<A | null> {
const watch = this.readWatch()
const watch = this.readWatch(effects)
const res = await watch.next()
if (effects.constRetry) {
if (!this.consts.includes(effects.constRetry))
@@ -165,9 +165,9 @@ export class FileHelper<A> {
return res.value
}
private async *readWatch() {
private async *readWatch(effects: T.Effects) {
let res
while (true) {
while (effects.isInContext) {
if (await exists(this.path)) {
const ctrl = new AbortController()
const watch = fs.watch(this.path, {
@@ -194,10 +194,11 @@ export class FileHelper<A> {
}
private readOnChange(
effects: T.Effects,
callback: (value: A | null, error?: Error) => void | Promise<void>,
) {
;(async () => {
for await (const value of this.readWatch()) {
for await (const value of this.readWatch(effects)) {
try {
await callback(value)
} catch (e) {
@@ -221,10 +222,11 @@ export class FileHelper<A> {
return {
once: () => this.readOnce(),
const: (effects: T.Effects) => this.readConst(effects),
watch: () => this.readWatch(),
watch: (effects: T.Effects) => this.readWatch(effects),
onChange: (
effects: T.Effects,
callback: (value: A | null, error?: Error) => void | Promise<void>,
) => this.readOnChange(callback),
) => this.readOnChange(effects, callback),
}
}