@luminati-io/luminati-proxy
Version:
A configurable local proxy for brightdata.com
155 lines (151 loc) • 5.71 kB
JavaScript
// LICENSE_CODE ZON ISC
'use strict'; /*jslint node:true, esnext:true*/
const cluster = require('cluster');
const os = require('os');
const forge = require('node-forge');
const lpm_file = require('../util/lpm_file.js');
const date = require('../util/date.js');
const zerr = require('../util/zerr.js');
const logger = require('./logger.js').child({category: 'CMGR'});
const no_workers_restart_timeout = 5*date.ms.MIN;
const keys = forge.pki.rsa.generateKeyPair(2048);
keys.privateKeyPem = forge.pki.privateKeyToPem(keys.privateKey);
keys.publicKeyPem = forge.pki.publicKeyToPem(keys.publicKey);
class Cluster_mgr {
constructor(mgr){
this.mgr = mgr;
this.on_worker_exit = this.on_worker_exit.bind(this);
}
run(){
const cores = os.cpus().length;
const num_workers = this.mgr.argv.cluster===true ?
cores : Number(this.mgr.argv.cluster)||1;
logger.system('Master cluster setting up '+num_workers+' workers');
const args = process.argv.slice(process.platform=='win32' ? 1 : 2);
// prevent race condition bugs when multiple workers interleave the
// work_dir migration in util/lpm_file.js
if (!args.includes('--dir'))
args.push('--dir', lpm_file.work_dir);
cluster.setupMaster({
exec: __dirname+'/worker.js',
execArgv: process.execArgv.concat(['--max-old-space-size=1024',
'--max-http-header-size=80000']),
args,
});
this.mgr._defaults.num_workers = num_workers;
const level = this.mgr.get_logger_level(this.mgr._defaults.log, true);
for (let i=0; i<num_workers; i++)
{
const worker = cluster.fork();
this.init_worker(worker, level);
}
cluster.on('exit', this.on_worker_exit);
this.inited = true;
}
worker_send(worker, message){
if (!worker)
return logger.warn(`Skip send on null worker`);
if (!worker.isConnected())
{
return logger.warn('Skip send %s: worker %s not connected',
message.code||'NO_CODE', worker.id);
}
worker.send(message, undefined, undefined, e=>{
if (e)
{
logger.error('Worker %s send error:\nPayload: %s\nErr: %s',
worker.id, JSON.stringify(message, null, 2), zerr.e2s(e));
}
});
}
on_worker_exit(worker, code, signal){
// remove all listeners when worker exit
worker.removeAllListeners();
if (worker.exitedAfterDisconnect || global.it)
return;
this.mgr.perr('error', {
ctx: 'worker_die',
error: ''+code+': '+signal,
});
logger.warn('Worker with PID %s died (%s %s). Restarting...',
worker.process.pid, code, signal);
this.recreate_worker();
}
init_worker(worker, level){
worker.setMaxListeners(0);
this.send_worker_setup(worker, level);
}
send_worker_setup(worker, level){
this.worker_send(worker, {
code: 'SETUP',
level,
customer: this.mgr._defaults.customer,
ca: this.mgr.get_ssl_ca(),
keys,
zagent: this.mgr.argv.zagent,
av_server: this.mgr.argv.av_server,
no_usage_stats: this.mgr.argv.no_usage_stats,
extra_ssl_ips: [...new Set([
...this.mgr._defaults.extra_ssl_ips||[],
...this.mgr.argv.extra_ssl_ips||[],
])],
});
}
run_workers(){
const cores = os.cpus().length;
const num_workers = this.mgr.argv.cluster===true ?
cores : Number(this.mgr.argv.cluster)||1;
logger.notice('Recreating '+num_workers+' workers');
for (let i=0; i<num_workers; i++)
this.recreate_worker();
}
recreate_worker(){
const new_worker = cluster.fork();
const level = this.mgr.get_logger_level(this.mgr._defaults.log, true);
this.init_worker(new_worker, level);
logger.notice('Worker with PID %s recreated', new_worker.process.pid);
Object.values(this.mgr.proxy_ports).forEach(p=>{
p.setup_worker(new_worker);
});
}
kill_workers(){
Object.values(cluster.workers).forEach(w=>{
// killing worker process immediately with disconnecting
// will not be rerun automatically
Object.values(this.mgr.proxy_ports).forEach(p=>{
p.remove_worker(w);
});
w.disconnect();
w.process.kill();
});
}
workers_running(){
return Object.values(cluster.workers);
}
broadcast(code, payload){
Object.values(cluster.workers).forEach(w=>
this.worker_send(w, {code, data: payload}));
}
health_check(){
if (!this.inited)
return;
const workers_running = this.workers_running().length;
if (workers_running==0 && !this.reload_to)
{
logger.system('Init process restart in %s, workers running is %s',
date.describe_interval(no_workers_restart_timeout),
workers_running);
this.reload_to = setTimeout(this.mgr.process_exit.bind(this.mgr,
'health check', 0), no_workers_restart_timeout);
}
if (workers_running>0 && this.reload_to)
{
logger.system('Restart cancellation, workers running is %s',
workers_running);
clearTimeout(this.reload_to);
this.reload_to = null;
}
}
}
module.exports = Cluster_mgr;