enh: port pooling

This commit is contained in:
Ben Allfree 2023-06-21 07:50:13 -07:00
parent eb8bed9cea
commit 91cbc91c2a
2 changed files with 66 additions and 35 deletions

View File

@ -1,7 +1,6 @@
import { import {
DAEMON_PB_DATA_DIR, DAEMON_PB_DATA_DIR,
DAEMON_PB_IDLE_TTL, DAEMON_PB_IDLE_TTL,
DAEMON_PB_PORT_BASE,
PUBLIC_APP_DB, PUBLIC_APP_DB,
PUBLIC_APP_DOMAIN, PUBLIC_APP_DOMAIN,
PUBLIC_APP_PROTOCOL, PUBLIC_APP_PROTOCOL,
@ -22,16 +21,16 @@ import {
SingletonBaseConfig, SingletonBaseConfig,
StreamNames, StreamNames,
} from '@pockethost/common' } from '@pockethost/common'
import { map, remove, values } from '@s-libs/micro-dash' import { map, values } from '@s-libs/micro-dash'
import Bottleneck from 'bottleneck' import Bottleneck from 'bottleneck'
import { existsSync } from 'fs' import { existsSync } from 'fs'
import getPort from 'get-port'
import { join } from 'path' import { join } from 'path'
import { ClientResponseError } from 'pocketbase' import { ClientResponseError } from 'pocketbase'
import { AsyncReturnType } from 'type-fest' import { AsyncReturnType } from 'type-fest'
import { instanceLoggerService } from '../InstanceLoggerService' import { instanceLoggerService } from '../InstanceLoggerService'
import { pocketbase } from '../PocketBaseService' import { pocketbase } from '../PocketBaseService'
import { createDenoProcess } from './Deno/DenoProcess' import { createDenoProcess } from './Deno/DenoProcess'
import { portManager } from './PortManager'
enum InstanceApiStatus { enum InstanceApiStatus {
Starting = 'starting', Starting = 'starting',
@ -206,7 +205,7 @@ export const instanceService = mkSingleton(
healthyGuard() healthyGuard()
await updateInstanceStatus(instance.id, InstanceStatus.Port) await updateInstanceStatus(instance.id, InstanceStatus.Port)
healthyGuard() healthyGuard()
const [newPort, releasePort] = await getNextPort() const [newPort, releasePort] = getNextPort()
shutdownManager.add(() => { shutdownManager.add(() => {
dbg(`Releasing port`) dbg(`Releasing port`)
releasePort() releasePort()
@ -508,37 +507,7 @@ export const instanceService = mkSingleton(
`InstanceService` `InstanceService`
) )
const getNextPort = (() => { const { getNextPort } = await portManager({})
const { dbg } = instanceServiceLogger.create(`getNextPort`)
let exclude: number[] = []
return serialAsyncExecutionGuard(
async (): Promise<[number, () => void]> => {
dbg(`Getting free port`)
try {
const newPort = await getPort({
port: DAEMON_PB_PORT_BASE,
exclude,
})
exclude.push(newPort)
dbg(`Currently excluded ports: ${exclude.join(',')}`)
return [
newPort,
() => {
const removed = remove(exclude, (v) => v === newPort)
dbg(
`Removed ${removed.join(
','
)} from excluded ports: ${exclude.join(',')}`
)
},
]
} catch (e) {
throw new Error(`Failed to get free port with ${e}`)
}
}
)
})()
const shutdown = async () => { const shutdown = async () => {
dbg(`Shutting down instance manager`) dbg(`Shutting down instance manager`)

View File

@ -0,0 +1,62 @@
import { DAEMON_PB_PORT_BASE } from '$constants'
import { serialAsyncExecutionGuard } from '$src/util/serialAsyncExecutionGuard'
import { logger, mkSingleton } from '@pockethost/common'
import { range, remove } from '@s-libs/micro-dash'
import getPort from 'get-port'
export type PortResult = [number, () => void]
export const portManager = mkSingleton(async () => {
const _logger = logger().create(`PortManager`)
const { dbg, error } = _logger
const getNextPort = (() => {
const { dbg, error } = _logger.create(`getNextPort`)
let exclude: number[] = []
return serialAsyncExecutionGuard(async (): Promise<PortResult> => {
dbg(`Getting free port`)
try {
const newPort = await getPort({
port: DAEMON_PB_PORT_BASE,
exclude,
})
exclude.push(newPort)
dbg(`Currently excluded ports: ${exclude.join(',')}`)
return [
newPort,
() => {
const removed = remove(exclude, (v) => v === newPort)
dbg(
`Removed ${removed.join(',')} from excluded ports: ${exclude.join(
','
)}`
)
},
]
} catch (e) {
throw new Error(`Failed to get free port with ${e}`)
}
})
})()
const ports = await (
await Promise.all<PortResult>(range(500).map(getNextPort))
).map((portInfo) => portInfo[0])
return {
getNextPort: (): PortResult => {
const port = ports.pop()
if (!port) {
throw new Error(`Out of ports`)
}
return [
port,
() => {
ports.push(port)
},
]
},
shutdown: () => {},
}
})