UNPKG

flexbiz-server

Version:

Flexible Server

20 lines (19 loc) 9.21 kB
const {Worker}=require("worker_threads"),fs=require("fs"),_=require("lodash"),crypto=require("crypto"),moment=require("moment"),redisCache=require("./redis-cache"),v8=require("v8"); class WorkerPool{constructor($filename_i$$,$maxQueue_worker$$=0,$maxWorkers$$=1,$defaultTimeout$$=3E5,$pre_created_workers$$=0,$poolName$$="WorkerPool"){if(!fs.existsSync($filename_i$$))throw Error("Worker file does not exist");this.filename=$filename_i$$;this.maxWorkers=$maxWorkers$$;(this.maxQueue=$maxQueue_worker$$)&&this.maxQueue<$maxWorkers$$&&(this.maxQueue=$maxWorkers$$);this.defaultTimeout=$defaultTimeout$$;this.pre_created_workers=$pre_created_workers$$;this.queue=[];this.queue_running=[]; this.workers={};this.poolName=$poolName$$;this.SCALE_THRESHOLD_LAG=40;this.SCALE_COOLDOWN=2E3;this.lastScaleTime=0;Logger.info(this.poolName,"\u0111ang t\u1ea1o s\u1eb5n",$pre_created_workers$$,"workers/",$maxWorkers$$);for($filename_i$$=0;$filename_i$$<Math.min($pre_created_workers$$,$maxWorkers$$);$filename_i$$++)$maxQueue_worker$$=this.createWorker(),this.workers[$maxQueue_worker$$.id]=$maxQueue_worker$$}checkLoadAndScale(){const $now$$=Date.now();if(!($now$$-this.lastScaleTime<this.SCALE_COOLDOWN)){var $avgLag_workersList$$= Object.values(this.workers),$hasBacklog_totalWorkers_wk$$=$avgLag_workersList$$.length;if(!($hasBacklog_totalWorkers_wk$$>=this.maxWorkers)&&$hasBacklog_totalWorkers_wk$$!==0){var $totalLag$$=0;$avgLag_workersList$$.forEach($w$$=>$totalLag$$+=$w$$.lag||0);$avgLag_workersList$$=$totalLag$$/$hasBacklog_totalWorkers_wk$$;$hasBacklog_totalWorkers_wk$$=this.queue.length>$hasBacklog_totalWorkers_wk$$;if($avgLag_workersList$$>this.SCALE_THRESHOLD_LAG||$hasBacklog_totalWorkers_wk$$&&$avgLag_workersList$$> 20)Logger.info(`[${this.poolName}] High Load detected (Avg Lag: ${$avgLag_workersList$$.toFixed(2)}ms, Queue: ${this.queue.length}). Scaling up...`),$hasBacklog_totalWorkers_wk$$=this.createWorker(),this.workers[$hasBacklog_totalWorkers_wk$$.id]=$hasBacklog_totalWorkers_wk$$,this.lastScaleTime=$now$$}}}workerIsAlive($wk$$){return!$wk$$.worker||$wk$$.worker.threadId<=0||moment().diff(moment($wk$$.time_responded),"seconds")>(($wk$$.task||{}).timeout||1E4)/1E3?!1:!0}createWorker(){const $wk$$={id:`worker-${crypto.randomBytes(20).toString("hex")}`, isBusy:!1,time_responded:new Date,lag:0};$wk$$.worker=new Worker(this.filename);$wk$$.worker.on("message",async $rawMessage$$=>{$wk$$.time_responded=new Date;let $message$$=$rawMessage$$;if(Buffer.isBuffer($rawMessage$$)||$rawMessage$$ instanceof Uint8Array)try{$message$$=v8.deserialize($rawMessage$$)}catch($e$$){Logger.error(`[${this.poolName}] L\u1ed7i deserialize message t\u1eeb worker:`,$e$$)}if($message$$&&$message$$.type==="metrics")$wk$$.lag=$message$$.lag,this.checkLoadAndScale();else{var $task$$; try{($task$$=$message$$.id_task?this.queue_running.find($q$$=>$q$$.id==$message$$.id_task):$wk$$.task)&&clearTimeout($task$$.timeoutId);if($task$$&&$task$$.callback)if($message$$.id_task&&$message$$.id_task!==$task$$.id)Logger.error("[",this.poolName,"] K\u1ebft qu\u1ea3 tr\u1ea3 v\u1ec1 kh\u00f4ng \u0111\u00fang cho task",$task$$.id,$message$$);else{$task$$.finished=new Date;$task$$.status=5;$task$$.message="\u0110\u00e3 th\u1ef1c hi\u1ec7n xong";$task$$.result=$message$$&&Array.isArray($message$$)? void 0:$message$$;if($task$$.save_log)try{let {finished:$finished$$,status:$status$$,message:$message$$,result:$result$$}=$task$$;$task$$.save_log&&await redisCache.updateObject($task$$.id,{finished:$finished$$,status:$status$$,message:$message$$,result:$result$$})}catch($e$$){Logger.error("[",this.poolName,"] can't update task status",$task$$.id,$e$$.message)}this.queue_running=this.queue_running.filter($q$$=>$q$$.id!==$task$$.id);$task$$.callback($message$$,$task$$.id)}delete $wk$$.task;$wk$$.isBusy= !1;this.processQueue()}catch($e$$){Logger.error("[",this.poolName,"]",$e$$);try{delete $wk$$.task,$wk$$.isBusy=!1,this.processQueue()}catch($e$$){Logger.error("[",this.poolName,"] cancel current task",$e$$)}}}});$wk$$.worker.on("error",$error$$=>{Logger.error("Worker error:",$error$$);$wk$$.worker.terminate()});$wk$$.worker.on("exit",async $code$$=>{Logger.error("[",this.poolName,"]",`: worker exited with code ${$code$$}. Creating new worker...`);if($wk$$.task){let $task$$=this.queue_running.find($q$$=> $q$$.id==$wk$$.task.id);if($task$$){clearTimeout($task$$.timeoutId);$task$$.finished=new Date;$task$$.status=9;$task$$.message="\u0110\u00e3 c\u00f3 l\u1ed7i x\u1ea3y ra trong h\u1ec7 th\u1ed1ng. H\u00e3y th\u1eed l\u1ea1i";let {finished:$finished$$,status:$status$$,message:$message$$,result:$result$$}=$task$$;if($task$$.save_log)try{$task$$.save_log&&await redisCache.updateObject($task$$.id,{finished:$finished$$,status:$status$$,message:$message$$,result:$result$$})}catch($e$$){Logger.error("[", this.poolName,"] can't update task status",$task$$.id,$e$$.message)}$task$$.callback&&$task$$.callback({error:$message$$},$task$$.id);this.queue_running=this.queue_running.filter($q$$=>$q$$.id!==$task$$.id)}}delete this.workers[$wk$$.id];$code$$=this.createWorker();this.workers[$code$$.id]=$code$$});return $wk$$}async processQueue($id_worker$$){for(let $i$$=0;$i$$<this.maxWorkers&&this.queue.length!=0;$i$$++){let $wk$$=Object.values(this.workers).find($w$$=>!$w$$.isBusy&&!$w$$.task&&(!$id_worker$$|| $id_worker$$==$w$$.id));if(!$wk$$)if(Object.keys(this.workers).length===0)$wk$$=this.createWorker(),this.workers[$wk$$.id]=$wk$$;else{this.checkLoadAndScale();break}if($wk$$){this.workerIsAlive($wk$$)||(Logger.error("[",this.poolName,"]: Worker",$wk$$.id,"kh\u00f4ng c\u00f2n ho\u1ea1t \u0111\u1ed9ng. T\u1ea1o l\u1ea1i..."),delete this.workers[$wk$$.id],$wk$$=this.createWorker(),this.workers[$wk$$.id]=$wk$$);const $task$$=this.queue.shift();if($task$$){$task$$.status=2;$task$$.message="\u0110ang th\u1ef1c hi\u1ec7n"; this.queue_running.push($task$$);if($task$$.save_log)try{let {finished:$finished$$,status:$status$$,message:$message$$,result:$result$$}=$task$$;$task$$.save_log&&await redisCache.updateObject($task$$.id,{finished:$finished$$,status:$status$$,message:$message$$,result:$result$$})}catch($e$$){Logger.error("[",this.poolName,"] can't update task status",$task$$.id,$e$$.message)}$wk$$.isBusy=!0;$wk$$.task=$task$$;$task$$.params.id_worker=$wk$$.id;try{const $bufferParams$$=v8.serialize($task$$.params); $wk$$.worker.postMessage($bufferParams$$,[$bufferParams$$.buffer])}catch($err$$){Logger.error(`[${this.poolName}] L\u1ed7i serialize task params. Fallback sang g\u1eedi th\u01b0\u1eddng.`,$err$$),$wk$$.worker.postMessage($task$$.params)}$task$$.timeoutId=setTimeout(async()=>{this.queue_running=this.queue_running.filter($q$$=>$q$$.id!==$task$$.id);$task$$.finished=new Date;$task$$.status=9;$task$$.message=`\u0110\u00e3 h\u1ebft th\u1eddi gian th\u1ef1c hi\u1ec7n (Time out: ${$task$$.timeout} ms)`; let {finished:$finished$$,status:$status$$,message:$message$$,result:$result$$}=$task$$;if($task$$.save_log)try{$task$$.save_log&&await redisCache.updateObject($task$$.id,{finished:$finished$$,status:$status$$,message:$message$$,result:$result$$})}catch($e$$){Logger.error("[",this.poolName,"] can't update task status",$task$$.id,$e$$.message)}$task$$.callback&&$task$$.callback({error:$message$$},$task$$.id);delete $wk$$.task;$wk$$.isBusy=!1;this.processQueue()},$task$$.timeout||this.defaultTimeout)}}}}async pushToQueue($params$$, $callback$$,$timeout$$=0,$id_worker$$){let $save_log$$=!0;$params$$.id_task||($params$$.id_task=`task-${crypto.randomBytes(20).toString("hex")}`,$save_log$$=!1);$timeout$$=$timeout$$||this.defaultTimeout;const $task$$={id:$params$$.id_task,start:new Date,params:_.cloneDeep($params$$),timeout:$timeout$$,status:0,save_log:$save_log$$};delete $task$$.params.configs;delete $task$$.params.user;delete $task$$.params.data;$task$$.params.req&&(delete $task$$.params.req.user,delete $task$$.params.req.body, delete ($task$$.params.req.query||{}).access_token);try{try{$save_log$$&&await redisCache.setObject($params$$.id_task,$task$$)}catch($e$$){Logger.error("[",this.poolName,"] Kh\u00f4ng th\u1ec3 l\u01b0u task t\u1edbi redis:",$e$$.message)}this.queue.push({id:$params$$.id_task,params:$params$$,callback:$callback$$,timeout:$timeout$$,status:0,save_log:$save_log$$});this.processQueue($id_worker$$)}catch($e$$){Logger.error("[",this.poolName,"] : l\u1ed7i \u0111\u1ea9y task t\u1edbi queue",$e$$),$callback$$({error:$e$$.error|| $e$$.message||$e$$})}}async exec($args_params$$,$callback$$=()=>{},$timeout$$=0){if(this.maxQueue>0&&this.queue.length>=this.maxQueue)Logger.error("[",this.poolName,"]",": queue is full"),$callback$$({error:"Queue is full"});else if($args_params$$.load)for(let $id_worker$$ of Object.keys(this.workers)){let $params$$=_.cloneDeep($args_params$$);this.pushToQueue($params$$,$callback$$,$timeout$$,$id_worker$$)}else $args_params$$=_.cloneDeep($args_params$$),this.pushToQueue($args_params$$,$callback$$, $timeout$$)}fullQueue(){return this.maxQueue>0&&this.queue.length>=this.maxQueue}busy(){return Object.values(this.workers).filter($w$$=>!$w$$.isBusy).length==0}destroy(){for(const $worker$$ of Object.values(this.workers))$worker$$.terminate();this.workers={};this.queue=[]}}module.exports=WorkerPool;