diff --git a/doc/api/all.md b/doc/api/all.md
index d013f07bd328fc..6f0a21dd092105 100644
--- a/doc/api/all.md
+++ b/doc/api/all.md
@@ -46,4 +46,5 @@
@include util
@include v8
@include vm
+@include worker
@include zlib
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 0c3c5fba7d0e49..161e5eb9564614 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -650,12 +650,23 @@ Used when a child process is being forked without specifying an IPC channel.
Used when the main process is trying to read data from the child process's
STDERR / STDOUT, and the data's length is longer than the `maxBuffer` option.
+
+### ERR_CLOSED_MESSAGE_PORT
+
+There was an attempt to use a `MessagePort` instance in a closed
+state, usually after `.close()` has been called.
+
### ERR_CONSOLE_WRITABLE_STREAM
`Console` was instantiated without `stdout` stream, or `Console` has a
non-writable `stdout` or `stderr` stream.
+
+### ERR_CONSTRUCT_CALL_REQUIRED
+
+A constructor for a class was called without `new`.
+
### ERR_CPU_USAGE
@@ -1213,6 +1224,11 @@ urlSearchParams.has.call(buf, 'foo');
// Throws a TypeError with code 'ERR_INVALID_THIS'
```
+
+### ERR_INVALID_TRANSFER_OBJECT
+
+An invalid transfer object was passed to `postMessage()`.
+
### ERR_INVALID_TUPLE
diff --git a/doc/api/worker.md b/doc/api/worker.md
new file mode 100644
index 00000000000000..4724714cd62f26
--- /dev/null
+++ b/doc/api/worker.md
@@ -0,0 +1,146 @@
+# Worker
+
+
+
+> Stability: 1 - Experimental
+
+## Class: MessageChannel
+
+
+Instances of the `worker.MessageChannel` class represent an asynchronous,
+two-way communications channel.
+The `MessageChannel` has no methods of its own. `new MessageChannel()`
+yields an object with `port1` and `port2` properties, which refer to linked
+[`MessagePort`][] instances.
+
+```js
+const { MessageChannel } = require('worker');
+
+const { port1, port2 } = new MessageChannel();
+port1.on('message', (message) => console.log('received', message));
+port2.postMessage({ foo: 'bar' });
+// prints: received { foo: 'bar' }
+```
+
+## Class: MessagePort
+
+
+* Extends: {EventEmitter}
+
+Instances of the `worker.MessagePort` class represent one end of an
+asynchronous, two-way communications channel. It can be used to transfer
+structured data, memory regions and other `MessagePort`s between different
+[`Worker`][]s.
+
+With the exception of `MessagePort`s being [`EventEmitter`][]s rather
+than `EventTarget`s, this implementation matches [browser `MessagePort`][]s.
+
+### Event: 'close'
+
+
+The `'close'` event is emitted once either side of the channel has been
+disconnected.
+
+### Event: 'message'
+
+
+* `value` {any} The transmitted value
+
+The `'message'` event is emitted for any incoming message, containing the cloned
+input of [`port.postMessage()`][].
+
+Listeners on this event will receive a clone of the `value` parameter as passed
+to `postMessage()` and no further arguments.
+
+### port.close()
+
+
+Disables further sending of messages on either side of the connection.
+This method can be called once you know that no further communication
+will happen over this `MessagePort`.
+
+### port.postMessage(value[, transferList])
+
+
+* `value` {any}
+* `transferList` {Object[]}
+
+Sends a JavaScript value to the receiving side of this channel.
+`value` will be transferred in a way which is compatible with
+the [HTML structured clone algorithm][]. In particular, it may contain circular
+references and objects like typed arrays that the `JSON` API is not able
+to stringify.
+
+`transferList` may be a list of `ArrayBuffer` objects.
+After transferring, they will not be usable on the sending side of the channel
+anymore (even if they are not contained in `value`).
+
+`value` may still contain `ArrayBuffer` instances that are not in
+`transferList`; in that case, the underlying memory is copied rather than moved.
+
+For more information on the serialization and deserialization mechanisms
+behind this API, see the [serialization API of the `v8` module][v8.serdes].
+
+Because the object cloning uses the structured clone algorithm,
+non-enumerable properties, property accessors, and object prototypes are
+not preserved. In particular, [`Buffer`][] objects will be read as
+plain [`Uint8Array`][]s on the receiving side.
+
+The message object will be cloned immediately, and can be modified after
+posting without having side effects.
+
+### port.ref()
+
+
+Opposite of `unref()`. Calling `ref()` on a previously `unref()`ed port will
+*not* let the program exit if it's the only active handle left (the default
+behavior). If the port is `ref()`ed, calling `ref()` again will have no effect.
+
+If listeners are attached or removed using `.on('message')`, the port will
+be `ref()`ed and `unref()`ed automatically depending on whether
+listeners for the event exist.
+
+### port.start()
+
+
+Starts receiving messages on this `MessagePort`. When using this port
+as an event emitter, this will be called automatically once `'message'`
+listeners are attached.
+
+### port.unref()
+
+
+Calling `unref()` on a port will allow the thread to exit if this is the only
+active handle in the event system. If the port is already `unref()`ed calling
+`unref()` again will have no effect.
+
+If listeners are attached or removed using `.on('message')`, the port will
+be `ref()`ed and `unref()`ed automatically depending on whether
+listeners for the event exist.
+
+[`Buffer`]: buffer.html
+[`EventEmitter`]: events.html
+[`MessagePort`]: #worker_class_messageport
+[`port.postMessage()`]: #worker_port_postmessage_value_transferlist
+[v8.serdes]: v8.html#v8_serialization_api
+[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
+[browser `MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
+[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
diff --git a/lib/internal/bootstrap/loaders.js b/lib/internal/bootstrap/loaders.js
index ff809a91291bee..417e8594e14aab 100644
--- a/lib/internal/bootstrap/loaders.js
+++ b/lib/internal/bootstrap/loaders.js
@@ -194,7 +194,8 @@
};
NativeModule.isInternal = function(id) {
- return id.startsWith('internal/');
+ return id.startsWith('internal/') ||
+ (id === 'worker' && !process.binding('config').experimentalWorker);
};
}
diff --git a/lib/internal/modules/cjs/helpers.js b/lib/internal/modules/cjs/helpers.js
index 60346c5841c7df..55eaed7d376506 100644
--- a/lib/internal/modules/cjs/helpers.js
+++ b/lib/internal/modules/cjs/helpers.js
@@ -105,6 +105,11 @@ const builtinLibs = [
'v8', 'vm', 'zlib'
];
+if (process.binding('config').experimentalWorker) {
+ builtinLibs.push('worker');
+ builtinLibs.sort();
+}
+
if (typeof process.binding('inspector').open === 'function') {
builtinLibs.push('inspector');
builtinLibs.sort();
diff --git a/lib/internal/worker.js b/lib/internal/worker.js
new file mode 100644
index 00000000000000..73f7525aa73cc2
--- /dev/null
+++ b/lib/internal/worker.js
@@ -0,0 +1,105 @@
+'use strict';
+
+const EventEmitter = require('events');
+const util = require('util');
+
+const { internalBinding } = require('internal/bootstrap/loaders');
+const { MessagePort, MessageChannel } = internalBinding('messaging');
+const { handle_onclose } = internalBinding('symbols');
+
+util.inherits(MessagePort, EventEmitter);
+
+const kOnMessageListener = Symbol('kOnMessageListener');
+
+const debug = util.debuglog('worker');
+
+// A MessagePort consists of a handle (that wraps around an
+// uv_async_t) which can receive information from other threads and emits
+// .onmessage events, and a function used for sending data to a MessagePort
+// in some other thread.
+MessagePort.prototype[kOnMessageListener] = function onmessage(payload) {
+ debug('received message', payload);
+ // Emit the deserialized object to userland.
+ this.emit('message', payload);
+};
+
+// This is for compatibility with the Web's MessagePort API. It makes sense to
+// provide it as an `EventEmitter` in Node.js, but if somebody overrides
+// `onmessage`, we'll switch over to the Web API model.
+Object.defineProperty(MessagePort.prototype, 'onmessage', {
+ enumerable: true,
+ configurable: true,
+ get() {
+ return this[kOnMessageListener];
+ },
+ set(value) {
+ this[kOnMessageListener] = value;
+ if (typeof value === 'function') {
+ this.ref();
+ this.start();
+ } else {
+ this.unref();
+ this.stop();
+ }
+ }
+});
+
+// This is called from inside the `MessagePort` constructor.
+function oninit() {
+ setupPortReferencing(this, this, 'message');
+}
+
+Object.defineProperty(MessagePort.prototype, 'oninit', {
+ enumerable: true,
+ writable: false,
+ value: oninit
+});
+
+// This is called after the underlying `uv_async_t` has been closed.
+function onclose() {
+ if (typeof this.onclose === 'function') {
+ // Not part of the Web standard yet, but there aren't many reasonable
+ // alternatives in a non-EventEmitter usage setting.
+ // Refs: https://github.com/whatwg/html/issues/1766
+ this.onclose();
+ }
+ this.emit('close');
+}
+
+Object.defineProperty(MessagePort.prototype, handle_onclose, {
+ enumerable: false,
+ writable: false,
+ value: onclose
+});
+
+const originalClose = MessagePort.prototype.close;
+MessagePort.prototype.close = function(cb) {
+ if (typeof cb === 'function')
+ this.once('close', cb);
+ originalClose.call(this);
+};
+
+function setupPortReferencing(port, eventEmitter, eventName) {
+ // Keep track of whether there are any workerMessage listeners:
+ // If there are some, ref() the channel so it keeps the event loop alive.
+ // If there are none or all are removed, unref() the channel so the worker
+ // can shutdown gracefully.
+ port.unref();
+ eventEmitter.on('newListener', (name) => {
+ if (name === eventName && eventEmitter.listenerCount(eventName) === 0) {
+ port.ref();
+ port.start();
+ }
+ });
+ eventEmitter.on('removeListener', (name) => {
+ if (name === eventName && eventEmitter.listenerCount(eventName) === 0) {
+ port.stop();
+ port.unref();
+ }
+ });
+}
+
+module.exports = {
+ MessagePort,
+ MessageChannel
+};
diff --git a/lib/worker.js b/lib/worker.js
new file mode 100644
index 00000000000000..d67fb4efe40a33
--- /dev/null
+++ b/lib/worker.js
@@ -0,0 +1,5 @@
+'use strict';
+
+const { MessagePort, MessageChannel } = require('internal/worker');
+
+module.exports = { MessagePort, MessageChannel };
diff --git a/node.gyp b/node.gyp
index 709ce226033af0..72a5d05c43d035 100644
--- a/node.gyp
+++ b/node.gyp
@@ -78,6 +78,7 @@
'lib/util.js',
'lib/v8.js',
'lib/vm.js',
+ 'lib/worker.js',
'lib/zlib.js',
'lib/internal/assert.js',
'lib/internal/async_hooks.js',
@@ -155,6 +156,7 @@
'lib/internal/validators.js',
'lib/internal/stream_base_commons.js',
'lib/internal/vm/module.js',
+ 'lib/internal/worker.js',
'lib/internal/streams/lazy_transform.js',
'lib/internal/streams/async_iterator.js',
'lib/internal/streams/buffer_list.js',
@@ -333,6 +335,7 @@
'src/node_file.cc',
'src/node_http2.cc',
'src/node_http_parser.cc',
+ 'src/node_messaging.cc',
'src/node_os.cc',
'src/node_platform.cc',
'src/node_perf.cc',
@@ -390,6 +393,7 @@
'src/node_http2_state.h',
'src/node_internals.h',
'src/node_javascript.h',
+ 'src/node_messaging.h',
'src/node_mutex.h',
'src/node_perf.h',
'src/node_perf_common.h',
diff --git a/src/async_wrap.h b/src/async_wrap.h
index 377702a8d6ef9c..cf269a4c1f5e1e 100644
--- a/src/async_wrap.h
+++ b/src/async_wrap.h
@@ -49,6 +49,7 @@ namespace node {
V(HTTP2SETTINGS) \
V(HTTPPARSER) \
V(JSSTREAM) \
+ V(MESSAGEPORT) \
V(PIPECONNECTWRAP) \
V(PIPESERVERWRAP) \
V(PIPEWRAP) \
diff --git a/src/env.h b/src/env.h
index 7a432eaa3d4ff0..d87c39c5186bd7 100644
--- a/src/env.h
+++ b/src/env.h
@@ -193,6 +193,7 @@ struct PackageConfig {
V(main_string, "main") \
V(max_buffer_string, "maxBuffer") \
V(message_string, "message") \
+ V(message_port_constructor_string, "MessagePort") \
V(minttl_string, "minttl") \
V(modulus_string, "modulus") \
V(name_string, "name") \
@@ -212,6 +213,7 @@ struct PackageConfig {
V(onhandshakedone_string, "onhandshakedone") \
V(onhandshakestart_string, "onhandshakestart") \
V(onheaders_string, "onheaders") \
+ V(oninit_string, "oninit") \
V(onmessage_string, "onmessage") \
V(onnewsession_string, "onnewsession") \
V(onocspresponse_string, "onocspresponse") \
@@ -242,6 +244,8 @@ struct PackageConfig {
V(pipe_target_string, "pipeTarget") \
V(pipe_source_string, "pipeSource") \
V(port_string, "port") \
+ V(port1_string, "port1") \
+ V(port2_string, "port2") \
V(preference_string, "preference") \
V(priority_string, "priority") \
V(promise_string, "promise") \
@@ -323,6 +327,7 @@ struct PackageConfig {
V(http2stream_constructor_template, v8::ObjectTemplate) \
V(immediate_callback_function, v8::Function) \
V(inspector_console_api_object, v8::Object) \
+ V(message_port_constructor_template, v8::FunctionTemplate) \
V(pbkdf2_constructor_template, v8::ObjectTemplate) \
V(pipe_constructor_template, v8::FunctionTemplate) \
V(performance_entry_callback, v8::Function) \
diff --git a/src/node.cc b/src/node.cc
index 4973fea636afd5..281d441a26a86c 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -248,6 +248,11 @@ bool config_experimental_modules = false;
// that is used by lib/vm.js
bool config_experimental_vm_modules = false;
+// Set in node.cc by ParseArgs when --experimental-worker is used.
+// Used in node_config.cc to set a constant on process.binding('config')
+// that is used by lib/worker.js
+bool config_experimental_worker = false;
+
// Set in node.cc by ParseArgs when --experimental-repl-await is used.
// Used in node_config.cc to set a constant on process.binding('config')
// that is used by lib/repl.js.
@@ -3104,6 +3109,7 @@ static void PrintHelp() {
" --experimental-vm-modules experimental ES Module support\n"
" in vm module\n"
#endif // defined(NODE_HAVE_I18N_SUPPORT)
+ " --experimental-worker experimental threaded Worker support\n"
#if HAVE_OPENSSL && NODE_FIPS_MODE
" --force-fips force FIPS crypto (cannot be disabled)\n"
#endif // HAVE_OPENSSL && NODE_FIPS_MODE
@@ -3267,6 +3273,7 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
"--experimental-modules",
"--experimental-repl-await",
"--experimental-vm-modules",
+ "--experimental-worker",
"--expose-http2", // keep as a non-op through v9.x
"--force-fips",
"--icu-data-dir",
@@ -3465,6 +3472,8 @@ static void ParseArgs(int* argc,
new_v8_argc += 1;
} else if (strcmp(arg, "--experimental-vm-modules") == 0) {
config_experimental_vm_modules = true;
+ } else if (strcmp(arg, "--experimental-worker") == 0) {
+ config_experimental_worker = true;
} else if (strcmp(arg, "--experimental-repl-await") == 0) {
config_experimental_repl_await = true;
} else if (strcmp(arg, "--loader") == 0) {
diff --git a/src/node_config.cc b/src/node_config.cc
index 603d55491a259b..dd5ee666486874 100644
--- a/src/node_config.cc
+++ b/src/node_config.cc
@@ -91,6 +91,9 @@ static void Initialize(Local