Spaces:
Runtime error
Runtime error
| import { sleep, waitUntil } from '@waha/utils/promiseTimeout'; | |
| import { spawn } from 'child_process'; | |
| import { Logger } from 'pino'; | |
| export class GowsSubprocess { | |
| private checkIntervalMs: number = 100; | |
| private readyDelayMs: number = 1_000; | |
| private readyText = 'gRPC server started!'; | |
| private child: any; | |
| private ready: boolean = false; | |
| constructor( | |
| private logger: Logger, | |
| readonly path: string, | |
| readonly socket: string, | |
| readonly pprof: boolean = false, | |
| ) {} | |
| start(onExit: (code: number) => void) { | |
| this.logger.info('Starting GOWS subprocess...'); | |
| this.logger.debug(`GOWS path '${this.path}', socket: '${this.socket}'...`); | |
| const args = ['--socket', this.socket]; | |
| if (this.pprof) { | |
| this.logger.info('Debug mode enabled, adding pprof flags'); | |
| args.push('--pprof'); | |
| args.push('--pprof-port=6060'); | |
| args.push('--pprof-host=0.0.0.0'); | |
| } | |
| this.child = spawn(this.path, args, { | |
| detached: true, | |
| }); | |
| this.logger.debug(`GOWS started with PID: ${this.child.pid}`); | |
| this.child.on('close', async (code, singal) => { | |
| const msg = code | |
| ? `GOWS subprocess closed with code ${code}` | |
| : `GOWS subprocess closed by signal ${singal}`; | |
| this.logger.debug(msg); | |
| onExit(code); | |
| }); | |
| this.child.on('error', (err) => { | |
| this.logger.error(`GOWS subprocess error: ${err}`); | |
| }); | |
| this.child.stderr.setEncoding('utf8'); | |
| this.child.stderr.on('data', (data) => { | |
| this.logger.error(data); | |
| }); | |
| this.child.stdout.setEncoding('utf8'); | |
| this.child.stdout.on('data', async (data) => { | |
| // remove empty line at the end, split by \n | |
| const lines = data.trim().split('\n'); | |
| lines.forEach((line) => this.log(line)); | |
| }); | |
| this.listenReady(); | |
| } | |
| listenReady() { | |
| this.child.stdout.on('data', async (data) => { | |
| if (this.ready) { | |
| return; | |
| } | |
| if (!data.includes(this.readyText)) { | |
| return; | |
| } | |
| await sleep(this.readyDelayMs); | |
| this.ready = true; | |
| this.logger.info('GOWS is ready'); | |
| }); | |
| } | |
| async waitWhenReady(timeout: number) { | |
| const started = await waitUntil( | |
| async () => this.ready, | |
| this.checkIntervalMs, | |
| timeout, | |
| ); | |
| if (!started) { | |
| const msg = 'GOWS did not start after 10 seconds'; | |
| this.logger.error(msg); | |
| throw new Error(msg); | |
| } | |
| } | |
| async stop() { | |
| this.logger.info('Stopping GOWS subprocess...'); | |
| this.child?.kill('SIGTERM'); | |
| this.logger.info('GOWS subprocess stopped'); | |
| } | |
| private log(msg) { | |
| if (msg.startsWith('ERROR | ')) { | |
| this.logger.error(msg.slice(8)); | |
| } else if (msg.startsWith('WARN | ')) { | |
| this.logger.warn(msg.slice(7)); | |
| } else if (msg.startsWith('INFO | ')) { | |
| this.logger.info(msg.slice(7)); | |
| } else if (msg.startsWith('DEBUG | ')) { | |
| this.logger.debug(msg.slice(8)); | |
| } else if (msg.startsWith('TRACE | ')) { | |
| this.logger.trace(msg.slice(8)); | |
| } else { | |
| this.logger.info(msg); | |
| } | |
| } | |
| } | |