mirror of
https://github.com/pockethost/pockethost.git
synced 2025-06-02 12:16:41 +00:00
enh: port pooling
This commit is contained in:
parent
eb8bed9cea
commit
91cbc91c2a
@ -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`)
|
||||||
|
62
packages/daemon/src/services/InstanceService/PortManager.ts
Normal file
62
packages/daemon/src/services/InstanceService/PortManager.ts
Normal 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: () => {},
|
||||||
|
}
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user