UNPKG

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
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;