t
This commit is contained in:
parent
4c4e767ec6
commit
9d70afb4cc
42 changed files with 1266 additions and 630 deletions
70
scripts/service-runner.js
Normal file
70
scripts/service-runner.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
'use strict';
|
||||
|
||||
const fs=require('fs');
|
||||
const path=require('path');
|
||||
const {spawn}=require('child_process');
|
||||
|
||||
const ROOT=path.resolve(__dirname,'..');
|
||||
const SERVER_FILE=path.join(ROOT,'server.js');
|
||||
const IDENTITY_FILE=path.resolve(process.env.LINK_FIELD_SERVICE_IDENTITY_FILE||path.join(process.env.LINK_FIELD_SERVICE_DIR||ROOT,'service.identity.json'));
|
||||
const NONCE=String(process.env.LINK_FIELD_SERVICE_NONCE||'');
|
||||
const LOG_FILE=String(process.env.LINK_FIELD_LOG_FILE||'');
|
||||
const PORT_FILE=path.join(String(process.env.LINK_FIELD_PUBLIC_DIR||ROOT),'.linkfield-port');
|
||||
const RESTART_WINDOW_MS=5*60*1000;
|
||||
const MAX_RESTARTS=10;
|
||||
const MAX_LOG_BYTES=Math.max(4096,Number(process.env.LINK_FIELD_LOG_MAX_BYTES)||10*1024*1024);
|
||||
const LOG_ROTATE_INTERVAL_MS=Math.max(100,Number(process.env.LINK_FIELD_LOG_ROTATE_INTERVAL_MS)||30_000);
|
||||
const LOG_GENERATIONS=5;
|
||||
|
||||
let child=null,stopping=false,restarts=[];
|
||||
|
||||
function writeIdentity(){
|
||||
const value={pid:process.pid,root:ROOT,nonce:NONCE,heartbeatAt:Date.now(),childPid:child?.pid||null};
|
||||
const temporary=`${IDENTITY_FILE}.${process.pid}.tmp`;
|
||||
fs.mkdirSync(path.dirname(IDENTITY_FILE),{recursive:true,mode:0o700});
|
||||
fs.writeFileSync(temporary,`${JSON.stringify(value)}\n`,{encoding:'utf8',mode:0o600});
|
||||
fs.renameSync(temporary,IDENTITY_FILE);
|
||||
}
|
||||
|
||||
function removeIdentity(){
|
||||
try{const value=JSON.parse(fs.readFileSync(IDENTITY_FILE,'utf8'));if(value?.pid===process.pid&&value?.nonce===NONCE)fs.unlinkSync(IDENTITY_FILE)}catch(error){if(error?.code!=='ENOENT')console.warn(`Service identity cleanup warning: ${error.message}`)}
|
||||
try{fs.unlinkSync(PORT_FILE)}catch(error){if(error?.code!=='ENOENT')console.warn(`Bridge-port cleanup warning: ${error.message}`)}
|
||||
}
|
||||
|
||||
function rotateLiveLog(){
|
||||
if(!LOG_FILE)return;
|
||||
let stat;try{stat=fs.statSync(LOG_FILE)}catch(error){if(error?.code==='ENOENT')return;throw error}
|
||||
if(stat.size<MAX_LOG_BYTES)return;
|
||||
try{fs.unlinkSync(`${LOG_FILE}.${LOG_GENERATIONS}`)}catch(error){if(error?.code!=='ENOENT')throw error}
|
||||
for(let index=LOG_GENERATIONS-1;index>=1;index--)try{fs.renameSync(`${LOG_FILE}.${index}`,`${LOG_FILE}.${index+1}`)}catch(error){if(error?.code!=='ENOENT')throw error}
|
||||
fs.copyFileSync(LOG_FILE,`${LOG_FILE}.1`);fs.truncateSync(LOG_FILE,0);
|
||||
}
|
||||
|
||||
function startChild(){
|
||||
if(stopping)return;
|
||||
const now=Date.now();restarts=restarts.filter(value=>now-value<RESTART_WINDOW_MS);
|
||||
if(restarts.length>=MAX_RESTARTS){console.error(`LinkField stopped after ${MAX_RESTARTS} crashes in five minutes.`);removeIdentity();process.exit(1);return}
|
||||
restarts.push(now);
|
||||
child=spawn(process.execPath,[SERVER_FILE],{cwd:ROOT,stdio:'inherit',env:{...process.env,LINK_FIELD_SUPERVISED:'1'}});
|
||||
writeIdentity();
|
||||
child.once('exit',(code,signal)=>{
|
||||
const lifetime=Date.now()-now;child=null;writeIdentity();
|
||||
if(stopping){removeIdentity();process.exit(0);return}
|
||||
console.error(`LinkField server exited (${signal||code}); restarting.`);
|
||||
if(lifetime>60_000)restarts=[];
|
||||
setTimeout(startChild,Math.min(10_000,500*Math.max(1,restarts.length)));
|
||||
});
|
||||
}
|
||||
|
||||
function shutdown(signal){
|
||||
if(stopping)return;stopping=true;console.log(`LinkField supervisor received ${signal}; stopping.`);
|
||||
if(child&&child.exitCode==null){child.kill('SIGTERM');const timer=setTimeout(()=>{if(child&&child.exitCode==null)child.kill('SIGKILL')},5000);timer.unref?.()}
|
||||
else{removeIdentity();process.exit(0)}
|
||||
}
|
||||
|
||||
process.once('SIGTERM',()=>shutdown('SIGTERM'));
|
||||
process.once('SIGINT',()=>shutdown('SIGINT'));
|
||||
process.once('exit',removeIdentity);
|
||||
const heartbeat=setInterval(()=>{try{writeIdentity()}catch(error){console.error(`Service heartbeat failed: ${error.message}`)}},1000);heartbeat.unref?.();
|
||||
const logRotation=setInterval(()=>{try{rotateLiveLog()}catch(error){console.error(`Live log rotation failed: ${error.message}`)}},LOG_ROTATE_INTERVAL_MS);logRotation.unref?.();
|
||||
startChild();
|
||||
Loading…
Add table
Add a link
Reference in a new issue