Skip to content

Commit b2d5586

Browse files
committed
Merge pull request #24 from AnyFetch/byebye-domains
Byebye domains
2 parents 8ae0fc5 + 4870fd0 commit b2d5586

File tree

15 files changed

+398
-334
lines changed

15 files changed

+398
-334
lines changed

lib/helpers/child-process.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use strict";
2+
var async = require("async");
3+
4+
process.on('message', function(task) {
5+
var hydrate = require(task.functionPath);
6+
async.waterfall([
7+
function startHydration(cb) {
8+
cb.urlCallback = task.options.urlCallback;
9+
cb.apiUrl = task.options.apiUrl;
10+
hydrate(task.path, task.document, task.changes, cb);
11+
}
12+
],
13+
function(err, changes) {
14+
process.send({
15+
err: err,
16+
changes: changes
17+
});
18+
});
19+
});

lib/helpers/hydrater.js

Lines changed: 76 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*/
99

1010
var async = require('async');
11-
var nodeDomain = require('domain');
11+
var shellFork = require('child_process').fork;
1212
var request = require('supertest');
1313
var crypto = require('crypto');
1414
var restify = require('restify');
@@ -44,38 +44,35 @@ module.exports = function(hydraterFunction, logger, errLogger) {
4444
/**
4545
* Download the file from task.file_path, store it in a temporary file if there is file_path
4646
*/
47-
function initHydration(cb) {
47+
function downloadFile(cb) {
4848
if(task.file_path) {
49-
// Download the file
50-
var stream = fs.createWriteStream(path);
51-
52-
// Store error if statusCode !== 200
53-
var err;
54-
stream.on("finish", function() {
55-
cb(err);
56-
});
57-
5849
var apiUrl = url.parse(task.file_path, false, true);
59-
var req = request(apiUrl.protocol + "//" + apiUrl.host)
60-
.get(apiUrl.path);
61-
62-
req.end().req.once('response', function(res) {
63-
if(res.statusCode !== 200) {
64-
err = new restify.BadGatewayError('Error when downloading file ' + task.file_path + ': ' + res.statusCode);
65-
stream.end();
66-
this.abort();
67-
}
68-
});
69-
70-
req.pipe(stream);
50+
request(apiUrl.protocol + "//" + apiUrl.host)
51+
.get(apiUrl.path)
52+
.expect(200)
53+
.end(function(err, res) {
54+
if(err) {
55+
err = new restify.BadGatewayError('Error when downloading file ' + task.file_path + ': ' + err);
56+
}
57+
cb(err, res && res.text);
58+
});
7159
}
7260
else {
7361
cb(null);
7462
}
7563
},
64+
function saveFile(res, cb) {
65+
if(res) {
66+
fs.writeFile(path, res, cb);
67+
}
68+
else {
69+
cb();
70+
}
71+
},
7672
function performHydration(cb) {
77-
var domain = nodeDomain.create();
78-
73+
var child = shellFork(__dirname + '/child-process.js', {silent: true});
74+
var stderr = "";
75+
var stdout = "";
7976
var timeout;
8077
/**
8178
* Function to call, either on domain error, on hydration error or successful hydration.
@@ -84,53 +81,73 @@ module.exports = function(hydraterFunction, logger, errLogger) {
8481
var cleaner = function(err, changes) {
8582
if(!cleaner.called) {
8683
cleaner.called = true;
87-
domain.exit();
88-
domain.dispose();
8984
cb(err, changes);
9085
}
86+
clearTimeout(timeout);
9187
};
9288
cleaner.called = false;
9389

94-
domain.on('error', cleaner);
95-
96-
// Run in a domain to prevent memory leak on crash
97-
domain.run(function() {
98-
async.waterfall([
99-
function callHydrationFunction(cb) {
100-
// Give user access to the final URL callback and the API url (which can be staging, prod or anything)
101-
// In case he wants to bypass us and send the changes himself
102-
cb.urlCallback = task.callback;
103-
cb.apiUrl = '';
104-
if(task.callback) {
105-
var parsed = url.parse(task.callback);
106-
cb.apiUrl = parsed.protocol + "//" + parsed.host;
107-
}
90+
child.on('error', function(exitCode) {
91+
cleaner(new HydrationError("Wild error appeared while spawning child. Exit code:" + exitCode));
92+
});
10893

109-
// Call the real function for hydration.
110-
hydraterFunction((task.file_path) ? path : null, task.document, lib.defaultChanges(), cb);
111-
}
112-
], function cleanHydration(err, changes) {
113-
// If the function replied with an "HydrationError", we'll wrap this in a nicely formatted document
114-
// and stop the error from bubbling up.
115-
if(err instanceof HydrationError) {
116-
changes = {};
117-
changes.hydration_errored = true;
118-
changes.hydration_error = err.message;
119-
err = null;
120-
}
94+
child.stderr.on('readable', function() {
95+
var chunk;
96+
while (null !== (chunk = child.stderr.read())) {
97+
stderr += chunk;
98+
}
99+
});
100+
101+
child.stdout.on('readable', function() {
102+
var chunk;
103+
while (null !== (chunk = child.stdout.read())) {
104+
stdout += chunk;
105+
}
106+
});
107+
108+
child.on('exit', function(errCode) {
109+
if(errCode !== 0) {
110+
cleaner(new HydrationError("Child exiting with err code: " + errCode + stdout + stderr));
111+
}
112+
});
113+
114+
// Build objects to send to child
115+
var options = {};
116+
options.urlCallback = task.callback;
117+
options.apiUrl = '';
118+
if(task.callback) {
119+
var parsed = url.parse(task.callback);
120+
options.apiUrl = parsed.protocol + "//" + parsed.host;
121+
}
122+
123+
child.send({
124+
functionPath: hydraterFunction,
125+
path: (task.file_path) ? path : null,
126+
document: task.document,
127+
changes: lib.defaultChanges(),
128+
options: options,
129+
});
130+
131+
child.on('message', function(res) {
132+
var err = res.err
133+
// If the function replied with an "HydrationError", we'll wrap this in a nicely formatted document
134+
// and stop the error from bubbling up.
135+
if(err && err._hydrationError) {
136+
res.changes = {};
137+
res.changes.hydration_errored = true;
138+
res.changes.hydration_error = res.err.message;
139+
err = null;
140+
}
141+
cleaner(err, res.changes);
121142

122-
// Wait for nexttick, to end this function and be able to properly GC it on domain.dispose().
123-
process.nextTick(function() {
124-
cleaner(err, changes);
125-
});
126-
});
127143
});
128144

129145
timeout = setTimeout(function() {
130146
if(!cleaner.called) {
131147
var changes = {};
132148
changes.hydration_errored = true;
133149
changes.hydration_error = "Task took too long.";
150+
child.kill('SIGKILL');
134151
cleaner(null, changes);
135152
}
136153
}, process.env.TIMEOUT || 60 * 1000);
@@ -183,6 +200,7 @@ module.exports = function(hydraterFunction, logger, errLogger) {
183200
}
184201

185202
var apiUrl = url.parse(task.callback, false, true);
203+
186204
request(apiUrl.protocol + "//" + apiUrl.host)
187205
.patch(apiUrl.path)
188206
.send(changes)

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,6 @@ module.exports.defaultChanges = function() {
6262
module.exports.HydrationError = function(message) {
6363
this.name = 'HydrationError';
6464
this.message = (message || "").toString();
65+
this._hydrationError = true;
6566
};
6667
util.inherits(module.exports.HydrationError, Error);

0 commit comments

Comments
 (0)