Mon 21 Jul 22:43:21 CEST 2025
This commit is contained in:
parent
e336137859
commit
55d9472986
499
js/dos/appl/boot.js
Normal file
499
js/dos/appl/boot.js
Normal file
|
@ -0,0 +1,499 @@
|
|||
/**
|
||||
** ==============================
|
||||
** O O O OOOO
|
||||
** O O O O O O
|
||||
** O O O O O O
|
||||
** OOOO OOOO O OOO OOOO
|
||||
** O O O O O O O
|
||||
** O O O O O O O
|
||||
** OOOO OOOO O O OOOO
|
||||
** ==============================
|
||||
** Dr. Stefan Bosse http://www.bsslab.de
|
||||
**
|
||||
** COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
|
||||
** BY THE AUTHOR(S).
|
||||
** THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
|
||||
** MODIFIED, OR OTHERWISE USED IN A CONTEXT
|
||||
** OUTSIDE OF THE SOFTWARE SYSTEM.
|
||||
**
|
||||
** $AUTHORS: Stefan Bosse
|
||||
** $INITIAL: (C) 2006-2016 bLAB
|
||||
** $CREATED: sbosse on 23-5-16.
|
||||
** $VERSION: 1.3.5
|
||||
**
|
||||
** $INFO:
|
||||
*
|
||||
* Boot Service that is a monolithic application embeddeding all serviced programs.
|
||||
*
|
||||
** $ENDOFINFO
|
||||
*/
|
||||
var Io = Require('com/io');
|
||||
var Comp = Require('com/compat');
|
||||
var Sch = Require('dos/scheduler');
|
||||
var Net = Require('dos/network');
|
||||
var Conn = Require('dos/connection');
|
||||
|
||||
var My = {domain:'default',host:'none'};
|
||||
|
||||
var Operation = {
|
||||
CLEAR:'clear',
|
||||
DISABLE:'disable',
|
||||
ENABLE:'enable',
|
||||
EXECUTE:'exec',
|
||||
INFO:'info',
|
||||
INIT:'init',
|
||||
MAKEAPP:'makeapp',
|
||||
REFRESH:'refresh',
|
||||
START:'start',
|
||||
STOP:'stop'
|
||||
}
|
||||
|
||||
var State = {
|
||||
OFFLINE:'offline',
|
||||
ONLINE:'online',
|
||||
UNINITIALIZED:'uninitialized',
|
||||
MAINTENANCE:'maintenance',
|
||||
DISABLED:'disabled',
|
||||
TEST:'test',
|
||||
WAIT:'wait'
|
||||
}
|
||||
|
||||
var options = {
|
||||
app : '*',
|
||||
args : [],
|
||||
bip:'localhost',
|
||||
boot_ops : [],
|
||||
bootst : 'boot.state',
|
||||
bootsf : 'boot.conf',
|
||||
bport:3001,
|
||||
broker: true,
|
||||
dip : 'localhost',
|
||||
http:false,
|
||||
keepalive:true,
|
||||
monitor:0,
|
||||
prog : process.argv[1]||'?',
|
||||
tcpnet:1,
|
||||
verbose:0,
|
||||
vm : process.argv[0]||'node'
|
||||
}
|
||||
|
||||
Comp.args.parse(process.argv,[
|
||||
[[
|
||||
Operation.CLEAR,
|
||||
Operation.DISABLE,
|
||||
Operation.ENABLE,
|
||||
Operation.INIT,
|
||||
Operation.INFO,
|
||||
Operation.MAKEAPP,
|
||||
Operation.REFRESH,
|
||||
Operation.START,
|
||||
Operation.STOP
|
||||
],
|
||||
0,function (val) {
|
||||
options.boot_ops.push(val)
|
||||
}],
|
||||
[Operation.EXECUTE,'*',function (vals) {
|
||||
options.boot_ops.push(Operation.EXECUTE);
|
||||
options.app=Comp.array.head(vals);
|
||||
options.args=Comp.array.tail(vals);
|
||||
}],
|
||||
['-conf',1,function (val) {options.bootsf=val}],
|
||||
['-state',1,function (val) {options.bootst=val}],
|
||||
['-broker',1,function(val){
|
||||
var tokens = Comp.string.split(':',val);
|
||||
if (tokens.length==1)
|
||||
options.bip=val;
|
||||
else {
|
||||
options.bip=tokens[0];
|
||||
options.bport=Perv.int_of_string(tokens[1])
|
||||
}
|
||||
}],
|
||||
['-dip',1,function(val){options.dip=val}],
|
||||
['-D',1,function(val){options.dports.push(Perv.int_of_string(val))}],
|
||||
['-L',2,function(val1,val2){options.links.push([Perv.int_of_string(val1),getip(val2),getipport(val2)])}],
|
||||
['-nokeepalive',0,function(val){options.keepalive=false;}],
|
||||
['-monitor',0,function() {options.monitor++;}],
|
||||
[['-v','-verbose'],0,function() {options.verbose++;}],
|
||||
['-T',0,function(val){options.tcpnet=1;options.http=false;}],
|
||||
['-T2',0,function(val){options.tcpnet=2;options.http=false;}],
|
||||
['-H',0,function(val){options.http=true;options.tcpnet=0;}],
|
||||
function (val) {
|
||||
options.app=val;
|
||||
}
|
||||
],2);
|
||||
|
||||
var verbose = !Comp.array.contains(options.boot_ops,Operation.EXECUTE);
|
||||
var makeapp = Comp.array.contains(options.boot_ops,Operation.MAKEAPP);
|
||||
var refresh = Comp.array.contains(options.boot_ops,Operation.REFRESH);
|
||||
var start = Comp.array.contains(options.boot_ops,Operation.START);
|
||||
options.privhostport = Net.uniqport();
|
||||
options.pubhostport = Net.prv2pub(options.privhostport);
|
||||
|
||||
// console.log(process.argv)
|
||||
|
||||
if (Comp.array.empty(options.boot_ops)) {
|
||||
Io.out('usage: boot [-verbose -v -monitor -H -T -T2 -nokeepalive] ');
|
||||
Io.out(' [clear disable enable init makeapp refresh(reconf) start stop]');
|
||||
Io.out(' [-conf <pathtoconffile>] (default '+options.bootsf+')');
|
||||
Io.out(' [-state <pathtostatefile>] (default '+options.bootst+')');
|
||||
Io.out(' [<service>]');
|
||||
Io.out(' [exec <app> args...]');
|
||||
process.exit(-1);
|
||||
} else if (Comp.array.contains(options.boot_ops,Operation.EXECUTE)) {
|
||||
Io.out('[BOOT] Executing '+options.app+' '+Comp.printf.list(options.args,_,' ')+' ..');
|
||||
process.argv=Comp.array.concat(['node',options.app],options.args);
|
||||
Require(options.app)
|
||||
return;
|
||||
}
|
||||
|
||||
/** Main watchdog service loop.
|
||||
* 1. Poll servers and records status
|
||||
* 2. Stops or restarts services
|
||||
*
|
||||
*/
|
||||
var watchdog = function (serv) {
|
||||
var self=this;
|
||||
|
||||
this.serv = serv;
|
||||
this.cap = null;
|
||||
this.down = false;
|
||||
this.dying = false;
|
||||
this.delay = serv.polltime||1000; // Poll delay
|
||||
this.maxtimeout = 15000; // Maximal time between a successfull repsonse and failures
|
||||
this.maxfailed = 5; // Maximal number of failures before state is changed to maintenance
|
||||
this.maxrestart = 3; // Maximal number of failed restarts
|
||||
|
||||
this.init = function () {
|
||||
Io.out('[BOOT] Polling service '+serv.app+' with PID='+serv.process.pid+' with '+serv.pollcap);
|
||||
serv.status = Net.Status.STD_UNKNOWN;
|
||||
serv.timestamp = Io.time();
|
||||
serv.restart = 0;
|
||||
serv.failed = 0;
|
||||
if (Io.exists(serv.pollcap)) {
|
||||
this.cap = Net.cap_of_file(serv.pollcap);
|
||||
if (this.cap) {
|
||||
Io.out('[BOOT] Using poll capability '+Net.Print.capability(this.cap)+' for '+serv.app);
|
||||
serv.status = Net.Status.STD_OK;
|
||||
} else Io.out('[BOOT] Invalid capability in '+serv.pollcap);
|
||||
} else Io.out('[BOOT] Not found: '+serv.pollcap);
|
||||
};
|
||||
|
||||
this.poll = function () {
|
||||
var thr=this;
|
||||
if (!this.cap) return;
|
||||
switch (serv.state) {
|
||||
case State.ONLINE:
|
||||
case State.MAINTENANCE:
|
||||
//print('pollcap: '+Net.Print.capability(this.cap));
|
||||
Services.network.std.std_info(this.cap, function (stat) {
|
||||
//print('... pollcap: '+Net.Print.capability(self.cap));
|
||||
serv.status=stat;
|
||||
if (serv.status==Net.Status.STD_OK) serv.timestamp=Io.time();
|
||||
else {
|
||||
serv.failed++;
|
||||
Io.out('[BOOT] Polling failed for '+serv.app+': '+Net.Print.status(serv.status));
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.service = function () {
|
||||
var state=serv.state,t;
|
||||
switch (serv.state) {
|
||||
case State.ONLINE:
|
||||
if ((Io.time()-serv.timestamp) > this.maxtimeout || serv.failed > this.maxfailed) {
|
||||
serv.state=State.MAINTENANCE;
|
||||
Io.out('[BOOT] '+serv.app+': '+state+' -> '+serv.state);
|
||||
}
|
||||
break;
|
||||
case State.MAINTENANCE:
|
||||
Io.out('[BOOT] Stopping '+serv.app);
|
||||
serv.stop();
|
||||
serv.state=State.OFFLINE;
|
||||
this.cap=undefined;
|
||||
break;
|
||||
case State.OFFLINE:
|
||||
if (serv.restart<this.maxrestart && serv.check()) {
|
||||
Io.out('[BOOT] Starting '+serv.app);
|
||||
serv.restart++;
|
||||
serv.start();
|
||||
Io.out('[BOOT] '+serv.app+': '+state+' -> '+serv.state);
|
||||
serv.timestamp = Io.time();
|
||||
serv.failed = 0;
|
||||
} else if (serv.restart>=this.maxrestart) {
|
||||
serv.state=State.DISABLED;
|
||||
Io.out('[BOOT] '+serv.app+' too often failed: '+state+' -> '+serv.state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
serv.status = Net.Status.STD_UNKNOWN;
|
||||
};
|
||||
|
||||
this.sleep = function () {
|
||||
Delay(this.delay);
|
||||
if (!this.cap && serv.state==State.ONLINE && Io.exists(serv.pollcap)) {
|
||||
this.cap = Net.cap_of_file(serv.pollcap);
|
||||
if (this.cap) {
|
||||
Io.out('[BOOT] Using poll capability '+Net.Print.capability(this.cap)+' for '+serv.app);
|
||||
serv.status = Net.Status.STD_OK;
|
||||
} else Io.out('[BOOT] Invalid capability in '+serv.pollcap);
|
||||
};
|
||||
};
|
||||
this.terminate = function () {};
|
||||
|
||||
this.transitions = [
|
||||
[undefined,this.init,function () {return serv.state==State.ONLINE}],
|
||||
[this.init,this.sleep],
|
||||
[this.poll,this.service, function (thr) {
|
||||
return serv.status == Net.Status.RPC_FAILURE }],
|
||||
[this.poll,this.sleep, function (thr) {
|
||||
return serv.status != Net.Status.RPC_FAILURE }],
|
||||
[this.service,this.sleep],
|
||||
[this.sleep,this.poll, function () {
|
||||
return !this.dying && serv.state!=State.OFFLINE}],
|
||||
[this.sleep,this.service, function () {
|
||||
return !this.dying && serv.state==State.OFFLINE}],
|
||||
[this.sleep,this.terminate, function () {return this.dying}]
|
||||
];
|
||||
this.on = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
// console.log(boot_ops)
|
||||
|
||||
/***************
|
||||
** SERVICES
|
||||
***************/
|
||||
|
||||
var services = function (options) {
|
||||
this.options=options;
|
||||
this.services=[];
|
||||
};
|
||||
|
||||
services.prototype.add = function (options) {
|
||||
if (!options.trans || !options.app)
|
||||
Io.fail('[BOOT] Invalid service object '+Io.inspect(options));
|
||||
var serv=Service(options);
|
||||
this.services.push(serv);
|
||||
serv.services=this;
|
||||
Io.out('[BOOT] Adding service '+serv.info());
|
||||
}
|
||||
|
||||
|
||||
services.prototype.exec = function (op) {
|
||||
Comp.array.iter(this.services,function (s) {
|
||||
var state=s.state,t,ready;
|
||||
if ((Comp.string.contains(s.app,options.app) || options.app=='*')) {
|
||||
switch (op) {
|
||||
case Operation.INFO:
|
||||
Io.out('[BOOT] '+s.app+' State='+s.state);
|
||||
break;
|
||||
case Operation.MAKEAPP:
|
||||
s.state=State.TEST;
|
||||
Io.out('[BOOT] '+s.app+' doing '+op);
|
||||
t=s.start();
|
||||
if (t && t.wait) Io.sleep(t.wait);
|
||||
s.state=State.UNINITIALIZED;
|
||||
break;
|
||||
case Operation.INIT:
|
||||
case Operation.START:
|
||||
if (s.state==State.ONLINE) return;
|
||||
if (op==Operation.START && s.state==State.MAINTENANCE) return;
|
||||
if (op==Operation.INIT && s.state!=State.UNINITIALIZED) return;
|
||||
// if (s.state==State.DISABLED) state=s.state=State.OFFLINE;
|
||||
ready=s.check();
|
||||
if (ready) {
|
||||
Io.out('[BOOT] '+s.app+' doing '+op);
|
||||
s.start();
|
||||
} else {
|
||||
Io.out('[BOOT] Warning: '+s.app+' has missing dependencie(s), cannot start!');
|
||||
s.state==State.OFFLINE;
|
||||
}
|
||||
break;
|
||||
case Operation.STOP:
|
||||
case Operation.DISABLE:
|
||||
if (s.state==State.ONLINE) {
|
||||
Io.out('[BOOT] Stopping '+s.app);
|
||||
s.stop();
|
||||
}
|
||||
if (op==Operation.DISABLE) {
|
||||
s.state=State.DISABLED;
|
||||
Io.out('[BOOT] '+s.app+' State '+state+' -> '+s.state);
|
||||
}
|
||||
break;
|
||||
case Operation.ENABLE:
|
||||
s.state=State.OFFLINE;
|
||||
Io.out('[BOOT] '+s.app+' State '+state+' -> '+s.state);
|
||||
break;
|
||||
case Operation.CLEAR:
|
||||
if (s.state==State.ONLINE) s.state=State.OFFLINE;
|
||||
if (s.state==State.OFFLINE) s.state=State.UNINITIALIZED;
|
||||
Io.out('[BOOT] '+s.app+' State '+state+' -> '+s.state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
services.prototype.find = function (app) {
|
||||
return Comp.array.find(this.services, function (s) {
|
||||
return s.app==app;
|
||||
});
|
||||
}
|
||||
|
||||
services.prototype.read = function (file) {
|
||||
try {
|
||||
Io.out('[BOOT] Reading service file '+file+' ..');
|
||||
var text=Io.read_file(file);
|
||||
if (text==undefined) throw 'No such file';
|
||||
services=eval(text);
|
||||
if (Comp.obj.isArray(services))
|
||||
Comp.array.iter(services,this.add.bind(this));
|
||||
} catch (e) {
|
||||
Io.out('[BOOT] Reading or compiling of configuration file '+file+' failed: '+e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
/***************
|
||||
** SERVICE
|
||||
***************/
|
||||
var service = function (options) {
|
||||
this.state=options.state||State.UNINITIALIZED;
|
||||
this.trans=options.trans;
|
||||
this.app=options.app;
|
||||
this.depends=options.depends;
|
||||
this.poll=options.poll||true;
|
||||
this.pollcap=options.pollcap;
|
||||
this.polltime=options.polltime;
|
||||
}
|
||||
|
||||
|
||||
var Service = function (options) {
|
||||
return new service(options);
|
||||
}
|
||||
|
||||
/** Check dependencies. Return true if service is ready to be scheduled.
|
||||
*
|
||||
*/
|
||||
service.prototype.check = function () {
|
||||
var self=this,state=this.state,ready=true,S=this.services;
|
||||
|
||||
if (!this.depends) return true;
|
||||
Comp.array.iter(this.depends,function (dep) {
|
||||
var serv=S.find(dep);
|
||||
if (serv) {
|
||||
ready=ready && (serv.state==State.ONLINE);
|
||||
} else {
|
||||
Io.out('[BOOT] Warning: Service '+self.app+' has unknwon dependency: '+dep);
|
||||
ready=false;
|
||||
}
|
||||
});
|
||||
return ready;
|
||||
}
|
||||
|
||||
service.prototype.info = function () {
|
||||
return this.app+': '+this.state;
|
||||
}
|
||||
|
||||
/** Start a service. Searches a matching transition based on current state.
|
||||
*/
|
||||
service.prototype.start = function () {
|
||||
var self=this,_args=process.argv,trans;
|
||||
Comp.array.iter_break(this.trans, function (t) {
|
||||
var exit=false;
|
||||
if (Comp.array.contains(t.states,self.state)) {
|
||||
exit=true;
|
||||
trans=t;
|
||||
}
|
||||
return exit;
|
||||
});
|
||||
if (!trans) return;
|
||||
if (Comp.array.contains(options.boot_ops,Operation.MAKEAPP)) {
|
||||
Io.out('[BOOT] Compiling '+this.app+' '+Comp.printf.list(trans.args,_,' ')+' ..');
|
||||
process.argv=Comp.array.concat(['node',this.app],trans.args);
|
||||
this.process=Require(this.app);
|
||||
process.argv=_args;
|
||||
} else {
|
||||
this.process=Io.fork(options.prog,Comp.array.concat(['exec',this.app],trans.args));
|
||||
Io.out('[BOOT] '+this.process.pid+': Started '+this.app+' '+Comp.printf.list(trans.args,_,' ')+' ..');
|
||||
switch (this.state) {
|
||||
case State.OFFLINE:
|
||||
case State.UNINITIALIZED:
|
||||
if (trans.wait) Io.sleep(trans.wait);
|
||||
this.state=State.ONLINE; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
service.prototype.stop = function () {
|
||||
if (this.process) {
|
||||
Io.out('[BOOT] '+this.process.pid+': Stopping '+this.app+' ..');
|
||||
this.process.kill();
|
||||
}
|
||||
this.process=null;
|
||||
this.state=State.OFFLINE;
|
||||
}
|
||||
|
||||
services.prototype.watchdog = function () {
|
||||
this.network = Conn.setup(options);
|
||||
this.network.init(this.network.start.bind(this));
|
||||
Comp.array.iter(this.services,function (s) {
|
||||
if (s.state != State.TEST && s.pollcap) {
|
||||
Io.out('[BOOT] Adding watchdog service for '+s.app);
|
||||
Sch.NewTask('WATCHDDOG '+s.app,watchdog,s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
services.prototype.write = function (file) {
|
||||
var services=[],
|
||||
text;
|
||||
Io.out('[BOOT] Writing service file '+file+' ..');
|
||||
Comp.array.iter(this.services,function (s) {
|
||||
var sc={app:s.app,trans:s.trans,state:s.state};
|
||||
if (s.poll) sc.poll=s.poll;
|
||||
if (s.pollcap) sc.pollcap=s.pollcap;
|
||||
if (s.polltime) sc.pollcap=s.polltime;
|
||||
if (s.depends) sc.depends=s.depends;
|
||||
services.push(sc);
|
||||
});
|
||||
text=JSON.stringify(services);
|
||||
Io.write_file(file,text);
|
||||
}
|
||||
|
||||
My.host=Io.hostname();
|
||||
|
||||
var Services = new services({});
|
||||
var scheduler = Sch.TaskScheduler();
|
||||
scheduler.Init();
|
||||
|
||||
if (makeapp || refresh || !Io.exists(options.bootst))
|
||||
Services.read(options.bootsf);
|
||||
else
|
||||
Services.read(options.bootst);
|
||||
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
Io.out('Got SIGINT ..');
|
||||
Io.out('[BOOT] Stopping all services ..');
|
||||
Services.exec(Operation.STOP);
|
||||
Services.write(options.bootst);
|
||||
process.exit(2);
|
||||
});
|
||||
|
||||
for (var cmd in options.boot_ops) {
|
||||
Services.exec(options.boot_ops[cmd]);
|
||||
}
|
||||
|
||||
Services.write(options.bootst);
|
||||
|
||||
if (start) {
|
||||
Services.watchdog();
|
||||
scheduler.Run();
|
||||
} else if (!makeapp) {
|
||||
Io.out('Exiting ..');
|
||||
process.exit(0);
|
||||
} else Io.out('Waiting ..');
|
Loading…
Reference in New Issue
Block a user