Skip to content

Commit fab6db9

Browse files
committed
workers: initial implementation
1 parent a07c691 commit fab6db9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3763
-507
lines changed

LICENSE

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,3 +657,34 @@ The externally maintained libraries used by io.js are:
657657
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
658658
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
659659
"""
660+
- src/producer-consumer-queue.h. The folly::ProducerConsumerQueue class is a
661+
one-producer one-consumer queue with very low synchronization overhead.
662+
ProducerConsumerQueue's license follows:
663+
"""
664+
Copyright 2015 Facebook, Inc.
665+
666+
Licensed under the Apache License, Version 2.0 (the "License");
667+
you may not use this file except in compliance with the License.
668+
You may obtain a copy of the License at
669+
670+
http://www.apache.org/licenses/LICENSE-2.0
671+
672+
Unless required by applicable law or agreed to in writing, software
673+
distributed under the License is distributed on an "AS IS" BASIS,
674+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
675+
See the License for the specific language governing permissions and
676+
limitations under the License.
677+
678+
Significant changes made to the software:
679+
680+
- Removed Boost dependency
681+
- Removed support for storing values directly
682+
- Removed construction and destruction of the queue items feature
683+
- Added initialization of all values to nullptr
684+
- Made size a template parameter
685+
- Crash instead of throw if malloc fails in constructor
686+
- Changed namespace from folly to node
687+
- Removed sizeGuess(), isFull(), isEmpty(), popFront() and frontPtr() methods
688+
- Renamed write() to PushBack(), read() to PopFront()
689+
- Added padding to fields
690+
"""

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ test-timers:
163163
test-timers-clean:
164164
$(MAKE) --directory=tools clean
165165

166+
test-workers: all
167+
$(PYTHON) tools/test.py --mode=release workers -J
168+
169+
test-workers-debug: all
170+
$(PYTHON) tools/test.py --mode=debug workers -J
171+
166172
apidoc_sources = $(wildcard doc/api/*.markdown)
167173
apidocs = $(addprefix out/,$(apidoc_sources:.markdown=.html)) \
168174
$(addprefix out/,$(apidoc_sources:.markdown=.json))

common.gypi

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@
231231
'libraries': [ '-llog' ],
232232
}],
233233
['OS=="mac"', {
234-
'defines': ['_DARWIN_USE_64_BIT_INODE=1'],
234+
'defines': ['_DARWIN_USE_64_BIT_INODE=1', 'NODE_OS_MACOSX'],
235235
'xcode_settings': {
236236
'ALWAYS_SEARCH_USER_PATHS': 'NO',
237237
'GCC_CW_ASM_SYNTAX': 'NO', # No -fasm-blocks
@@ -242,7 +242,7 @@
242242
'GCC_ENABLE_PASCAL_STRINGS': 'NO', # No -mpascal-strings
243243
'GCC_THREADSAFE_STATICS': 'NO', # -fno-threadsafe-statics
244244
'PREBINDING': 'NO', # No -Wl,-prebind
245-
'MACOSX_DEPLOYMENT_TARGET': '10.5', # -mmacosx-version-min=10.5
245+
'MACOSX_DEPLOYMENT_TARGET': '10.7', # -mmacosx-version-min=10.7
246246
'USE_HEADERMAP': 'NO',
247247
'OTHER_CFLAGS': [
248248
'-fno-strict-aliasing',
@@ -269,7 +269,8 @@
269269
['clang==1', {
270270
'xcode_settings': {
271271
'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
272-
'CLANG_CXX_LANGUAGE_STANDARD': 'gnu++0x', # -std=gnu++0x
272+
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', # -std=c++11
273+
'CLANG_CXX_LIBRARY': 'libc++', #-stdlib=libc++
273274
},
274275
}],
275276
],

lib/worker.js

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
'use strict';
2+
3+
if (!process.features.experimental_workers) {
4+
throw new Error('Experimental workers are disabled');
5+
}
6+
7+
const util = require('util');
8+
const assert = require('assert');
9+
const EventEmitter = require('events');
10+
const WorkerContextBinding = process.binding('WorkerContext');
11+
const JSONStringify = function(value) {
12+
if (value === undefined) value = null;
13+
return JSON.stringify(value);
14+
};
15+
const JSONParse = JSON.parse;
16+
const EMPTY_ARRAY = [];
17+
18+
const workerContextSymbol = Symbol('workerContext');
19+
const installEventsSymbol = Symbol('installEvents');
20+
const checkAliveSymbol = Symbol('checkAlive');
21+
const initSymbol = WorkerContextBinding.initSymbol;
22+
23+
const builtinErrorTypes = new Map([
24+
Error, SyntaxError, RangeError, URIError, TypeError, EvalError, ReferenceError
25+
].map(function(Type) {
26+
return [Type.name, Type];
27+
}));
28+
29+
const Worker = WorkerContextBinding.JsConstructor;
30+
util.inherits(Worker, EventEmitter);
31+
32+
Worker.prototype[initSymbol] = function(entryModulePath, options) {
33+
if (typeof entryModulePath !== 'string')
34+
throw new TypeError('entryModulePath must be a string');
35+
EventEmitter.call(this);
36+
options = Object(options);
37+
var keepAlive = options.keepAlive === undefined ? true : !!options.keepAlive;
38+
this[workerContextSymbol] =
39+
new WorkerContextBinding.WorkerContext(entryModulePath,
40+
{keepAlive: keepAlive});
41+
this[installEventsSymbol]();
42+
};
43+
44+
Worker.prototype[installEventsSymbol] = function() {
45+
const workerObject = this;
46+
const workerContext = this[workerContextSymbol];
47+
48+
const onerror = function(payload) {
49+
var ErrorConstructor = builtinErrorTypes.get(payload.builtinType);
50+
if (typeof ErrorConstructor !== 'function')
51+
ErrorConstructor = Error;
52+
const error = new ErrorConstructor(payload.message);
53+
error.stack = payload.stack;
54+
util._extend(error, payload.additionalProperties);
55+
workerObject.emit('error', error);
56+
};
57+
58+
workerContext._onexit = function(exitCode) {
59+
workerObject[workerContextSymbol] = null;
60+
workerObject.emit('exit', exitCode);
61+
};
62+
63+
workerContext._onmessage = function(payload, messageType) {
64+
payload = JSONParse(payload);
65+
switch (messageType) {
66+
case WorkerContextBinding.USER:
67+
return workerObject.emit('message', payload);
68+
case WorkerContextBinding.INTERNAL:
69+
assert.fail('unreachable');
70+
case WorkerContextBinding.EXCEPTION:
71+
return onerror(payload);
72+
default:
73+
assert.fail('unreachable');
74+
}
75+
};
76+
};
77+
78+
Worker.prototype[checkAliveSymbol] = function() {
79+
if (!this[workerContextSymbol])
80+
throw new RangeError('this worker has been terminated');
81+
};
82+
83+
Worker.prototype.postMessage = function(payload) {
84+
this[checkAliveSymbol]();
85+
this[workerContextSymbol].postMessage(JSONStringify(payload),
86+
EMPTY_ARRAY,
87+
WorkerContextBinding.USER);
88+
};
89+
90+
Worker.prototype.terminate = function(callback) {
91+
this[checkAliveSymbol]();
92+
var context = this[workerContextSymbol];
93+
this[workerContextSymbol] = null;
94+
if (typeof callback === 'function') {
95+
this.once('exit', function(exitCode) {
96+
callback(null, exitCode);
97+
});
98+
}
99+
context.terminate();
100+
};
101+
102+
Worker.prototype.ref = function() {
103+
this[checkAliveSymbol]();
104+
this[workerContextSymbol].ref();
105+
};
106+
107+
Worker.prototype.unref = function() {
108+
this[checkAliveSymbol]();
109+
this[workerContextSymbol].unref();
110+
};
111+
112+
if (process.isWorkerInstance) {
113+
const postMessage = function(payload, transferList, type) {
114+
if (!Array.isArray(transferList))
115+
throw new Error('transferList must be an array');
116+
117+
WorkerContextBinding.workerWrapper._postMessage(JSONStringify(payload),
118+
transferList,
119+
type);
120+
};
121+
const workerFatalError = function(er) {
122+
const defaultStack = null;
123+
const defaultMessage = '[toString() conversion failed]';
124+
const defaultBuiltinType = 'Error';
125+
126+
var message = defaultMessage;
127+
var builtinType = defaultBuiltinType;
128+
var stack = defaultStack;
129+
var additionalProperties = {};
130+
131+
if (er instanceof Error) {
132+
try {
133+
builtinType = er.name;
134+
} catch (ignore) {}
135+
136+
if (typeof builtinType !== 'string')
137+
builtinType = defaultBuiltinType;
138+
139+
try {
140+
stack = er.stack;
141+
} catch (ignore) {}
142+
143+
if (typeof stack !== 'string')
144+
stack = defaultStack;
145+
146+
try {
147+
// Get inherited enumerable properties.
148+
// .name, .stack and .message are all non-enumerable
149+
for (var key in er)
150+
additionalProperties[key] = er[key];
151+
// The message delivery must always succeed, otherwise the real cause
152+
// of this fatal error is masked.
153+
JSONStringify(additionalProperties);
154+
} catch (e) {
155+
additionalProperties = {};
156+
}
157+
}
158+
159+
try {
160+
if (er instanceof Error) {
161+
message = er.message;
162+
if (typeof message !== 'string')
163+
message = '' + er;
164+
} else {
165+
message = '' + er;
166+
}
167+
} catch (e) {
168+
message = defaultMessage;
169+
}
170+
171+
postMessage({
172+
message: message,
173+
stack: stack,
174+
additionalProperties: additionalProperties,
175+
builtinType: builtinType
176+
}, EMPTY_ARRAY, WorkerContextBinding.EXCEPTION);
177+
};
178+
179+
util._extend(Worker, EventEmitter.prototype);
180+
EventEmitter.call(Worker);
181+
182+
WorkerContextBinding.workerWrapper._onmessage =
183+
function(payload, messageType) {
184+
payload = JSONParse(payload);
185+
switch (messageType) {
186+
case WorkerContextBinding.USER:
187+
return Worker.emit('message', payload);
188+
case WorkerContextBinding.INTERNAL:
189+
assert.fail('unreachable');
190+
case WorkerContextBinding.EXCEPTION:
191+
assert.fail('unreachable');
192+
default:
193+
assert.fail('unreachable');
194+
}
195+
};
196+
197+
Worker.postMessage = function(payload) {
198+
postMessage(payload, EMPTY_ARRAY, WorkerContextBinding.USER);
199+
};
200+
201+
Object.defineProperty(Worker, '_workerFatalError', {
202+
configurable: true,
203+
writable: false,
204+
enumerable: false,
205+
value: workerFatalError
206+
});
207+
}
208+
209+
210+
module.exports = Worker;

node.gyp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@
6969
'lib/v8.js',
7070
'lib/vm.js',
7171
'lib/zlib.js',
72-
7372
'lib/internal/freelist.js',
73+
'lib/worker.js'
7474
],
7575
},
7676

@@ -115,6 +115,8 @@
115115
'src/node_watchdog.cc',
116116
'src/node_zlib.cc',
117117
'src/node_i18n.cc',
118+
'src/notification-channel.cc',
119+
'src/persistent-handle-cleanup.cc',
118120
'src/pipe_wrap.cc',
119121
'src/signal_wrap.cc',
120122
'src/smalloc.cc',
@@ -128,6 +130,7 @@
128130
'src/process_wrap.cc',
129131
'src/udp_wrap.cc',
130132
'src/uv.cc',
133+
'src/worker.cc',
131134
# headers to make for a more pleasant IDE experience
132135
'src/async-wrap.h',
133136
'src/async-wrap-inl.h',
@@ -141,6 +144,7 @@
141144
'src/node.h',
142145
'src/node_buffer.h',
143146
'src/node_constants.h',
147+
'src/node-contextify.h',
144148
'src/node_file.h',
145149
'src/node_http_parser.h',
146150
'src/node_internals.h',
@@ -150,7 +154,10 @@
150154
'src/node_watchdog.h',
151155
'src/node_wrap.h',
152156
'src/node_i18n.h',
157+
'src/notification-channel.h',
158+
'src/persistent-handle-cleanup.h',
153159
'src/pipe_wrap.h',
160+
'src/producer-consumer-queue.h',
154161
'src/smalloc.h',
155162
'src/tty_wrap.h',
156163
'src/tcp_wrap.h',
@@ -165,6 +172,7 @@
165172
'src/util.h',
166173
'src/util-inl.h',
167174
'src/util.cc',
175+
'src/worker.h',
168176
'deps/http_parser/http_parser.h',
169177
'deps/v8/include/v8.h',
170178
'deps/v8/include/v8-debug.h',
@@ -183,6 +191,12 @@
183191
'NODE_WANT_INTERNALS=1',
184192
],
185193

194+
'xcode_settings': {
195+
'OTHER_LDFLAGS': [
196+
'-stdlib=libc++',
197+
],
198+
},
199+
186200
'conditions': [
187201
# No node_main.cc for anything except executable
188202
[ 'node_target_type!="executable"', {
@@ -625,10 +639,11 @@
625639
{
626640
'target_name': 'cctest',
627641
'type': 'executable',
628-
'dependencies': [
642+
'dependencies': [
629643
'deps/gtest/gtest.gyp:gtest',
630644
'deps/v8/tools/gyp/v8.gyp:v8',
631-
'deps/v8/tools/gyp/v8.gyp:v8_libplatform'
645+
'deps/v8/tools/gyp/v8.gyp:v8_libplatform',
646+
'deps/uv/uv.gyp:libuv',
632647
],
633648
'include_dirs': [
634649
'src',

0 commit comments

Comments
 (0)