@cgjs/cluster
Version:
cluster core module for cgjs
162 lines (154 loc) • 4.25 kB
JavaScript
const gi = imports.gi;
const GLib = gi.GLib;
const Gio = gi.Gio;
const mainloop = imports.cgjs.mainloop;
const EventEmitter = require('events').EventEmitter;
const cluster = {
isMaster: !process.env._CGJS_WORKER,
isWorker: !!process.env._CGJS_WORKER,
workers: {},
tmp: {},
// FIXME callback isn't called
disconnect(callback) { // eslint-disable-line no-unused-vars
Object.keys(cluster.workers).forEach(id => {
cluster.workers[id].disconnect();
});
},
fork() {
const tmp = GLib.file_open_tmp(null);
const monitor = monitorFile(tmp[1], info => {
if (info.hasOwnProperty('id')) {
cluster.workers[info.id].emit('message', info.data);
}
});
const closeTMP = () => {
monitor.cancel();
GLib.close(tmp);
};
const env = Object.assign(
{_CGJS_WORKER: tmp[1]},
process.env
);
const filename = module.parent.filename;
const [
ok, pid,
// TODO stdin and stderr
stdin, stdout, stderr // eslint-disable-line no-unused-vars
] = GLib.spawn_async_with_pipes(
GLib.path_get_dirname(filename),
[
process.argv[0],
filename
],
Object.keys(env).map(key => `${key}=${env[key]}`),
GLib.SpawnFlags.SEARCH_PATH,
null
);
if (ok) {
cluster.tmp[pid] = tmp;
const worker = (cluster.workers[pid] = new Worker(cluster, pid, tmp[1]));
const reader = new Gio.DataInputStream({
base_stream: new Gio.UnixInputStream({fd: stdout})
});
worker.reader = reader;
worker.async = new Gio.Cancellable;
const asyncRead = (self, result) => {
if (worker.dead) return;
else if (!worker.connected) {
worker.connected = true;
worker.emit('online');
}
const [out, size] = reader.read_line_finish_utf8(result);
if (size) console.log(out.toString());
reader.read_line_async(GLib.PRIORITY_DEFAULT, worker.async, asyncRead);
};
reader.read_line_async(GLib.PRIORITY_DEFAULT, worker.async, asyncRead);
return worker.on('disconnect', closeTMP).on('exit', closeTMP);
} else {
closeTMP();
}
}
};
module.exports = cluster;
class Worker extends EventEmitter {
constructor(cluster, id, tmp) {
super();
this.cluster = cluster;
this.id = id;
this.tmp = tmp;
this.connected = false;
this.dead = false;
this.exitedAfterDisconnect = false;
this.process = process;
mainloop.wait();
}
disconnect() {
destroy
.call(this)
.then(() => this.emit('disconnect'));
}
kill(signal) {
destroy
.call(this)
.then(() => this.emit('exit', null, signal || 'SIGTERM'));
}
send(data) {
if (this.connected) {
sendInfo(this.tmp, {data: data});
} else {
this.once('online', () => this.send(data));
}
}
isConnected() {
return this.connected;
}
isDead() {
return this.dead;
}
}
if (cluster.isWorker) {
process._monitor = monitorFile(process.env._CGJS_WORKER, info => {
if (!info.hasOwnProperty('id')) {
process.emit('message', info.data);
}
});
process.send = (data) => sendInfo(
process.env._CGJS_WORKER,
{
id: process.pid,
data: data
}
);
}
function monitorFile(path, then) {
const monitor = Gio.File.new_for_path(path).monitor_file(
Gio.FileMonitorEvent.CHANGES_DONE_HINT, null
);
monitor.connect('changed', (monitor, file, other, event) => {
if (event === Gio.FileMonitorEvent.CHANGES_DONE_HINT) {
const info = JSON.parse(GLib.file_get_contents(path)[1].toString());
then(info);
}
});
return monitor;
}
function sendInfo(file, info) {
GLib.file_set_contents(file, JSON.stringify(info));
}
function destroy() {
if (this.dead) throw new Error('already dead');
return new Promise(resolve => {
delete this.cluster.workers[this.id];
this.connected = false;
this.dead = true;
this.exitedAfterDisconnect = true;
this.async.cancel();
this.reader.clear_pending();
this.reader.read_line_async(GLib.PRIORITY_DEFAULT, null, () => {
this.reader.close(null);
this.async = this.reader = null;
mainloop.go();
resolve();
});
});
}