|
2 | 2 |
|
3 | 3 | #include <algorithm> |
4 | 4 | #include <initializer_list> |
5 | | -#include <set> |
6 | 5 | #include <shared_mutex> |
7 | 6 | #include <unordered_map> |
8 | 7 | #include <unordered_set> |
9 | 8 | #include <utility> |
| 9 | +#include <vector> |
10 | 10 |
|
11 | 11 | #include "ArgConverter.h" |
12 | 12 | #include "BuiltinLoader.h" |
@@ -194,7 +194,10 @@ class SiblingGroup final : public std::enable_shared_from_this<SiblingGroup> { |
194 | 194 | // group is one channel's two ends. |
195 | 195 | const bool named_ = false; |
196 | 196 | std::shared_mutex mutex_; |
197 | | - std::set<PortData*> ports_; |
| 197 | + // In join order: HTML delivers a broadcast to its destinations in the order |
| 198 | + // the channels were created, and a burst that finds none of them already |
| 199 | + // scheduled posts their drains in that order. |
| 200 | + std::vector<PortData*> ports_; |
198 | 201 | }; |
199 | 202 |
|
200 | 203 | namespace { |
@@ -233,7 +236,7 @@ DispatchResult SiblingGroup::Dispatch(PortData* source, std::shared_ptr<Message> |
233 | 236 | std::string* error) { |
234 | 237 | std::shared_lock<std::shared_mutex> lock(this->mutex_); |
235 | 238 |
|
236 | | - if (this->ports_.find(source) == this->ports_.end()) { |
| 239 | + if (std::find(this->ports_.begin(), this->ports_.end(), source) == this->ports_.end()) { |
237 | 240 | if (error != nullptr) { |
238 | 241 | *error = "Source MessagePort is not entangled with this group."; |
239 | 242 | } |
@@ -278,7 +281,7 @@ void SiblingGroup::Entangle(PortData* port) { |
278 | 281 | void SiblingGroup::Entangle(std::initializer_list<PortData*> ports) { |
279 | 282 | std::unique_lock<std::shared_mutex> lock(this->mutex_); |
280 | 283 | for (PortData* data : ports) { |
281 | | - this->ports_.insert(data); |
| 284 | + this->ports_.push_back(data); |
282 | 285 | // group_ is written under the port's own mutex, which is what lets |
283 | 286 | // PortData::Dispatch read it without racing a disentangle. Taken here in |
284 | 287 | // the only legal order: this group's lock is already held. |
@@ -292,7 +295,8 @@ void SiblingGroup::Disentangle(PortData* data) { |
292 | 295 | // Keeps the group alive past the last member dropping its reference. |
293 | 296 | std::shared_ptr<SiblingGroup> self = this->shared_from_this(); |
294 | 297 | std::unique_lock<std::shared_mutex> lock(this->mutex_); |
295 | | - this->ports_.erase(data); |
| 298 | + this->ports_.erase(std::remove(this->ports_.begin(), this->ports_.end(), data), |
| 299 | + this->ports_.end()); |
296 | 300 | { |
297 | 301 | std::lock_guard<std::mutex> dataLock(data->mutex_); |
298 | 302 | data->group_.reset(); |
@@ -317,10 +321,11 @@ PortData::~PortData() { |
317 | 321 |
|
318 | 322 | void PortData::AddToIncomingQueue(std::shared_ptr<Message> message) { |
319 | 323 | std::lock_guard<std::mutex> lock(this->mutex_); |
| 324 | + // Still holding the mutex: an owner read outside it could be detached by |
| 325 | + // the time the wake reaches it, and Start() checks the backlog under it. |
| 326 | + bool wake = this->owner_ != nullptr && this->owner_->WantsDrainFor(*message); |
320 | 327 | this->incoming_.push_back(std::move(message)); |
321 | | - if (this->owner_ != nullptr) { |
322 | | - // Still holding the mutex: an owner read outside it could be detached by |
323 | | - // the time the wake reaches it. |
| 328 | + if (wake) { |
324 | 329 | this->owner_->TriggerAsync(); |
325 | 330 | } |
326 | 331 | } |
@@ -397,6 +402,7 @@ std::shared_ptr<NativeMessagePort> NativeMessagePort::New(Local<Context> context |
397 | 402 | } else { |
398 | 403 | port->data_ = std::make_unique<PortData>(port.get()); |
399 | 404 | if (group != nullptr) { |
| 405 | + port->broadcast_ = true; |
400 | 406 | group->Entangle(port->data_.get()); |
401 | 407 | } |
402 | 408 | } |
@@ -449,6 +455,19 @@ void NativeMessagePort::Stop() { |
449 | 455 | this->receiving_ = false; |
450 | 456 | } |
451 | 457 |
|
| 458 | +bool NativeMessagePort::WantsDrainFor(const Message& message) const { |
| 459 | + // A port that was never started still learns that its sibling died. |
| 460 | + return this->receiving_ || message.IsCloseMessage(); |
| 461 | +} |
| 462 | + |
| 463 | +void NativeMessagePort::SetBroadcastTarget(Local<Object> target) { |
| 464 | + if (target.IsEmpty()) { |
| 465 | + this->broadcastTarget_.Reset(); |
| 466 | + } else { |
| 467 | + this->broadcastTarget_.Reset(this->isolate_, target); |
| 468 | + } |
| 469 | +} |
| 470 | + |
452 | 471 | std::unique_ptr<PortData> NativeMessagePort::Detach() { |
453 | 472 | // owner_ drops under the data mutex, so a producer either wakes this port |
454 | 473 | // before the detach or never sees an owner at all. Node carries a separate |
@@ -470,6 +489,7 @@ void NativeMessagePort::CloseHandle() { |
470 | 489 | 0, nullptr, v8::kEmbedderDataTypeTagDefault); |
471 | 490 | this->wrapper_.Reset(); |
472 | 491 | } |
| 492 | + this->broadcastTarget_.Reset(); |
473 | 493 | MessagingState* state = State(isolate); |
474 | 494 | if (state != nullptr) { |
475 | 495 | state->livePorts.erase(this->shared_from_this()); |
@@ -499,8 +519,9 @@ void NativeMessagePort::Close() { |
499 | 519 | } |
500 | 520 | // Last, on the wrapper the port has just let go of, so a listener finds an |
501 | 521 | // already-detached port and a close() from inside one is a no-op rather than |
502 | | - // a recursion. |
503 | | - if (!wrapper.IsEmpty()) { |
| 522 | + // a recursion. A broadcast port's wrapper never reaches app code, so there |
| 523 | + // is no one to tell. |
| 524 | + if (!wrapper.IsEmpty() && !this->broadcast_) { |
504 | 525 | this->EmitClose(wrapper); |
505 | 526 | } |
506 | 527 | } |
@@ -669,6 +690,17 @@ void NativeMessagePort::Drain() { |
669 | 690 | return; |
670 | 691 | } |
671 | 692 |
|
| 693 | + Local<Object> receiver = wrapper; |
| 694 | + if (this->broadcast_) { |
| 695 | + // Per message: a listener can add or remove the channel's last listener. |
| 696 | + // With none, the event would reach no one and reading the value has no |
| 697 | + // other observable effect, so it is never read. |
| 698 | + if (this->broadcastTarget_.IsEmpty()) { |
| 699 | + continue; |
| 700 | + } |
| 701 | + receiver = this->broadcastTarget_.Get(isolate); |
| 702 | + } |
| 703 | + |
672 | 704 | Local<Value> payload; |
673 | 705 | Local<Value> ports = v8::Undefined(isolate); |
674 | 706 | bool read; |
@@ -693,12 +725,12 @@ void NativeMessagePort::Drain() { |
693 | 725 | } |
694 | 726 | } |
695 | 727 | if (!read) { |
696 | | - this->Emit(context, wrapper, emitMessage, payload, v8::Undefined(isolate), |
| 728 | + this->Emit(context, receiver, emitMessage, payload, v8::Undefined(isolate), |
697 | 729 | "messageerror"); |
698 | 730 | reschedule = true; |
699 | 731 | break; |
700 | 732 | } |
701 | | - if (!this->Emit(context, wrapper, emitMessage, payload, ports, "message")) { |
| 733 | + if (!this->Emit(context, receiver, emitMessage, payload, ports, "message")) { |
702 | 734 | reschedule = true; |
703 | 735 | break; |
704 | 736 | } |
@@ -882,6 +914,19 @@ void StopCallback(const FunctionCallbackInfo<Value>& info) { |
882 | 914 | } |
883 | 915 | } |
884 | 916 |
|
| 917 | +void SetBroadcastTargetCallback(const FunctionCallbackInfo<Value>& info) { |
| 918 | + Local<Object> wrapper; |
| 919 | + if (!PortArg(info, 0, &wrapper)) { |
| 920 | + return; |
| 921 | + } |
| 922 | + NativeMessagePort* port = PortFromWrapper(info.GetIsolate(), wrapper); |
| 923 | + if (port == nullptr) { |
| 924 | + return; |
| 925 | + } |
| 926 | + port->SetBroadcastTarget(info.Length() > 1 && info[1]->IsObject() ? info[1].As<Object>() |
| 927 | + : Local<Object>()); |
| 928 | +} |
| 929 | + |
885 | 930 | void CloseCallback(const FunctionCallbackInfo<Value>& info) { |
886 | 931 | Local<Object> wrapper; |
887 | 932 | if (!PortArg(info, 0, &wrapper)) { |
@@ -1038,6 +1083,7 @@ MaybeLocal<Object> CreateBinding(Local<Context> context) { |
1038 | 1083 | tns::SetMethod(context, binding, "postMessage", PostMessageCallback); |
1039 | 1084 | tns::SetMethod(context, binding, "start", StartCallback); |
1040 | 1085 | tns::SetMethod(context, binding, "stop", StopCallback); |
| 1086 | + tns::SetMethod(context, binding, "setBroadcastTarget", SetBroadcastTargetCallback); |
1041 | 1087 | tns::SetMethod(context, binding, "close", CloseCallback); |
1042 | 1088 | tns::SetMethod(context, binding, "drainOne", DrainOneCallback); |
1043 | 1089 | tns::SetMethodNoSideEffect(context, binding, "isDetached", IsDetachedCallback); |
|