Просмотр исходного кода

fix(ssh): keep completion cleanup under release ownership

Tianyi Cui 3 недель назад
Родитель
Сommit
28c4daa66c

+ 1 - 2
packages/ssh/ssh/src/helper-processes.ts

@@ -337,7 +337,6 @@ export class RemoteProcesses {
     this.closing = true
     const releases = [...this.records.keys()].map(id => this.release(id))
     const outcomes = await Promise.allSettled([...releases, ...this.cleanups])
-    while (this.cleanups.size > 0) outcomes.push(...await Promise.allSettled([...this.cleanups]))
     const errors = [...new Set(outcomes.flatMap(result => result.status === 'rejected' ? [result.reason as unknown] : []))]
     if (errors.length > 0) throw new AggregateError(errors, 'SSH remote process cleanup failed')
   }
@@ -374,7 +373,7 @@ export class RemoteProcesses {
   }
 
   private async rememberCompleted(id: SshProcessId, record: ProcessRecord, result: Promise<Completion>): Promise<void> {
-    if (this.records.get(id) !== record) return
+    if (this.records.get(id) !== record || record.release !== undefined) return
     const cleanup = this.trackCleanup(async () => {
       await Promise.all(Object.values(record.endpoints).map(closeEndpoint))
       await rm(record.directory, { recursive: true, force: true })

+ 34 - 0
packages/ssh/ssh/tests/helper-finalization.spec.ts

@@ -122,4 +122,38 @@ describe.skipIf(process.platform === 'win32')('SSH helper finalization ownership
     await expect(test.owner.done(test.id)).rejects.toThrow('Unknown or expired SSH process handle')
     expect(await readdir(test.root)).toEqual([])
   })
+
+  it('leaves completion owned by a release that is still observing the native range', async () => {
+    const child = nativeProcess()
+    const firstEntered = Promise.withResolvers<undefined>()
+    const secondEntered = Promise.withResolvers<undefined>()
+    const first = Promise.withResolvers<undefined>()
+    const second = Promise.withResolvers<undefined>()
+    child.waitForExit.mockImplementationOnce(async () => {
+      firstEntered.resolve(undefined)
+      await first.promise
+      return true
+    }).mockImplementationOnce(async () => {
+      secondEntered.resolve(undefined)
+      await second.promise
+      return true
+    })
+    const test = await harness(child, false)
+    onTestFinished(() => { first.resolve(undefined); second.resolve(undefined) })
+    child.finish()
+    await firstEntered.promise
+    const closed = test.owner.close()
+    try {
+      await secondEntered.promise
+      first.resolve(undefined)
+      await setImmediate()
+      second.resolve(undefined)
+      await closed
+      await expect(test.owner.done(test.id)).rejects.toThrow('Unknown or expired SSH process handle')
+    } finally {
+      first.resolve(undefined)
+      second.resolve(undefined)
+      await closed
+    }
+  })
 })