pomeloes-robot
Version:
A fully modernized pomelo-robot, upgraded with ES6+ and latest dependencies. Now supports standalone mode.
162 lines (142 loc) • 5.05 kB
JavaScript
const io = require('socket.io-client');
const logging = require('../common/logging').Logger;
const { Actor } = require('./actor');
const monitor = require('../monitor/monitor');
const util = require('../common/util');
const STATUS_INTERVAL = 10 * 1000; // 10 seconds
const RECONNECT_INTERVAL = 10 * 1000; // 10 seconds
const HEARTBEAT_PERIOD = 30 * 1000; // 30 seconds
const HEARTBEAT_FAILS = 3; // Reconnect after 3 missed heartbeats
class Agent {
constructor(conf) {
this.log = logging;
this.conf = conf || {};
this.last_heartbeat = null;
this.connected = false;
this.reconnecting = false;
this.actors = {};
this.count = 0;
}
// Create socket, bind callbacks, connect to server
connect() {
const uri = `http://${this.conf.master.host}:${this.conf.master.port}`;
this.socket = io(uri, {
forceNew: true,
transports: ['websocket'],
reconnection: false
});
this.socket.on('error', (reason) => {
this.log.error(`Socket error: ${reason}`);
this.reconnect();
});
this.socket.on('connect', () => {
this.log.info("Connected to server, sending announcement...");
this.announce();
this.connected = true;
this.reconnecting = false;
this.last_heartbeat = new Date().getTime();
});
this.socket.on('disconnect', (reason) => {
this.log.error(`Disconnected from server: ${reason}`);
this.connected = false;
});
this.socket.on('heartbeat', () => {
this.last_heartbeat = new Date().getTime();
});
this.socket.on('node_already_exists', () => {
this.log.error("ERROR: A node of the same name is already registered. Exiting.");
process.exit(1);
});
this.socket.on('run', (message) => {
this.run(message);
});
this.socket.on('exit4reready', () => {
this.log.info("Exit for BTN_ReReady.");
process.exit(0);
});
}
run(msg) {
util.deleteLog();
this.count = msg.maxuser;
const { script, index } = msg;
if (script && script.length > 1) {
this.conf.script = script;
}
this.log.info(`${this.nodeId} runs ${this.count} actors`);
monitor.clear();
this.actors = {};
const offset = index * this.count;
for (let i = 0; i < this.count; i++) {
const aid = i + offset;
const actor = new Actor(this.conf, aid);
this.actors[aid] = actor;
// Listen for errors from the actor's script execution (vm sandbox)
// and log them to the console, so they appear in the redirected log file.
actor.on('error', (error) => {
console.error(`[Actor ${aid} Error]`, error);
// Also report the error to the master server
this.socket.emit('error', `Actor ${aid} crashed: ${error.message}`);
});
if (this.conf.master.interval <= 0) {
actor.run();
} else {
const time = Math.round(Math.random() * 1000 + i * this.conf.master.interval);
setTimeout(() => {
actor.run();
}, time);
}
}
setInterval(() => {
const mdata = monitor.getData();
this.socket.emit('report', mdata);
}, STATUS_INTERVAL);
}
start() {
this.connect();
setInterval(() => {
if (this.last_heartbeat === null) return;
const delta = Date.now() - this.last_heartbeat;
if (delta > (HEARTBEAT_PERIOD * HEARTBEAT_FAILS)) {
this.log.warn("Failed heartbeat check, reconnecting...");
this.connected = false;
this.reconnect();
}
}, HEARTBEAT_PERIOD);
}
announce() {
const sessionid = this.socket.id;
this.nodeId = sessionid;
this._send('announce_node', {
client_type: 'node',
nodeId: sessionid
});
}
reconnect(force) {
if (!force && this.reconnecting) { return; }
this.reconnecting = true;
if (this.socket) {
this.socket.disconnect();
}
this.connected = false;
this.log.info("Reconnecting to server...");
setTimeout(() => {
if (this.connected) {
this.reconnecting = false;
return;
}
this.connect();
}, RECONNECT_INTERVAL);
}
_send(event, message) {
try {
if (this.socket) {
this.socket.emit(event, message);
}
} catch (err) {
this.log.error("ERROR: Unable to send message over socket.");
this.connected = false;
this.reconnect();
}
}
}
exports.Agent = Agent;