Skip to content

Commit

Permalink
worker: allow passing JS wrapper objects via postMessage
Browse files Browse the repository at this point in the history
Enable JS wrapper objects to be used as transferable or cloneable
objects in `postMessage()` calls, by having them extend a C++-backed
class.

This requires a few internal changes:
- This commit adds the possibility for transferred objects to
  read/write JS values at the end of the serialization/deserialization
  phases.
- This commit adds the possibility for transferred objects to list
  sub-transferables, e.g. typically the public JS wrapper class
  would list its C++ handle in there.
- This commit adds usage of `BaseObject` in a few more places, because
  now during deserialization weakly held objects can also be involved,
  in addition to `MessagePort`s.

PR-URL: #33772
Backport-PR-URL: #33965
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
  • Loading branch information
addaleax authored and codebytere committed Jun 30, 2020
1 parent 8be7c37 commit c28726b
Show file tree
Hide file tree
Showing 10 changed files with 384 additions and 33 deletions.
1 change: 1 addition & 0 deletions lib/internal/bootstrap/node.js
Expand Up @@ -58,6 +58,7 @@ process._exiting = false;

// process.config is serialized config.gypi
process.config = JSONParse(internalBinding('native_module').config);
require('internal/worker/js_transferable').setup();

// Bootstrappers for all threads, including worker threads and main thread
const perThreadSetup = require('internal/process/per_thread');
Expand Down
31 changes: 31 additions & 0 deletions lib/internal/worker/js_transferable.js
@@ -0,0 +1,31 @@
'use strict';
const {
messaging_deserialize_symbol,
messaging_transfer_symbol,
messaging_clone_symbol,
messaging_transfer_list_symbol
} = internalBinding('symbols');
const {
JSTransferable,
setDeserializerCreateObjectFunction
} = internalBinding('messaging');

function setup() {
// Register the handler that will be used when deserializing JS-based objects
// from .postMessage() calls. The format of `deserializeInfo` is generally
// 'module:Constructor', e.g. 'internal/fs/promises:FileHandle'.
setDeserializerCreateObjectFunction((deserializeInfo) => {
const [ module, ctor ] = deserializeInfo.split(':');
const Ctor = require(module)[ctor];
return new Ctor();
});
}

module.exports = {
setup,
JSTransferable,
kClone: messaging_clone_symbol,
kDeserialize: messaging_deserialize_symbol,
kTransfer: messaging_transfer_symbol,
kTransferList: messaging_transfer_list_symbol
};
1 change: 1 addition & 0 deletions node.gyp
Expand Up @@ -218,6 +218,7 @@
'lib/internal/vm/module.js',
'lib/internal/worker.js',
'lib/internal/worker/io.js',
'lib/internal/worker/js_transferable.js',
'lib/internal/watchdog.js',
'lib/internal/streams/lazy_transform.js',
'lib/internal/streams/async_iterator.js',
Expand Down
9 changes: 9 additions & 0 deletions src/base_object.h
Expand Up @@ -123,12 +123,17 @@ class BaseObject : public MemoryRetainer {
// make sure that they are not accidentally destroyed on the sending side.
// TransferForMessaging() will be called to get a representation of the
// object that is used for subsequent deserialization.
// The NestedTransferables() method can be used to transfer other objects
// along with this one, if a situation requires it.
// - kCloneable:
// This object can be cloned without being modified.
// CloneForMessaging() will be called to get a representation of the
// object that is used for subsequent deserialization, unless the
// object is listed in transferList, in which case TransferForMessaging()
// is attempted first.
// After a successful clone, FinalizeTransferRead() is called on the receiving
// end, and can read deserialize JS data possibly serialized by a previous
// FinalizeTransferWrite() call.
enum class TransferMode {
kUntransferable,
kTransferable,
Expand All @@ -137,6 +142,10 @@ class BaseObject : public MemoryRetainer {
virtual TransferMode GetTransferMode() const;
virtual std::unique_ptr<worker::TransferData> TransferForMessaging();
virtual std::unique_ptr<worker::TransferData> CloneForMessaging() const;
virtual v8::Maybe<std::vector<BaseObjectPtrImpl<BaseObject, false>>>
NestedTransferables() const;
virtual v8::Maybe<bool> FinalizeTransferRead(
v8::Local<v8::Context> context, v8::ValueDeserializer* deserializer);

virtual inline void OnGCCollect();

Expand Down
6 changes: 6 additions & 0 deletions src/env.h
Expand Up @@ -159,6 +159,10 @@ constexpr size_t kFsStatsBufferLength =
V(async_id_symbol, "async_id_symbol") \
V(handle_onclose_symbol, "handle_onclose") \
V(no_message_symbol, "no_message_symbol") \
V(messaging_deserialize_symbol, "messaging_deserialize_symbol") \
V(messaging_transfer_symbol, "messaging_transfer_symbol") \
V(messaging_clone_symbol, "messaging_clone_symbol") \
V(messaging_transfer_list_symbol, "messaging_transfer_list_symbol") \
V(oninit_symbol, "oninit") \
V(owner_symbol, "owner_symbol") \
V(onpskexchange_symbol, "onpskexchange") \
Expand Down Expand Up @@ -201,6 +205,7 @@ constexpr size_t kFsStatsBufferLength =
V(crypto_rsa_pss_string, "rsa-pss") \
V(cwd_string, "cwd") \
V(data_string, "data") \
V(deserialize_info_string, "deserializeInfo") \
V(dest_string, "dest") \
V(destroyed_string, "destroyed") \
V(detached_string, "detached") \
Expand Down Expand Up @@ -454,6 +459,7 @@ constexpr size_t kFsStatsBufferLength =
V(internal_binding_loader, v8::Function) \
V(immediate_callback_function, v8::Function) \
V(inspector_console_extension_installer, v8::Function) \
V(messaging_deserialize_create_object, v8::Function) \
V(message_port, v8::Object) \
V(native_module_require, v8::Function) \
V(performance_entry_callback, v8::Function) \
Expand Down
3 changes: 2 additions & 1 deletion src/node_errors.h
Expand Up @@ -93,7 +93,8 @@ void OnFatalError(const char* location, const char* message);
V(ERR_MEMORY_ALLOCATION_FAILED, "Failed to allocate memory") \
V(ERR_OSSL_EVP_INVALID_DIGEST, "Invalid digest used") \
V(ERR_MISSING_MESSAGE_PORT_IN_TRANSFER_LIST, \
"MessagePort was found in message but not listed in transferList") \
"Object that needs transfer was found in message but not listed " \
"in transferList") \
V(ERR_MISSING_PLATFORM_FOR_WORKER, \
"The V8 platform used by this instance of Node does not support " \
"creating Workers") \
Expand Down

0 comments on commit c28726b

Please sign in to comment.