-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add unsubscribe feature for client #196
base: master
Are you sure you want to change the base?
Changes from 7 commits
f0e26d9
5cd0adb
8062d61
b9f0593
7d26b70
6c54aba
012d9ae
8e2412f
a618797
a2d0fcb
13c9bf7
42b95e2
8624062
453c5c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ | |
use Thruway\Message\SubscribedMessage; | ||
use Thruway\Message\SubscribeMessage; | ||
use Thruway\Message\UnsubscribedMessage; | ||
use Thruway\Message\UnsubscribeMessage; | ||
|
||
/** | ||
* Class Subscriber | ||
|
@@ -28,14 +29,19 @@ class Subscriber extends AbstractRole | |
* @var array | ||
*/ | ||
private $subscriptions; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private $unsubscriptionsPromises; | ||
|
||
/** | ||
* Constructor | ||
*/ | ||
public function __construct() | ||
{ | ||
|
||
$this->subscriptions = []; | ||
$this->unsubscriptionsPromises = []; | ||
} | ||
|
||
/** | ||
|
@@ -86,7 +92,7 @@ protected function processError(AbstractSession $session, ErrorMessage $msg) | |
$this->processSubscribeError($session, $msg); | ||
break; | ||
case Message::MSG_UNSUBSCRIBE: | ||
// TODO | ||
$this->processUnsubscribeError($session, $msg); | ||
break; | ||
default: | ||
Logger::critical($this, "Unhandled error"); | ||
|
@@ -128,6 +134,32 @@ protected function processSubscribed(ClientSession $session, SubscribedMessage $ | |
} | ||
} | ||
} | ||
|
||
/** | ||
* Process unsubscribe error | ||
* | ||
* @param \Thruway\AbstractSession $session | ||
* @param \Thruway\Message\ErrorMessage $msg | ||
*/ | ||
protected function processUnsubscribeError(AbstractSession $session, ErrorMessage $msg) | ||
{ | ||
foreach ($this->subscriptions as $key => $subscription) { | ||
if ($subscription["unsubscribed_request_id"] === $msg->getErrorRequestId()) { | ||
// reject the promise | ||
$this->subscriptions[$key]['deferred']->reject($msg); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This deferred is from the original subscription. It would not be possible to reject this because in order to unsubscribe, you would need the subscribe to resolve first (so that you would have the subscription_id), and at that point, you cannot reject. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, I updated that part. |
||
return; | ||
} | ||
} | ||
|
||
// Execution continues up here in case the original unsubscribe request has not been found | ||
foreach ($this->unsubscriptionsPromises as $key => $unsubscriptionPromise) { | ||
if ($unsubscriptionPromise["request_id"] === $msg->getErrorRequestId()) { | ||
$unsubscriptionPromise["deferred"]->reject($msg); | ||
unset($this->unsubscriptionPromises[$key]); | ||
return; | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the spec say anything about what the client should do if there is an error unsubscribing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
/** | ||
* process unsubscribed | ||
|
@@ -219,13 +251,49 @@ public function subscribe(ClientSession $session, $topicName, callable $callback | |
"options" => $options, | ||
"deferred" => $deferred | ||
]; | ||
|
||
array_push($this->subscriptions, $subscription); | ||
|
||
$subscribeMsg = new SubscribeMessage($requestId, $options, $topicName); | ||
$session->sendMessage($subscribeMsg); | ||
|
||
return $deferred->promise(); | ||
} | ||
|
||
|
||
/** | ||
* process unsubscribe | ||
* @param ClientSession $session | ||
* @param type $topicName | ||
* @return boolean | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do see that this actually returns promise - the docblock just needs updated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, this has been fixed |
||
*/ | ||
public function unsubscribe(ClientSession $session, $topicName) | ||
{ | ||
$requestId = Utils::getUniqueId(); | ||
$subscriptionId = false; | ||
$deferred = new Deferred(); | ||
|
||
foreach ($this->subscriptions as $i => $subscription) { | ||
if ($subscription["topic_name"] == $topicName) { | ||
$subscriptionId = $subscription["subscription_id"]; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should noop the subscription callback at this point so that the callback won't receive messages once it has asked to be unsubscribed - even if the router decides to keep sending them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think is the best way to achieve this? We could set the |
||
$this->subscriptions[$i]["unsubscribed_request_id"] = $requestId; | ||
$this->subscriptions[$i]["unsubscribed_deferred"] = $deferred; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is possible that more than one subscription to the same uri may exist. Difference routers handle this differently. With the Thruway router, the subscription is going to be a new subscription with a new id - giving two distinct subscriptions with an identical uri. I can't remember exactly what crossbar will do, I believe it will either send a This could be further compounded by the fact that subscriptions with the same uri may actually be different because of other reasons (pattern matching comes to mind). It would be better to handle unsubscribe by using the subscription_id which can be obtained when the promise returned by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we take a look at Autobahn|JS client, the Here is the implementation I propose. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While it would be good to keep the pattern that Autobahn|JS uses (which would have If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated the code so that the unsubscribe method takes the |
||
|
||
// In case the client never subscribed to this topic before | ||
if ($subscriptionId === false) { | ||
$unsubscriptionPromise = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If false here - we should just return a rejected promise. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right. This way, the |
||
"request_id" => $requestId, | ||
"deferred" => $deferred | ||
]; | ||
|
||
array_push($this->unsubscriptionsPromises, $unsubscriptionPromise); | ||
} | ||
|
||
$unsubscribeMessage = new UnsubscribeMessage($requestId, $subscriptionId); | ||
$session->sendMessage($unsubscribeMessage); | ||
|
||
return $deferred->promise(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should be called
processUnsubscribe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is called whenever the router responds with an
ERROR
message. Why should it be calledprocessUnsubscribe
? It follows the same logic asprocessSubscribeError
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
processUnsubscribeError
is correct. Only the broker would handle theUNSUBSCRIBE
message.