1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | var pathUtil = require("path");
|
16 |
|
17 | var exitProcess = require("exit");
|
18 |
|
19 | var optimizeParallel = require("../util/optimize-parallel.js");
|
20 | var spawn = require("../util/child-processes.js").spawn;
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | var attester = require("../attester");
|
27 | var config = attester.config;
|
28 | var logger = attester.logger;
|
29 |
|
30 |
|
31 | var cfg = {
|
32 | maxRetries: 3,
|
33 |
|
34 | onAllPhantomsDied: function () {
|
35 | endProcess(1);
|
36 | },
|
37 | phantomPath: null,
|
38 | slaveURL: null,
|
39 | pipeStdOut: true,
|
40 | phantomInstances: 0
|
41 | };
|
42 | var state = {
|
43 | resetCalled: false,
|
44 | retries: [],
|
45 |
|
46 | erroredPhantomInstances: 0
|
47 |
|
48 | };
|
49 |
|
50 |
|
51 |
|
52 | module.exports = {
|
53 | __init__: function () {
|
54 | state.resetCalled = false;
|
55 | attester.event.on("launcher.connect", onLauncherConnect);
|
56 | },
|
57 | __reset__: function () {
|
58 | state.resetCalled = true;
|
59 | attester.event.off("launcher.connect", onLauncherConnect);
|
60 | },
|
61 | |
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | bootPhantom: function (cfg, state, n) {
|
69 | cfg.args = cfg.args || {};
|
70 | var phantomPath = cfg.phantomPath;
|
71 | var controlScript = pathUtil.join(__dirname, '../browsers/phantomjs-control-script.js');
|
72 |
|
73 | var args = [];
|
74 | args.push(controlScript);
|
75 | args.push("--auto-exit");
|
76 | if (cfg.args.autoExitPolling) {
|
77 | args.push("--auto-exit-polling=" + cfg.args.autoExitPolling);
|
78 | }
|
79 | if (typeof n == "undefined") {
|
80 | n = Math.round(Math.random() * 1000) % 1000;
|
81 | }
|
82 | args.push("--instance-id=" + n);
|
83 | args.push(cfg.slaveURL);
|
84 |
|
85 | var phantomProcess = spawn(phantomPath, args, {
|
86 | stdio: "pipe"
|
87 | });
|
88 | if (cfg.pipeStdOut) {
|
89 | phantomProcess.stdout.pipe(process.stdout);
|
90 | phantomProcess.stderr.pipe(process.stderr);
|
91 | }
|
92 | if (cfg.onData) {
|
93 | phantomProcess.stdout.on("data", cfg.onData);
|
94 | }
|
95 | phantomProcess.on("exit", cfg.onExit || this.createPhantomExitCb(cfg, state, n).bind(this));
|
96 | phantomProcess.on("error", cfg.onError || this.createPhantomErrorCb(cfg, state, n).bind(this));
|
97 | return phantomProcess;
|
98 | },
|
99 |
|
100 | |
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | createPhantomExitCb: function (cfg, state, n) {
|
108 |
|
109 |
|
110 | return function (code, signal) {
|
111 |
|
112 | if (code === 0 || signal == "SIGTERM" || state.resetCalled) {
|
113 | return;
|
114 | }
|
115 |
|
116 | var isNotRecoverable = (code == 127 || code == 126);
|
117 | if (isNotRecoverable) {
|
118 | ++state.erroredPhantomInstances;
|
119 | var path = cfg.phantomPath;
|
120 | if (code == 127) {
|
121 | logger.logError("Spawn: exited with code 127. PhantomJS executable not found. Make sure to download PhantomJS and add its folder to your system's PATH, or pass the full path directly to Attester via --phantomjs-path.\nUsed command: '" + path + "'");
|
122 | } else if (code == 126) {
|
123 | logger.logError("Spawn: exited with code 126. Unable to execute PhantomJS. Make sure to have proper read & execute permissions set.\nUsed command: '" + path + "'");
|
124 | }
|
125 | checkIfAllPhantomsDied(cfg, state);
|
126 | return;
|
127 | }
|
128 |
|
129 |
|
130 |
|
131 |
|
132 | var errMsg;
|
133 | if (code == 75) {
|
134 | errMsg = "Spawn: PhantomJS[" + n + "] exited with code 75: unable to load attester page within specified timeout, or errors happened while loading.";
|
135 | if (cfg.phantomInstances > 1) {
|
136 | errMsg += " You may try decreasing the number of PhantomJS instances in attester config to avoid that problem.";
|
137 | }
|
138 | } else {
|
139 | errMsg = "Spawn: PhantomJS[" + n + "] exited with code " + code + " and signal " + signal;
|
140 | }
|
141 |
|
142 |
|
143 | var retries = state.retries;
|
144 | retries[n] = (retries[n] || 0) + 1;
|
145 | if (retries[n] < cfg.maxRetries) {
|
146 |
|
147 | logger.logWarn(errMsg);
|
148 | logger.logWarn("Trying to reboot instance nr " + n + "...");
|
149 | this.bootPhantom(cfg, state, n);
|
150 | } else {
|
151 | logger.logError(errMsg);
|
152 | ++state.erroredPhantomInstances;
|
153 | checkIfAllPhantomsDied(cfg, state);
|
154 | }
|
155 | };
|
156 | },
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | createPhantomErrorCb: function (cfg, state, n) {
|
166 | return function (err) {
|
167 | if (err.code == "ENOENT") {
|
168 | logger.logError("Spawn: exited with code ENOENT. PhantomJS executable not found. Make sure to download PhantomJS and add its folder to your system's PATH, or pass the full path directly to Attester via --phantomjs-path.\nUsed command: '" + cfg.phantomPath + "'");
|
169 | } else {
|
170 | logger.logError("Unable to spawn PhantomJS; error code " + err.code);
|
171 | }
|
172 | };
|
173 | }
|
174 | };
|
175 |
|
176 | function onLauncherConnect(slaveURL) {
|
177 | var suggestedInstances = config["phantomjs-instances"];
|
178 | var phantomInstances = optimizeParallel({
|
179 | memoryPerInstance: 60,
|
180 | maxInstances: suggestedInstances
|
181 | }, logger);
|
182 | if (phantomInstances === 0) {
|
183 | return;
|
184 | }
|
185 |
|
186 |
|
187 | cfg.phantomInstances = phantomInstances;
|
188 | cfg.phantomPath = config["phantomjs-path"];
|
189 | cfg.slaveURL = slaveURL;
|
190 |
|
191 | logger.logDebug("Spawning " + phantomInstances + " instances of PhantomJS");
|
192 | if (phantomInstances == 1) {
|
193 |
|
194 |
|
195 |
|
196 | module.exports.bootPhantom(cfg, state);
|
197 | } else {
|
198 | for (var n = 1; n <= phantomInstances; n++) {
|
199 | module.exports.bootPhantom(cfg, state, n);
|
200 | }
|
201 | }
|
202 | }
|
203 |
|
204 | function checkIfAllPhantomsDied(cfg, state) {
|
205 |
|
206 | if (state.erroredPhantomInstances === cfg.phantomInstances && cfg.phantomInstances > 0) {
|
207 | logger.logError("All the instances of PhantomJS were terminated with errors; disposing attester and exiting");
|
208 | if (cfg.onAllPhantomsDied) {
|
209 | cfg.onAllPhantomsDied();
|
210 | }
|
211 | }
|
212 | }
|
213 |
|
214 | function endProcess(code) {
|
215 | attester.event.emit("closing");
|
216 | process.nextTick(function () {
|
217 | exitProcess(code);
|
218 | });
|
219 | }
|