fix: release idle remote terminal multiplexers (#4101)
This commit is contained in:
parent
4615f2e169
commit
4b1f7b33ac
|
|
@ -81,7 +81,13 @@ class RemoteRuntimeTerminalMultiplexer {
|
|||
private ready = false
|
||||
private nextStreamId = 1
|
||||
|
||||
constructor(private readonly environmentId: string) {}
|
||||
constructor(
|
||||
private readonly environmentId: string,
|
||||
private readonly releaseIfCurrent: (
|
||||
environmentId: string,
|
||||
multiplexer: RemoteRuntimeTerminalMultiplexer
|
||||
) => void
|
||||
) {}
|
||||
|
||||
async subscribeTerminal(args: {
|
||||
terminal: string
|
||||
|
|
@ -347,6 +353,10 @@ class RemoteRuntimeTerminalMultiplexer {
|
|||
stream.callbacks.onError?.(message)
|
||||
}
|
||||
}
|
||||
// Why: a closed transport has no live streams or subscription; keeping it
|
||||
// in the module map only retains callbacks for an environment that must
|
||||
// reconnect through a fresh subscription anyway.
|
||||
this.releaseIfCurrent(this.environmentId, this)
|
||||
}
|
||||
|
||||
private closeIfIdle(): void {
|
||||
|
|
@ -357,22 +367,39 @@ class RemoteRuntimeTerminalMultiplexer {
|
|||
this.subscription = null
|
||||
this.connectPromise = null
|
||||
this.ready = false
|
||||
this.releaseIfCurrent(this.environmentId, this)
|
||||
}
|
||||
}
|
||||
|
||||
const multiplexers = new Map<string, RemoteRuntimeTerminalMultiplexer>()
|
||||
|
||||
function releaseRemoteRuntimeTerminalMultiplexer(
|
||||
environmentId: string,
|
||||
multiplexer: RemoteRuntimeTerminalMultiplexer
|
||||
): void {
|
||||
if (multiplexers.get(environmentId) === multiplexer) {
|
||||
multiplexers.delete(environmentId)
|
||||
}
|
||||
}
|
||||
|
||||
export function getRemoteRuntimeTerminalMultiplexer(
|
||||
environmentId: string
|
||||
): RemoteRuntimeTerminalMultiplexer {
|
||||
let multiplexer = multiplexers.get(environmentId)
|
||||
if (!multiplexer) {
|
||||
multiplexer = new RemoteRuntimeTerminalMultiplexer(environmentId)
|
||||
multiplexer = new RemoteRuntimeTerminalMultiplexer(
|
||||
environmentId,
|
||||
releaseRemoteRuntimeTerminalMultiplexer
|
||||
)
|
||||
multiplexers.set(environmentId, multiplexer)
|
||||
}
|
||||
return multiplexer
|
||||
}
|
||||
|
||||
export function _getRemoteRuntimeTerminalMultiplexerCountForTest(): number {
|
||||
return multiplexers.size
|
||||
}
|
||||
|
||||
export function resetRemoteRuntimeTerminalMultiplexersForTests(): void {
|
||||
multiplexers.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import {
|
|||
encodeTerminalStreamFrame,
|
||||
encodeTerminalStreamText
|
||||
} from '../../../shared/terminal-stream-protocol'
|
||||
import { resetRemoteRuntimeTerminalMultiplexersForTests } from './remote-runtime-terminal-multiplexer'
|
||||
import {
|
||||
_getRemoteRuntimeTerminalMultiplexerCountForTest,
|
||||
resetRemoteRuntimeTerminalMultiplexersForTests
|
||||
} from './remote-runtime-terminal-multiplexer'
|
||||
import {
|
||||
getRemoteRuntimePtyEnvironmentId,
|
||||
getRemoteRuntimeTerminalHandle,
|
||||
|
|
@ -106,8 +109,36 @@ describe('remote runtime terminal data subscriptions', () => {
|
|||
)
|
||||
|
||||
expect(watcher).toHaveBeenCalledWith('live')
|
||||
expect(_getRemoteRuntimeTerminalMultiplexerCountForTest()).toBe(1)
|
||||
dispose()
|
||||
expect(unsubscribe).toHaveBeenCalled()
|
||||
expect(_getRemoteRuntimeTerminalMultiplexerCountForTest()).toBe(0)
|
||||
})
|
||||
|
||||
it('keeps the shared terminal multiplexer until the last watcher closes', async () => {
|
||||
const firstDispose = await subscribeToRuntimeTerminalData(
|
||||
{ activeRuntimeEnvironmentId: 'env-fallback' },
|
||||
'remote:env-1@@terminal-1',
|
||||
'watcher-1',
|
||||
vi.fn()
|
||||
)
|
||||
const secondDispose = await subscribeToRuntimeTerminalData(
|
||||
{ activeRuntimeEnvironmentId: 'env-fallback' },
|
||||
'remote:env-1@@terminal-2',
|
||||
'watcher-2',
|
||||
vi.fn()
|
||||
)
|
||||
|
||||
expect(runtimeSubscribe).toHaveBeenCalledTimes(1)
|
||||
expect(_getRemoteRuntimeTerminalMultiplexerCountForTest()).toBe(1)
|
||||
|
||||
firstDispose()
|
||||
expect(unsubscribe).not.toHaveBeenCalled()
|
||||
expect(_getRemoteRuntimeTerminalMultiplexerCountForTest()).toBe(1)
|
||||
|
||||
secondDispose()
|
||||
expect(unsubscribe).toHaveBeenCalledOnce()
|
||||
expect(_getRemoteRuntimeTerminalMultiplexerCountForTest()).toBe(0)
|
||||
})
|
||||
|
||||
it('rejects remote terminal subscriptions when the multiplex connection fails', async () => {
|
||||
|
|
@ -123,5 +154,6 @@ describe('remote runtime terminal data subscriptions', () => {
|
|||
).rejects.toThrow('offline')
|
||||
|
||||
expect(sendBinary).not.toHaveBeenCalled()
|
||||
expect(_getRemoteRuntimeTerminalMultiplexerCountForTest()).toBe(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue