mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
add process handler for starting and managing GradidoNode as subprocess
This commit is contained in:
parent
b9d51269ca
commit
f962baf1a1
@ -1,4 +1,5 @@
|
||||
import { Logger } from 'log4js'
|
||||
import { GradidoNodeProcess } from '../client/GradidoNode/GradidoNodeProcess'
|
||||
import { type AppContextClients } from './appContext'
|
||||
|
||||
export function setupGracefulShutdown(logger: Logger, clients: AppContextClients) {
|
||||
@ -25,4 +26,5 @@ export function setupGracefulShutdown(logger: Logger, clients: AppContextClients
|
||||
async function gracefulShutdown(logger: Logger, clients: AppContextClients) {
|
||||
logger.info('graceful shutdown')
|
||||
await clients.hiero.waitForPendingPromises()
|
||||
await GradidoNodeProcess.getInstance().exit()
|
||||
}
|
||||
|
||||
117
dlt-connector/src/client/GradidoNode/GradidoNodeProcess.ts
Normal file
117
dlt-connector/src/client/GradidoNode/GradidoNodeProcess.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { Subprocess, spawn } from 'bun'
|
||||
import { getLogger, Logger } from 'log4js'
|
||||
import { CONFIG } from '../../config'
|
||||
import {
|
||||
GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS,
|
||||
GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS,
|
||||
GRADIDO_NODE_RUNTIME_PATH,
|
||||
LOG4JS_BASE_CATEGORY,
|
||||
} from '../../config/const'
|
||||
|
||||
/**
|
||||
* A Singleton class defines the `getInstance` method that lets clients access
|
||||
* the unique singleton instance.
|
||||
*
|
||||
* Singleton Managing GradidoNode if started as subprocess
|
||||
* will restart GradidoNode if it exits more than `GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS` milliseconds after start
|
||||
* if exit was called, it will first try to exit graceful with SIGTERM and then kill with SIGKILL after `GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS` milliseconds
|
||||
*/
|
||||
export class GradidoNodeProcess {
|
||||
private static instance: GradidoNodeProcess | null = null
|
||||
private proc: Subprocess | null = null
|
||||
private logger: Logger
|
||||
private lastStarted: Date | null = null
|
||||
private exitCalled: boolean = false
|
||||
|
||||
private constructor() {
|
||||
// constructor is private to prevent instantiation from outside
|
||||
// of the class except from the static getInstance method.
|
||||
this.logger = getLogger(`${LOG4JS_BASE_CATEGORY}.client.GradidoNodeProcess`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Static method that returns the singleton instance of the class.
|
||||
* @returns the singleton instance of the class.
|
||||
*/
|
||||
public static getInstance(): GradidoNodeProcess {
|
||||
if (!GradidoNodeProcess.instance) {
|
||||
GradidoNodeProcess.instance = new GradidoNodeProcess()
|
||||
}
|
||||
return GradidoNodeProcess.instance
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (this.proc) {
|
||||
this.logger.warn('GradidoNodeProcess already running.')
|
||||
return
|
||||
}
|
||||
this.logger.info(`starting GradidoNodeProcess with path: ${GRADIDO_NODE_RUNTIME_PATH}`)
|
||||
this.lastStarted = new Date()
|
||||
const logger = this.logger
|
||||
this.proc = spawn([GRADIDO_NODE_RUNTIME_PATH], {
|
||||
env: {
|
||||
CLIENTS_HIERO_NETWORKTYPE: CONFIG.HIERO_HEDERA_NETWORK,
|
||||
SERVER_JSON_RPC_PORT: CONFIG.DLT_NODE_SERVER_PORT.toString(),
|
||||
},
|
||||
onExit(proc, exitCode, signalCode, error) {
|
||||
logger.warn(`GradidoNodeProcess exited with code ${exitCode} and signalCode ${signalCode}`)
|
||||
if (error) {
|
||||
logger.error(`GradidoNodeProcess exit error: ${error}`)
|
||||
if (logger.isDebugEnabled() && proc.stderr) {
|
||||
// print error messages from GradidoNode in our own log if debug is enabled
|
||||
proc.stderr
|
||||
.getReader()
|
||||
.read()
|
||||
.then((chunk) => {
|
||||
logger.debug(chunk.value?.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
logger.debug(`ressource usage: ${proc?.resourceUsage()}`)
|
||||
const gradidoNodeProcess = GradidoNodeProcess.getInstance()
|
||||
gradidoNodeProcess.proc = null
|
||||
if (
|
||||
!gradidoNodeProcess.exitCalled &&
|
||||
gradidoNodeProcess.lastStarted &&
|
||||
Date.now() - gradidoNodeProcess.lastStarted.getTime() >
|
||||
GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS
|
||||
) {
|
||||
// restart only if enough time was passed since last start to prevent restart loop
|
||||
gradidoNodeProcess.start()
|
||||
}
|
||||
},
|
||||
stdout: 'ignore',
|
||||
stderr: logger.isDebugEnabled() ? 'pipe' : 'ignore',
|
||||
})
|
||||
}
|
||||
|
||||
public async restart() {
|
||||
if (this.proc) {
|
||||
await this.exit()
|
||||
this.exitCalled = false
|
||||
this.start()
|
||||
}
|
||||
}
|
||||
|
||||
public async exit(): Promise<void> {
|
||||
this.exitCalled = true
|
||||
if (this.proc) {
|
||||
this.proc.kill('SIGTERM')
|
||||
const timeout = setTimeout(() => {
|
||||
this.logger.warn(
|
||||
`GradidoNode couldn't exit graceful after ${GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS} milliseconds with SIGTERM, killing with SIGKILL`,
|
||||
)
|
||||
this.proc?.kill('SIGKILL')
|
||||
}, GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS)
|
||||
try {
|
||||
await this.proc.exited
|
||||
} catch (error) {
|
||||
this.logger.error(`GradidoNodeProcess exit error: ${error}`)
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,12 @@
|
||||
import path from 'node:path'
|
||||
|
||||
export const LOG4JS_BASE_CATEGORY = 'dlt'
|
||||
// 7 days
|
||||
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_UPDATE = 1000 * 60 * 60 * 24 * 7
|
||||
// 10 minutes
|
||||
export const MIN_TOPIC_EXPIRE_MILLISECONDS_FOR_SEND_MESSAGE = 1000 * 60 * 10
|
||||
|
||||
export const GRADIDO_NODE_RUNTIME_PATH = path.join(__dirname, 'gradido_node', 'bin', 'GradidoNode')
|
||||
// if last start was less than this time, do not restart
|
||||
export const GRADIDO_NODE_MIN_RUNTIME_BEFORE_RESTART_MILLISECONDS = 1000 * 30
|
||||
export const GRADIDO_NODE_KILL_TIMEOUT_MILLISECONDS = 1000
|
||||
|
||||
@ -78,6 +78,7 @@ export const configSchema = v.object({
|
||||
),
|
||||
'8340',
|
||||
),
|
||||
DLT_NODE_SERVER_VERSION: v.optional(v.string('The version of the DLT node server'), '0.9.0'),
|
||||
PORT: v.optional(
|
||||
v.pipe(
|
||||
v.string('A valid port on which the backend server is running'),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user