import { ChildProcess , spawn } from 'child_process' ; import * as events from 'events' ; import { inject , injectable } from 'inversify' ; import ProcessUtil from '../../../util/ProcessUtil' ; import IConfiguration from '../../IConfiguration' ; import ILogger from '../../ILogger' ; import ILoggerModel from '../../ILoggerModel' ; import IEncodeProcessManageModel , { CreateProcessOption } from './IEncodeProcessManageModel' ; interface ChildProcessInfo { child : ChildProcess ; priority : number ; processId : number ; } @ injectable ( ) class EncodeProcessManageModel implements IEncodeProcessManageModel { private log : ILogger ; private maxEncode : number ; private childs : ChildProcessInfo [ ] = [ ] ; private listener : events . EventEmitter = new events . EventEmitter ( ) ; constructor ( @ inject ( 'ILoggerModel' ) logeer , @ inject ( 'IConfiguration' ) configure ) { this . log = logeer . getLogger ( ) ; this . maxEncode = configure . getConfig ( ) . encodeProcessNum ; } param option return public create ( option ) { return new Promise < ChildProcess > ( async ( resolve , reject ) => { if ( this . childs . length >= this . maxEncode ) { for ( let i = 0 ; i < this . childs . length ; i ++ ) { if ( option . priority > this . childs [ i ] . priority ) { try { const child = await this . killAndCreateProcess ( this . childs [ i ] . processId , option ) ; resolve ( child ) ; } catch ( err ) { reject ( err ) ; } return ; } } reject ( new Error ( 'EncodeProcessManageModelCreateError' ) ) ; } else { try { const child = this . buildProcess ( option ) ; this . childs . unshift ( child ) ; resolve ( child . child ) ; this . log . encode . info ( `create new encode process: ${ child . processId }` ) ; } catch ( err ) { this . log . encode . error ( 'create encode process failed' ) ; this . log . encode . error ( err ) ; reject ( err ) ; } } } ) ; } param planToKillProcessId param option return private killAndCreateProcess ( planToKillProcessId , option ) { return new Promise < ChildProcess > ( async ( resolve , reject ) => { let timeoutId = null ; /**
             * プロセスを生成
             *  プロセスが殺されたら呼ばされる
             */ const createChild = ( killedProcessId ) => { if ( killedProcessId !== planToKillProcessId ) { return ; } this . removeListener ( createChild ) ; try { const child = this . buildProcess ( option ) ; this . childs . unshift ( child ) ; resolve ( child . child ) ; if ( timeoutId !== null ) { clearInterval ( timeoutId ) ; } this . log . encode . info ( `kill & create new encode process: ${ child . processId }` ) ; } catch ( err ) { this . log . encode . error ( 'kill & create new encode process failed' ) ; this . log . encode . error ( err ) ; reject ( err ) ; } } ; /**
             * timeout 設定
             * 時間内にプロセスが殺せなかったらエラー
             */ timeoutId = setTimeout ( ( ) => { this . removeListener ( createChild ) ; this . log . encode . error ( `EncodeProcessManageModel timeout error: ${ planToKillProcessId }` ) ; reject ( new Error ( 'EncodeProcessManageModelTimeoutError' ) ) ; } , 3 * 1000 ) ; this . addListener ( createChild ) ; try { await this . killChild ( planToKillProcessId ) ; } catch ( err ) { this . log . encode . error ( `kill process failed: ${ planToKillProcessId }` ) ; this . log . encode . error ( err ) ; this . removeListener ( createChild ) ; reject ( err ) ; } } ) ; } param callback private addListener ( callback ) { this . listener . on ( EncodeProcessManageModel . KILL_CHILD_EVENT , callback ) ; } param callback private removeListener ( callback ) { this . listener . removeListener ( EncodeProcessManageModel . KILL_CHILD_EVENT , callback ) ; } param processId param isRemoveOnly return private async killChild ( processId , isRemoveOnly = false ) { let isError = true ; for ( let i = 0 ; i < this . childs . length ; i ++ ) { if ( this . childs [ i ] . processId === processId ) { isError = false ; try { if ( isRemoveOnly === false ) { this . log . encode . info ( `kill child: ${ processId }` ) ; await ProcessUtil . kill ( this . childs [ i ] . child ) ; } } catch ( err ) { this . log . encode . error ( err ) ; } this . childs . splice ( i , 1 ) ; break ; } } if ( isError && isRemoveOnly === false ) { throw new Error ( 'EncodeProcessManageModelKillChildError' ) ; } } param option return private buildProcess ( option ) { let cmds ; try { cmds = ProcessUtil . parseCmdStr ( option . cmd ) ; } catch ( err ) { this . log . encode . error ( `build process error: ${ option . cmd }` ) ; throw err ; } for ( let i = 0 ; i < cmds . args . length ; i ++ ) { if ( option . input !== null ) { cmds . args [ i ] = cmds . args [ i ] . replace ( /%INPUT%/g , option . input ) ; } if ( option . output !== null ) { cmds . args [ i ] = cmds . args [ i ] . replace ( /%OUTPUT%/g , option . output ) ; } } const child = typeof option . spawnOption === 'undefined' ? spawn ( cmds . bin , cmds . args ) : spawn ( cmds . bin , cmds . args , option . spawnOption ) ; const processId = new Date ( ) . getTime ( ) ; child . on ( 'error' , async ( ) => { await this . killChild ( processId , true ) . catch ( err => { this . log . encode . error ( err ) ; } ) ; this . eventsNotify ( processId ) ; } ) ; child . on ( 'exit' , async ( ) => { await this . killChild ( processId , true ) . catch ( err => { this . log . encode . error ( err ) ; } ) ; this . eventsNotify ( processId ) ; } ) ; if ( child . stdout !== null ) { child . stdout . on ( 'data' , ( ) => { } ) ; } if ( child . stderr !== null ) { child . stderr . on ( 'data' , ( ) => { } ) ; } if ( ProcessUtil . isExited ( child ) === true ) { setTimeout ( async ( ) => { await this . killChild ( processId , true ) . catch ( err => { this . log . encode . error ( err ) ; } ) ; this . eventsNotify ( processId ) ; child . removeAllListeners ( ) ; } , 50 ) ; } return { child : child , priority : option . priority , processId : processId , } ; } param processId private eventsNotify ( processId ) { this . listener . emit ( EncodeProcessManageModel . KILL_CHILD_EVENT , processId ) ; } } namespace EncodeProcessManageModel { export const KILL_CHILD_EVENT = 'killChild' ; } export default EncodeProcessManageModel ;