Anna Henningsen | e7a2367 | 2017-09-05 20:38:32 | [diff] [blame^] | 1 | #include "node_messaging.h" |
| 2 | #include "node_internals.h" |
| 3 | #include "node_buffer.h" |
| 4 | #include "node_errors.h" |
| 5 | #include "util.h" |
| 6 | #include "util-inl.h" |
| 7 | #include "async_wrap.h" |
| 8 | #include "async_wrap-inl.h" |
| 9 | |
| 10 | using v8::Array; |
| 11 | using v8::ArrayBuffer; |
| 12 | using v8::ArrayBufferCreationMode; |
| 13 | using v8::Context; |
| 14 | using v8::EscapableHandleScope; |
| 15 | using v8::Exception; |
| 16 | using v8::Function; |
| 17 | using v8::FunctionCallbackInfo; |
| 18 | using v8::FunctionTemplate; |
| 19 | using v8::HandleScope; |
| 20 | using v8::Isolate; |
| 21 | using v8::Just; |
| 22 | using v8::Local; |
| 23 | using v8::Maybe; |
| 24 | using v8::MaybeLocal; |
| 25 | using v8::Nothing; |
| 26 | using v8::Object; |
| 27 | using v8::String; |
| 28 | using v8::Value; |
| 29 | using v8::ValueDeserializer; |
| 30 | using v8::ValueSerializer; |
| 31 | |
| 32 | namespace node { |
| 33 | namespace worker { |
| 34 | |
| 35 | Message::Message(MallocedBuffer<char>&& buffer) |
| 36 | : main_message_buf_(std::move(buffer)) {} |
| 37 | |
| 38 | namespace { |
| 39 | |
| 40 | // This is used to tell V8 how to read transferred host objects, like other |
| 41 | // `MessagePort`s and `SharedArrayBuffer`s, and make new JS objects out of them. |
| 42 | class DeserializerDelegate : public ValueDeserializer::Delegate { |
| 43 | public: |
| 44 | DeserializerDelegate(Message* m, Environment* env) |
| 45 | : env_(env), msg_(m) {} |
| 46 | |
| 47 | ValueDeserializer* deserializer = nullptr; |
| 48 | |
| 49 | private: |
| 50 | Environment* env_; |
| 51 | Message* msg_; |
| 52 | }; |
| 53 | |
| 54 | } // anonymous namespace |
| 55 | |
| 56 | MaybeLocal<Value> Message::Deserialize(Environment* env, |
| 57 | Local<Context> context) { |
| 58 | EscapableHandleScope handle_scope(env->isolate()); |
| 59 | Context::Scope context_scope(context); |
| 60 | |
| 61 | DeserializerDelegate delegate(this, env); |
| 62 | ValueDeserializer deserializer( |
| 63 | env->isolate(), |
| 64 | reinterpret_cast<const uint8_t*>(main_message_buf_.data), |
| 65 | main_message_buf_.size, |
| 66 | &delegate); |
| 67 | delegate.deserializer = &deserializer; |
| 68 | |
| 69 | // Attach all transfered ArrayBuffers to their new Isolate. |
| 70 | for (uint32_t i = 0; i < array_buffer_contents_.size(); ++i) { |
| 71 | Local<ArrayBuffer> ab = |
| 72 | ArrayBuffer::New(env->isolate(), |
| 73 | array_buffer_contents_[i].release(), |
| 74 | array_buffer_contents_[i].size, |
| 75 | ArrayBufferCreationMode::kInternalized); |
| 76 | deserializer.TransferArrayBuffer(i, ab); |
| 77 | } |
| 78 | array_buffer_contents_.clear(); |
| 79 | |
| 80 | if (deserializer.ReadHeader(context).IsNothing()) |
| 81 | return MaybeLocal<Value>(); |
| 82 | return handle_scope.Escape( |
| 83 | deserializer.ReadValue(context).FromMaybe(Local<Value>())); |
| 84 | } |
| 85 | |
| 86 | namespace { |
| 87 | |
| 88 | // This tells V8 how to serialize objects that it does not understand |
| 89 | // (e.g. C++ objects) into the output buffer, in a way that our own |
| 90 | // DeserializerDelegate understands how to unpack. |
| 91 | class SerializerDelegate : public ValueSerializer::Delegate { |
| 92 | public: |
| 93 | SerializerDelegate(Environment* env, Local<Context> context, Message* m) |
| 94 | : env_(env), context_(context), msg_(m) {} |
| 95 | |
| 96 | void ThrowDataCloneError(Local<String> message) override { |
| 97 | env_->isolate()->ThrowException(Exception::Error(message)); |
| 98 | } |
| 99 | |
| 100 | ValueSerializer* serializer = nullptr; |
| 101 | |
| 102 | private: |
| 103 | Environment* env_; |
| 104 | Local<Context> context_; |
| 105 | Message* msg_; |
| 106 | |
| 107 | friend class worker::Message; |
| 108 | }; |
| 109 | |
| 110 | } // anynomous namespace |
| 111 | |
| 112 | Maybe<bool> Message::Serialize(Environment* env, |
| 113 | Local<Context> context, |
| 114 | Local<Value> input, |
| 115 | Local<Value> transfer_list_v) { |
| 116 | HandleScope handle_scope(env->isolate()); |
| 117 | Context::Scope context_scope(context); |
| 118 | |
| 119 | // Verify that we're not silently overwriting an existing message. |
| 120 | CHECK(main_message_buf_.is_empty()); |
| 121 | |
| 122 | SerializerDelegate delegate(env, context, this); |
| 123 | ValueSerializer serializer(env->isolate(), &delegate); |
| 124 | delegate.serializer = &serializer; |
| 125 | |
| 126 | std::vector<Local<ArrayBuffer>> array_buffers; |
| 127 | if (transfer_list_v->IsArray()) { |
| 128 | Local<Array> transfer_list = transfer_list_v.As<Array>(); |
| 129 | uint32_t length = transfer_list->Length(); |
| 130 | for (uint32_t i = 0; i < length; ++i) { |
| 131 | Local<Value> entry; |
| 132 | if (!transfer_list->Get(context, i).ToLocal(&entry)) |
| 133 | return Nothing<bool>(); |
| 134 | // Currently, we support ArrayBuffers. |
| 135 | if (entry->IsArrayBuffer()) { |
| 136 | Local<ArrayBuffer> ab = entry.As<ArrayBuffer>(); |
| 137 | // If we cannot render the ArrayBuffer unusable in this Isolate and |
| 138 | // take ownership of its memory, copying the buffer will have to do. |
| 139 | if (!ab->IsNeuterable() || ab->IsExternal()) |
| 140 | continue; |
| 141 | // We simply use the array index in the `array_buffers` list as the |
| 142 | // ID that we write into the serialized buffer. |
| 143 | uint32_t id = array_buffers.size(); |
| 144 | array_buffers.push_back(ab); |
| 145 | serializer.TransferArrayBuffer(id, ab); |
| 146 | continue; |
| 147 | } |
| 148 | |
| 149 | THROW_ERR_INVALID_TRANSFER_OBJECT(env); |
| 150 | return Nothing<bool>(); |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | serializer.WriteHeader(); |
| 155 | if (serializer.WriteValue(context, input).IsNothing()) { |
| 156 | return Nothing<bool>(); |
| 157 | } |
| 158 | |
| 159 | for (Local<ArrayBuffer> ab : array_buffers) { |
| 160 | // If serialization succeeded, we want to take ownership of |
| 161 | // (a.k.a. externalize) the underlying memory region and render |
| 162 | // it inaccessible in this Isolate. |
| 163 | ArrayBuffer::Contents contents = ab->Externalize(); |
| 164 | ab->Neuter(); |
| 165 | array_buffer_contents_.push_back( |
| 166 | MallocedBuffer<char> { static_cast<char*>(contents.Data()), |
| 167 | contents.ByteLength() }); |
| 168 | } |
| 169 | |
| 170 | // The serializer gave us a buffer allocated using `malloc()`. |
| 171 | std::pair<uint8_t*, size_t> data = serializer.Release(); |
| 172 | main_message_buf_ = |
| 173 | MallocedBuffer<char>(reinterpret_cast<char*>(data.first), data.second); |
| 174 | return Just(true); |
| 175 | } |
| 176 | |
| 177 | MessagePortData::MessagePortData(MessagePort* owner) : owner_(owner) { } |
| 178 | |
| 179 | MessagePortData::~MessagePortData() { |
| 180 | CHECK_EQ(owner_, nullptr); |
| 181 | Disentangle(); |
| 182 | } |
| 183 | |
| 184 | void MessagePortData::AddToIncomingQueue(Message&& message) { |
| 185 | // This function will be called by other threads. |
| 186 | Mutex::ScopedLock lock(mutex_); |
| 187 | incoming_messages_.emplace_back(std::move(message)); |
| 188 | |
| 189 | if (owner_ != nullptr) |
| 190 | owner_->TriggerAsync(); |
| 191 | } |
| 192 | |
| 193 | bool MessagePortData::IsSiblingClosed() const { |
| 194 | Mutex::ScopedLock lock(*sibling_mutex_); |
| 195 | return sibling_ == nullptr; |
| 196 | } |
| 197 | |
| 198 | void MessagePortData::Entangle(MessagePortData* a, MessagePortData* b) { |
| 199 | CHECK_EQ(a->sibling_, nullptr); |
| 200 | CHECK_EQ(b->sibling_, nullptr); |
| 201 | a->sibling_ = b; |
| 202 | b->sibling_ = a; |
| 203 | a->sibling_mutex_ = b->sibling_mutex_; |
| 204 | } |
| 205 | |
| 206 | void MessagePortData::PingOwnerAfterDisentanglement() { |
| 207 | Mutex::ScopedLock lock(mutex_); |
| 208 | if (owner_ != nullptr) |
| 209 | owner_->TriggerAsync(); |
| 210 | } |
| 211 | |
| 212 | void MessagePortData::Disentangle() { |
| 213 | // Grab a copy of the sibling mutex, then replace it so that each sibling |
| 214 | // has its own sibling_mutex_ now. |
| 215 | std::shared_ptr<Mutex> sibling_mutex = sibling_mutex_; |
| 216 | Mutex::ScopedLock sibling_lock(*sibling_mutex); |
| 217 | sibling_mutex_ = std::make_shared<Mutex>(); |
| 218 | |
| 219 | MessagePortData* sibling = sibling_; |
| 220 | if (sibling_ != nullptr) { |
| 221 | sibling_->sibling_ = nullptr; |
| 222 | sibling_ = nullptr; |
| 223 | } |
| 224 | |
| 225 | // We close MessagePorts after disentanglement, so we trigger the |
| 226 | // corresponding uv_async_t to let them know that this happened. |
| 227 | PingOwnerAfterDisentanglement(); |
| 228 | if (sibling != nullptr) { |
| 229 | sibling->PingOwnerAfterDisentanglement(); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | MessagePort::~MessagePort() { |
| 234 | if (data_) |
| 235 | data_->owner_ = nullptr; |
| 236 | } |
| 237 | |
| 238 | MessagePort::MessagePort(Environment* env, |
| 239 | Local<Context> context, |
| 240 | Local<Object> wrap) |
| 241 | : HandleWrap(env, |
| 242 | wrap, |
| 243 | reinterpret_cast<uv_handle_t*>(new uv_async_t()), |
| 244 | AsyncWrap::PROVIDER_MESSAGEPORT), |
| 245 | data_(new MessagePortData(this)) { |
| 246 | auto onmessage = [](uv_async_t* handle) { |
| 247 | // Called when data has been put into the queue. |
| 248 | MessagePort* channel = static_cast<MessagePort*>(handle->data); |
| 249 | channel->OnMessage(); |
| 250 | }; |
| 251 | CHECK_EQ(uv_async_init(env->event_loop(), |
| 252 | async(), |
| 253 | onmessage), 0); |
| 254 | async()->data = static_cast<void*>(this); |
| 255 | |
| 256 | Local<Value> fn; |
| 257 | if (!wrap->Get(context, env->oninit_string()).ToLocal(&fn)) |
| 258 | return; |
| 259 | |
| 260 | if (fn->IsFunction()) { |
| 261 | Local<Function> init = fn.As<Function>(); |
| 262 | USE(init->Call(context, wrap, 0, nullptr)); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | void MessagePort::AddToIncomingQueue(Message&& message) { |
| 267 | data_->AddToIncomingQueue(std::move(message)); |
| 268 | } |
| 269 | |
| 270 | uv_async_t* MessagePort::async() { |
| 271 | return reinterpret_cast<uv_async_t*>(GetHandle()); |
| 272 | } |
| 273 | |
| 274 | void MessagePort::TriggerAsync() { |
| 275 | CHECK_EQ(uv_async_send(async()), 0); |
| 276 | } |
| 277 | |
| 278 | void MessagePort::New(const FunctionCallbackInfo<Value>& args) { |
| 279 | Environment* env = Environment::GetCurrent(args); |
| 280 | if (!args.IsConstructCall()) { |
| 281 | THROW_ERR_CONSTRUCT_CALL_REQUIRED(env); |
| 282 | return; |
| 283 | } |
| 284 | |
| 285 | Local<Context> context = args.This()->CreationContext(); |
| 286 | Context::Scope context_scope(context); |
| 287 | |
| 288 | new MessagePort(env, context, args.This()); |
| 289 | } |
| 290 | |
| 291 | MessagePort* MessagePort::New( |
| 292 | Environment* env, |
| 293 | Local<Context> context, |
| 294 | std::unique_ptr<MessagePortData> data) { |
| 295 | Context::Scope context_scope(context); |
| 296 | Local<Function> ctor; |
| 297 | if (!GetMessagePortConstructor(env, context).ToLocal(&ctor)) |
| 298 | return nullptr; |
| 299 | MessagePort* port = nullptr; |
| 300 | |
| 301 | // Construct a new instance, then assign the listener instance and possibly |
| 302 | // the MessagePortData to it. |
| 303 | Local<Object> instance; |
| 304 | if (!ctor->NewInstance(context).ToLocal(&instance)) |
| 305 | return nullptr; |
| 306 | ASSIGN_OR_RETURN_UNWRAP(&port, instance, nullptr); |
| 307 | if (data) { |
| 308 | port->Detach(); |
| 309 | port->data_ = std::move(data); |
| 310 | port->data_->owner_ = port; |
| 311 | // If the existing MessagePortData object had pending messages, this is |
| 312 | // the easiest way to run that queue. |
| 313 | port->TriggerAsync(); |
| 314 | } |
| 315 | return port; |
| 316 | } |
| 317 | |
| 318 | void MessagePort::OnMessage() { |
| 319 | HandleScope handle_scope(env()->isolate()); |
| 320 | Local<Context> context = object()->CreationContext(); |
| 321 | |
| 322 | // data_ can only ever be modified by the owner thread, so no need to lock. |
| 323 | // However, the message port may be transferred while it is processing |
| 324 | // messages, so we need to check that this handle still owns its `data_` field |
| 325 | // on every iteration. |
| 326 | while (data_) { |
| 327 | Message received; |
| 328 | { |
| 329 | // Get the head of the message queue. |
| 330 | Mutex::ScopedLock lock(data_->mutex_); |
| 331 | if (!data_->receiving_messages_) |
| 332 | break; |
| 333 | if (data_->incoming_messages_.empty()) |
| 334 | break; |
| 335 | received = std::move(data_->incoming_messages_.front()); |
| 336 | data_->incoming_messages_.pop_front(); |
| 337 | } |
| 338 | |
| 339 | if (!env()->can_call_into_js()) { |
| 340 | // In this case there is nothing to do but to drain the current queue. |
| 341 | continue; |
| 342 | } |
| 343 | |
| 344 | { |
| 345 | // Call the JS .onmessage() callback. |
| 346 | HandleScope handle_scope(env()->isolate()); |
| 347 | Context::Scope context_scope(context); |
| 348 | Local<Value> args[] = { |
| 349 | received.Deserialize(env(), context).FromMaybe(Local<Value>()) |
| 350 | }; |
| 351 | |
| 352 | if (args[0].IsEmpty() || |
| 353 | !object()->Has(context, env()->onmessage_string()).FromMaybe(false) || |
| 354 | MakeCallback(env()->onmessage_string(), 1, args).IsEmpty()) { |
| 355 | // Re-schedule OnMessage() execution in case of failure. |
| 356 | if (data_) |
| 357 | TriggerAsync(); |
| 358 | return; |
| 359 | } |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | if (data_ && data_->IsSiblingClosed()) { |
| 364 | Close(); |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | bool MessagePort::IsSiblingClosed() const { |
| 369 | CHECK(data_); |
| 370 | return data_->IsSiblingClosed(); |
| 371 | } |
| 372 | |
| 373 | void MessagePort::OnClose() { |
| 374 | if (data_) { |
| 375 | data_->owner_ = nullptr; |
| 376 | data_->Disentangle(); |
| 377 | } |
| 378 | data_.reset(); |
| 379 | delete async(); |
| 380 | } |
| 381 | |
| 382 | std::unique_ptr<MessagePortData> MessagePort::Detach() { |
| 383 | Mutex::ScopedLock lock(data_->mutex_); |
| 384 | data_->owner_ = nullptr; |
| 385 | return std::move(data_); |
| 386 | } |
| 387 | |
| 388 | |
| 389 | void MessagePort::Send(Message&& message) { |
| 390 | Mutex::ScopedLock lock(*data_->sibling_mutex_); |
| 391 | if (data_->sibling_ == nullptr) |
| 392 | return; |
| 393 | data_->sibling_->AddToIncomingQueue(std::move(message)); |
| 394 | } |
| 395 | |
| 396 | void MessagePort::Send(const FunctionCallbackInfo<Value>& args) { |
| 397 | Environment* env = Environment::GetCurrent(args); |
| 398 | Message msg; |
| 399 | if (msg.Serialize(env, object()->CreationContext(), args[0], args[1]) |
| 400 | .IsNothing()) { |
| 401 | return; |
| 402 | } |
| 403 | Send(std::move(msg)); |
| 404 | } |
| 405 | |
| 406 | void MessagePort::PostMessage(const FunctionCallbackInfo<Value>& args) { |
| 407 | Environment* env = Environment::GetCurrent(args); |
| 408 | MessagePort* port; |
| 409 | ASSIGN_OR_RETURN_UNWRAP(&port, args.This()); |
| 410 | if (!port->data_) { |
| 411 | return THROW_ERR_CLOSED_MESSAGE_PORT(env); |
| 412 | } |
| 413 | if (args.Length() == 0) { |
| 414 | return THROW_ERR_MISSING_ARGS(env, "Not enough arguments to " |
| 415 | "MessagePort.postMessage"); |
| 416 | } |
| 417 | port->Send(args); |
| 418 | } |
| 419 | |
| 420 | void MessagePort::Start() { |
| 421 | Mutex::ScopedLock lock(data_->mutex_); |
| 422 | data_->receiving_messages_ = true; |
| 423 | if (!data_->incoming_messages_.empty()) |
| 424 | TriggerAsync(); |
| 425 | } |
| 426 | |
| 427 | void MessagePort::Stop() { |
| 428 | Mutex::ScopedLock lock(data_->mutex_); |
| 429 | data_->receiving_messages_ = false; |
| 430 | } |
| 431 | |
| 432 | void MessagePort::Start(const FunctionCallbackInfo<Value>& args) { |
| 433 | Environment* env = Environment::GetCurrent(args); |
| 434 | MessagePort* port; |
| 435 | ASSIGN_OR_RETURN_UNWRAP(&port, args.This()); |
| 436 | if (!port->data_) { |
| 437 | THROW_ERR_CLOSED_MESSAGE_PORT(env); |
| 438 | return; |
| 439 | } |
| 440 | port->Start(); |
| 441 | } |
| 442 | |
| 443 | void MessagePort::Stop(const FunctionCallbackInfo<Value>& args) { |
| 444 | Environment* env = Environment::GetCurrent(args); |
| 445 | MessagePort* port; |
| 446 | ASSIGN_OR_RETURN_UNWRAP(&port, args.This()); |
| 447 | if (!port->data_) { |
| 448 | THROW_ERR_CLOSED_MESSAGE_PORT(env); |
| 449 | return; |
| 450 | } |
| 451 | port->Stop(); |
| 452 | } |
| 453 | |
| 454 | size_t MessagePort::self_size() const { |
| 455 | Mutex::ScopedLock lock(data_->mutex_); |
| 456 | size_t sz = sizeof(*this) + sizeof(*data_); |
| 457 | for (const Message& msg : data_->incoming_messages_) |
| 458 | sz += sizeof(msg) + msg.main_message_buf_.size; |
| 459 | return sz; |
| 460 | } |
| 461 | |
| 462 | void MessagePort::Entangle(MessagePort* a, MessagePort* b) { |
| 463 | Entangle(a, b->data_.get()); |
| 464 | } |
| 465 | |
| 466 | void MessagePort::Entangle(MessagePort* a, MessagePortData* b) { |
| 467 | MessagePortData::Entangle(a->data_.get(), b); |
| 468 | } |
| 469 | |
| 470 | MaybeLocal<Function> GetMessagePortConstructor( |
| 471 | Environment* env, Local<Context> context) { |
| 472 | // Factor generating the MessagePort JS constructor into its own piece |
| 473 | // of code, because it is needed early on in the child environment setup. |
| 474 | Local<FunctionTemplate> templ = env->message_port_constructor_template(); |
| 475 | if (!templ.IsEmpty()) |
| 476 | return templ->GetFunction(context); |
| 477 | |
| 478 | { |
| 479 | Local<FunctionTemplate> m = env->NewFunctionTemplate(MessagePort::New); |
| 480 | m->SetClassName(env->message_port_constructor_string()); |
| 481 | m->InstanceTemplate()->SetInternalFieldCount(1); |
| 482 | |
| 483 | AsyncWrap::AddWrapMethods(env, m); |
| 484 | |
| 485 | env->SetProtoMethod(m, "postMessage", MessagePort::PostMessage); |
| 486 | env->SetProtoMethod(m, "start", MessagePort::Start); |
| 487 | env->SetProtoMethod(m, "stop", MessagePort::Stop); |
| 488 | env->SetProtoMethod(m, "close", HandleWrap::Close); |
| 489 | env->SetProtoMethod(m, "unref", HandleWrap::Unref); |
| 490 | env->SetProtoMethod(m, "ref", HandleWrap::Ref); |
| 491 | env->SetProtoMethod(m, "hasRef", HandleWrap::HasRef); |
| 492 | |
| 493 | env->set_message_port_constructor_template(m); |
| 494 | } |
| 495 | |
| 496 | return GetMessagePortConstructor(env, context); |
| 497 | } |
| 498 | |
| 499 | namespace { |
| 500 | |
| 501 | static void MessageChannel(const FunctionCallbackInfo<Value>& args) { |
| 502 | Environment* env = Environment::GetCurrent(args); |
| 503 | if (!args.IsConstructCall()) { |
| 504 | THROW_ERR_CONSTRUCT_CALL_REQUIRED(env); |
| 505 | return; |
| 506 | } |
| 507 | |
| 508 | Local<Context> context = args.This()->CreationContext(); |
| 509 | Context::Scope context_scope(context); |
| 510 | |
| 511 | MessagePort* port1 = MessagePort::New(env, context); |
| 512 | MessagePort* port2 = MessagePort::New(env, context); |
| 513 | MessagePort::Entangle(port1, port2); |
| 514 | |
| 515 | args.This()->Set(env->context(), env->port1_string(), port1->object()) |
| 516 | .FromJust(); |
| 517 | args.This()->Set(env->context(), env->port2_string(), port2->object()) |
| 518 | .FromJust(); |
| 519 | } |
| 520 | |
| 521 | static void InitMessaging(Local<Object> target, |
| 522 | Local<Value> unused, |
| 523 | Local<Context> context, |
| 524 | void* priv) { |
| 525 | Environment* env = Environment::GetCurrent(context); |
| 526 | |
| 527 | { |
| 528 | Local<String> message_channel_string = |
| 529 | FIXED_ONE_BYTE_STRING(env->isolate(), "MessageChannel"); |
| 530 | Local<FunctionTemplate> templ = env->NewFunctionTemplate(MessageChannel); |
| 531 | templ->SetClassName(message_channel_string); |
| 532 | target->Set(env->context(), |
| 533 | message_channel_string, |
| 534 | templ->GetFunction(context).ToLocalChecked()).FromJust(); |
| 535 | } |
| 536 | |
| 537 | target->Set(context, |
| 538 | env->message_port_constructor_string(), |
| 539 | GetMessagePortConstructor(env, context).ToLocalChecked()) |
| 540 | .FromJust(); |
| 541 | } |
| 542 | |
| 543 | } // anonymous namespace |
| 544 | |
| 545 | } // namespace worker |
| 546 | } // namespace node |
| 547 | |
| 548 | NODE_MODULE_CONTEXT_AWARE_INTERNAL(messaging, node::worker::InitMessaging) |