Skip to content
This repository was archived by the owner on Aug 31, 2018. It is now read-only.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit adf9752

Browse files
committedOct 15, 2017
worker: improve error (de)serialization
1 parent 659b4ae commit adf9752

6 files changed

+169
-13
lines changed
 

‎lib/internal/error-serdes.js

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
'use strict';
2+
3+
const Buffer = require('buffer').Buffer;
4+
const { serialize, deserialize } = require('v8');
5+
const { SafeSet } = require('internal/safe_globals');
6+
7+
const kSerializedError = 0;
8+
const kSerializedObject = 1;
9+
const kInspectedError = 2;
10+
11+
const GetPrototypeOf = Object.getPrototypeOf;
12+
const GetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
13+
const GetOwnPropertyNames = Object.getOwnPropertyNames;
14+
const DefineProperty = Object.defineProperty;
15+
const Assign = Object.assign;
16+
const ObjectPrototypeToString =
17+
Function.prototype.call.bind(Object.prototype.toString);
18+
const ForEach = Function.prototype.call.bind(Array.prototype.forEach);
19+
const Call = Function.prototype.call.bind(Function.prototype.call);
20+
21+
const errors = {
22+
Error, TypeError, RangeError, URIError, SyntaxError, ReferenceError, EvalError
23+
};
24+
const errorConstructorNames = new SafeSet(Object.keys(errors));
25+
26+
function TryGetAllProperties(object, target = object) {
27+
const all = Object.create(null);
28+
if (object === null)
29+
return all;
30+
Assign(all, TryGetAllProperties(GetPrototypeOf(object), target));
31+
const keys = GetOwnPropertyNames(object);
32+
ForEach(keys, (key) => {
33+
const descriptor = GetOwnPropertyDescriptor(object, key);
34+
const getter = descriptor.get;
35+
if (getter && key !== '__proto__') {
36+
try {
37+
descriptor.value = Call(getter, target);
38+
} catch (e) {}
39+
}
40+
if ('value' in descriptor && typeof descriptor.value !== 'function') {
41+
delete descriptor.get;
42+
delete descriptor.set;
43+
all[key] = descriptor;
44+
}
45+
});
46+
return all;
47+
}
48+
49+
function GetConstructors(object) {
50+
const constructors = [];
51+
52+
for (var current = object;
53+
current !== null;
54+
current = GetPrototypeOf(current)) {
55+
const desc = GetOwnPropertyDescriptor(current, 'constructor');
56+
if (desc && desc.value) {
57+
DefineProperty(constructors, constructors.length, {
58+
value: desc.value, enumerable: true
59+
});
60+
}
61+
}
62+
63+
return constructors;
64+
}
65+
66+
function GetName(object) {
67+
const desc = GetOwnPropertyDescriptor(object, 'name');
68+
return desc && desc.value;
69+
}
70+
71+
let util;
72+
function lazyUtil() {
73+
if (!util)
74+
util = require('util');
75+
return util;
76+
}
77+
78+
function serializeError(error) {
79+
try {
80+
if (typeof error === 'object' &&
81+
ObjectPrototypeToString(error) === '[object Error]') {
82+
const constructors = GetConstructors(error);
83+
for (var i = constructors.length - 1; i >= 0; i--) {
84+
const name = GetName(constructors[i]);
85+
if (errorConstructorNames.has(name)) {
86+
try { error.stack; } catch (e) {}
87+
const serialized = serialize({
88+
constructor: name,
89+
properties: TryGetAllProperties(error)
90+
});
91+
return Buffer.concat([Buffer.from([kSerializedError]), serialized]);
92+
}
93+
}
94+
}
95+
} catch (e) {}
96+
try {
97+
const serialized = serialize(error);
98+
return Buffer.concat([Buffer.from([kSerializedObject]), serialized]);
99+
} catch (e) {}
100+
return Buffer.concat([Buffer.from([kInspectedError]),
101+
Buffer.from(lazyUtil().inspect(error), 'utf8')]);
102+
}
103+
104+
function deserializeError(error) {
105+
switch (error[0]) {
106+
case kSerializedError:
107+
const { constructor, properties } = deserialize(error.slice(1));
108+
const ctor = errors[constructor];
109+
return Object.create(ctor.prototype, properties);
110+
case kSerializedObject:
111+
return deserialize(error.slice(1));
112+
case kInspectedError:
113+
return error.toString('utf8', 1);
114+
}
115+
require('assert').fail('This should not happen');
116+
}
117+
118+
module.exports = { serializeError, deserializeError };

‎lib/internal/worker.js

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
'use strict';
22

3-
const Buffer = require('buffer').Buffer;
43
const EventEmitter = require('events');
54
const assert = require('assert');
65
const path = require('path');
76
const util = require('util');
87
const errors = require('internal/errors');
98

9+
const { serializeError, deserializeError } = require('internal/error-serdes');
10+
1011
const { MessagePort, MessageChannel } = process.binding('messaging');
1112
util.inherits(MessagePort, EventEmitter);
1213

@@ -220,14 +221,6 @@ function setupChild(evalScript) {
220221
port.start();
221222
}
222223

223-
// TODO(addaleax): These can be improved a lot.
224-
function serializeError(error) {
225-
return Buffer.from(util.inspect(error), 'utf8');
226-
}
227-
228-
function deserializeError(error) {
229-
return error.toString('utf8');
230-
}
231224

232225
module.exports = {
233226
MessagePort,

‎node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
'lib/internal/crypto/util.js',
9696
'lib/internal/encoding.js',
9797
'lib/internal/errors.js',
98+
'lib/internal/error-serdes.js',
9899
'lib/internal/freelist.js',
99100
'lib/internal/fs.js',
100101
'lib/internal/http.js',

‎test/parallel/test-error-serdes.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
require('../common');
4+
const assert = require('assert');
5+
const errors = require('internal/errors');
6+
const { serializeError, deserializeError } = require('internal/error-serdes');
7+
8+
function cycle(err) {
9+
return deserializeError(serializeError(err));
10+
}
11+
12+
assert.strictEqual(cycle(0), 0);
13+
assert.strictEqual(cycle(-1), -1);
14+
assert.strictEqual(cycle(1.4), 1.4);
15+
assert.strictEqual(cycle(null), null);
16+
assert.strictEqual(cycle(undefined), undefined);
17+
assert.strictEqual(cycle('foo'), 'foo');
18+
19+
{
20+
const err = cycle(new Error('foo'));
21+
assert(err instanceof Error);
22+
assert.strictEqual(err.name, 'Error');
23+
assert.strictEqual(err.message, 'foo');
24+
assert(/^Error: foo\n/.test(err.stack));
25+
}
26+
27+
assert.strictEqual(cycle(new RangeError('foo')).name, 'RangeError');
28+
assert.strictEqual(cycle(new TypeError('foo')).name, 'TypeError');
29+
assert.strictEqual(cycle(new ReferenceError('foo')).name, 'ReferenceError');
30+
assert.strictEqual(cycle(new URIError('foo')).name, 'URIError');
31+
assert.strictEqual(cycle(new EvalError('foo')).name, 'EvalError');
32+
assert.strictEqual(cycle(new SyntaxError('foo')).name, 'SyntaxError');
33+
34+
class SubError extends Error {}
35+
36+
assert.strictEqual(cycle(new SubError('foo')).name, 'Error');
37+
38+
assert.deepStrictEqual(cycle({ message: 'foo' }), { message: 'foo' });
39+
assert.strictEqual(cycle(Function), '[Function: Function]');
40+
41+
{
42+
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE', 'object', 'object');
43+
assert(/^TypeError \[ERR_INVALID_ARG_TYPE\]:/.test(err));
44+
assert.strictEqual(err.name, 'TypeError [ERR_INVALID_ARG_TYPE]');
45+
assert.strictEqual(err.code, 'ERR_INVALID_ARG_TYPE');
46+
}

‎test/parallel/test-worker-uncaught-exception-async.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ if (isMainThread) {
77
const w = new Worker(__filename);
88
w.on('message', common.mustNotCall());
99
w.on('error', common.mustCall((err) => {
10-
// TODO(addaleax): be more specific here
11-
assert(/foo/.test(err));
10+
assert(/^Error: foo$/.test(err));
1211
}));
1312
} else {
1413
setImmediate(() => {

‎test/parallel/test-worker-uncaught-exception.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ if (isMainThread) {
77
const w = new Worker(__filename);
88
w.on('message', common.mustNotCall());
99
w.on('error', common.mustCall((err) => {
10-
// TODO(addaleax): be more specific here
11-
assert(/foo/.test(err));
10+
assert(/^Error: foo$/.test(err));
1211
}));
1312
} else {
1413
throw new Error('foo');

0 commit comments

Comments
 (0)
This repository has been archived.