Commit 9c61bff73ca87278ad04af6c5620ecf9fe21ee2a
1 parent
794a28bb
Fix various will things
- Delay is properly counted from moment of disconnect. - When a session is picked up again, the will is not sent. - An actual fix to what I tried a few commits ago: fix sending will twice. This logic should make storing wills also easier.
Showing
8 changed files
with
92 additions
and
31 deletions
client.cpp
| ... | ... | @@ -55,12 +55,18 @@ Client::~Client() |
| 55 | 55 | if (!this->threadData) |
| 56 | 56 | return; |
| 57 | 57 | |
| 58 | - std::shared_ptr<SubscriptionStore> &store = this->threadData->getSubscriptionStore(); | |
| 59 | - | |
| 60 | 58 | if (disconnectReason.empty()) |
| 61 | 59 | disconnectReason = "not specified"; |
| 62 | 60 | |
| 63 | 61 | logger->logf(LOG_NOTICE, "Removing client '%s'. Reason(s): %s", repr().c_str(), disconnectReason.c_str()); |
| 62 | + | |
| 63 | + std::shared_ptr<SubscriptionStore> &store = this->threadData->getSubscriptionStore(); | |
| 64 | + | |
| 65 | + if (willPublish) | |
| 66 | + { | |
| 67 | + store->queueWillMessage(willPublish, session); | |
| 68 | + } | |
| 69 | + | |
| 64 | 70 | if (fd > 0) // this check is essentially for testing, when working with a dummy fd. |
| 65 | 71 | { |
| 66 | 72 | if (epoll_ctl(threadData->epollfd, EPOLL_CTL_DEL, fd, NULL) != 0) | ... | ... |
forward_declarations.h
mqttpacket.cpp
| ... | ... | @@ -447,7 +447,6 @@ void MqttPacket::handleConnect() |
| 447 | 447 | { |
| 448 | 448 | case Mqtt5Properties::WillDelayInterval: |
| 449 | 449 | willpublish.will_delay = readFourBytesToUint32(); |
| 450 | - willpublish.setCreatedAt(std::chrono::steady_clock::now()); | |
| 451 | 450 | break; |
| 452 | 451 | case Mqtt5Properties::PayloadFormatIndicator: |
| 453 | 452 | willpublish.propertyBuilder->writePayloadFormatIndicator(readByte()); | ... | ... |
sessionsandsubscriptionsdb.cpp
| ... | ... | @@ -130,7 +130,7 @@ SessionsAndSubscriptionsResult SessionsAndSubscriptionsDB::readDataV2() |
| 130 | 130 | Publish pub(pack.getPublishData()); |
| 131 | 131 | |
| 132 | 132 | const uint32_t newPubAge = persistence_state_age + originalPubAge; |
| 133 | - pub.setCreatedAt(timepointFromAge(newPubAge)); | |
| 133 | + pub.createdAt = timepointFromAge(newPubAge); | |
| 134 | 134 | |
| 135 | 135 | logger->logf(LOG_DEBUG, "Loaded QoS %d message for topic '%s' for session '%s'.", pub.qos, pub.topic.c_str(), ses->getClientId().c_str()); |
| 136 | 136 | ses->qosPacketQueue.queuePublish(std::move(pub), id); | ... | ... |
subscriptionstore.cpp
| ... | ... | @@ -280,6 +280,9 @@ std::shared_ptr<Session> SubscriptionStore::lockSession(const std::string &clien |
| 280 | 280 | * The expiry interval as set in the properties of the will message is not used to check for expiration here. To |
| 281 | 281 | * quote the specs: "If present, the Four Byte value is the lifetime of the Will Message in seconds and is sent as |
| 282 | 282 | * the Publication Expiry Interval when the Server publishes the Will Message." |
| 283 | + * | |
| 284 | + * If a new Network Connection to this Session is made before the Will Delay Interval has passed, the Server | |
| 285 | + * MUST NOT send the Will Message [MQTT-3.1.3-9]. | |
| 283 | 286 | */ |
| 284 | 287 | void SubscriptionStore::sendQueuedWillMessages() |
| 285 | 288 | { |
| ... | ... | @@ -289,15 +292,27 @@ void SubscriptionStore::sendQueuedWillMessages() |
| 289 | 292 | auto it = pendingWillMessages.begin(); |
| 290 | 293 | while (it != pendingWillMessages.end()) |
| 291 | 294 | { |
| 292 | - std::shared_ptr<Publish> p = (*it).lock(); | |
| 295 | + QueuedWill &qw = *it; | |
| 296 | + | |
| 297 | + std::shared_ptr<Publish> p = qw.getWill().lock(); | |
| 293 | 298 | if (p) |
| 294 | 299 | { |
| 295 | - if (p->getCreatedAt() + std::chrono::seconds(p->will_delay) > now) | |
| 300 | + if (qw.getSendAt() > now) | |
| 296 | 301 | break; |
| 297 | 302 | |
| 303 | + std::shared_ptr<Session> s = qw.getSession(); | |
| 304 | + | |
| 305 | + if (!s || s->hasActiveClient()) | |
| 306 | + { | |
| 307 | + it = pendingWillMessages.erase(it); | |
| 308 | + continue; | |
| 309 | + } | |
| 310 | + | |
| 298 | 311 | logger->logf(LOG_DEBUG, "Sending delayed will on topic '%s'.", p->topic.c_str() ); |
| 299 | 312 | PublishCopyFactory factory(p.get()); |
| 300 | 313 | queuePacketAtSubscribers(factory); |
| 314 | + | |
| 315 | + s->clearWill(); | |
| 301 | 316 | } |
| 302 | 317 | it = pendingWillMessages.erase(it); |
| 303 | 318 | } |
| ... | ... | @@ -308,7 +323,7 @@ void SubscriptionStore::sendQueuedWillMessages() |
| 308 | 323 | * @param willMessage |
| 309 | 324 | * @param forceNow |
| 310 | 325 | */ |
| 311 | -void SubscriptionStore::queueWillMessage(std::shared_ptr<Publish> &willMessage, bool forceNow) | |
| 326 | +void SubscriptionStore::queueWillMessage(const std::shared_ptr<Publish> &willMessage, const std::shared_ptr<Session> &session, bool forceNow) | |
| 312 | 327 | { |
| 313 | 328 | if (!willMessage) |
| 314 | 329 | return; |
| ... | ... | @@ -320,12 +335,19 @@ void SubscriptionStore::queueWillMessage(std::shared_ptr<Publish> &willMessage, |
| 320 | 335 | { |
| 321 | 336 | PublishCopyFactory factory(willMessage.get()); |
| 322 | 337 | queuePacketAtSubscribers(factory); |
| 338 | + | |
| 339 | + // Avoid sending two immediate wills when a session is destroyed with the client disconnect. | |
| 340 | + if (session) // session is null when you're destroying a client before a session is assigned. | |
| 341 | + session->clearWill(); | |
| 342 | + | |
| 323 | 343 | return; |
| 324 | 344 | } |
| 325 | 345 | |
| 346 | + QueuedWill queuedWill(willMessage, session); | |
| 347 | + | |
| 326 | 348 | std::lock_guard<std::mutex>(this->pendingWillsMutex); |
| 327 | - auto pos = std::upper_bound(this->pendingWillMessages.begin(), this->pendingWillMessages.end(), willMessage, WillDelayCompare); | |
| 328 | - this->pendingWillMessages.insert(pos, willMessage); | |
| 349 | + auto pos = std::upper_bound(this->pendingWillMessages.begin(), this->pendingWillMessages.end(), willMessage, willDelayCompare); | |
| 350 | + this->pendingWillMessages.insert(pos, queuedWill); | |
| 329 | 351 | } |
| 330 | 352 | |
| 331 | 353 | void SubscriptionStore::publishNonRecursively(const std::unordered_map<std::string, Subscription> &subscribers, |
| ... | ... | @@ -588,7 +610,7 @@ void SubscriptionStore::removeSession(const std::shared_ptr<Session> &session) |
| 588 | 610 | std::shared_ptr<Publish> &will = session->getWill(); |
| 589 | 611 | if (will) |
| 590 | 612 | { |
| 591 | - queueWillMessage(will, true); | |
| 613 | + queueWillMessage(will, session, true); | |
| 592 | 614 | } |
| 593 | 615 | |
| 594 | 616 | RWLockGuard lock_guard(&subscriptionsRwlock); |
| ... | ... | @@ -984,3 +1006,36 @@ std::shared_ptr<Session> QueuedSessionRemoval::getSession() const |
| 984 | 1006 | { |
| 985 | 1007 | return session.lock(); |
| 986 | 1008 | } |
| 1009 | + | |
| 1010 | +QueuedWill::QueuedWill(const std::shared_ptr<Publish> &will, const std::shared_ptr<Session> &session) : | |
| 1011 | + will(will), | |
| 1012 | + session(session), | |
| 1013 | + sendAt(std::chrono::steady_clock::now() + std::chrono::seconds(will->will_delay)) | |
| 1014 | +{ | |
| 1015 | + | |
| 1016 | +} | |
| 1017 | + | |
| 1018 | +const std::weak_ptr<Publish> &QueuedWill::getWill() const | |
| 1019 | +{ | |
| 1020 | + return this->will; | |
| 1021 | +} | |
| 1022 | + | |
| 1023 | +std::chrono::time_point<std::chrono::steady_clock> QueuedWill::getSendAt() const | |
| 1024 | +{ | |
| 1025 | + return this->sendAt; | |
| 1026 | +} | |
| 1027 | + | |
| 1028 | +std::shared_ptr<Session> QueuedWill::getSession() | |
| 1029 | +{ | |
| 1030 | + return this->session.lock(); | |
| 1031 | +} | |
| 1032 | + | |
| 1033 | +bool willDelayCompare(const std::shared_ptr<Publish> &a, const QueuedWill &b) | |
| 1034 | +{ | |
| 1035 | + std::shared_ptr<Publish> _b = b.getWill().lock(); | |
| 1036 | + | |
| 1037 | + if (!_b) | |
| 1038 | + return true; | |
| 1039 | + | |
| 1040 | + return a->will_delay < _b->will_delay; | |
| 1041 | +}; | ... | ... |
subscriptionstore.h
| ... | ... | @@ -101,6 +101,20 @@ public: |
| 101 | 101 | std::shared_ptr<Session> getSession() const; |
| 102 | 102 | }; |
| 103 | 103 | |
| 104 | +class QueuedWill | |
| 105 | +{ | |
| 106 | + std::weak_ptr<Publish> will; | |
| 107 | + std::weak_ptr<Session> session; | |
| 108 | + std::chrono::time_point<std::chrono::steady_clock> sendAt; | |
| 109 | + | |
| 110 | +public: | |
| 111 | + QueuedWill(const std::shared_ptr<Publish> &will, const std::shared_ptr<Session> &session); | |
| 112 | + | |
| 113 | + const std::weak_ptr<Publish> &getWill() const; | |
| 114 | + std::chrono::time_point<std::chrono::steady_clock> getSendAt() const; | |
| 115 | + std::shared_ptr<Session> getSession(); | |
| 116 | +}; | |
| 117 | + | |
| 104 | 118 | class SubscriptionStore |
| 105 | 119 | { |
| 106 | 120 | #ifdef TESTING |
| ... | ... | @@ -122,7 +136,7 @@ class SubscriptionStore |
| 122 | 136 | int64_t retainedMessageCount = 0; |
| 123 | 137 | |
| 124 | 138 | std::mutex pendingWillsMutex; |
| 125 | - std::list<std::weak_ptr<Publish>> pendingWillMessages; | |
| 139 | + std::list<QueuedWill> pendingWillMessages; | |
| 126 | 140 | |
| 127 | 141 | std::chrono::time_point<std::chrono::steady_clock> lastTreeCleanup; |
| 128 | 142 | |
| ... | ... | @@ -151,7 +165,7 @@ public: |
| 151 | 165 | std::shared_ptr<Session> lockSession(const std::string &clientid); |
| 152 | 166 | |
| 153 | 167 | void sendQueuedWillMessages(); |
| 154 | - void queueWillMessage(std::shared_ptr<Publish> &willMessage, bool forceNow = false); | |
| 168 | + void queueWillMessage(const std::shared_ptr<Publish> &willMessage, const std::shared_ptr<Session> &session, bool forceNow = false); | |
| 155 | 169 | void queuePacketAtSubscribers(PublishCopyFactory ©Factory, bool dollar = false); |
| 156 | 170 | uint64_t giveClientRetainedMessages(const std::shared_ptr<Client> &client, const std::shared_ptr<Session> &ses, |
| 157 | 171 | const std::vector<std::string> &subscribeSubtopics, char max_qos); |
| ... | ... | @@ -174,4 +188,6 @@ public: |
| 174 | 188 | void queueSessionRemoval(const std::shared_ptr<Session> &session); |
| 175 | 189 | }; |
| 176 | 190 | |
| 191 | +bool willDelayCompare(const std::shared_ptr<Publish> &a, const QueuedWill &b); | |
| 192 | + | |
| 177 | 193 | #endif // SUBSCRIPTIONSTORE_H | ... | ... |
types.cpp
| ... | ... | @@ -187,11 +187,6 @@ bool PublishBase::hasExpired() const |
| 187 | 187 | return (expiresAfter > age); |
| 188 | 188 | } |
| 189 | 189 | |
| 190 | -void PublishBase::setCreatedAt(std::chrono::time_point<std::chrono::steady_clock> t) | |
| 191 | -{ | |
| 192 | - this->createdAt = t; | |
| 193 | -} | |
| 194 | - | |
| 195 | 190 | void PublishBase::setExpireAfter(uint32_t s) |
| 196 | 191 | { |
| 197 | 192 | this->createdAt = std::chrono::steady_clock::now(); |
| ... | ... | @@ -221,16 +216,6 @@ Publish::Publish(const std::string &topic, const std::string &payload, char qos) |
| 221 | 216 | |
| 222 | 217 | } |
| 223 | 218 | |
| 224 | -bool WillDelayCompare(const std::shared_ptr<Publish> &a, const std::weak_ptr<Publish> &b) | |
| 225 | -{ | |
| 226 | - std::shared_ptr<Publish> _b = b.lock(); | |
| 227 | - | |
| 228 | - if (!_b) | |
| 229 | - return true; | |
| 230 | - | |
| 231 | - return a->will_delay < _b->will_delay; | |
| 232 | -}; | |
| 233 | - | |
| 234 | 219 | PubResponse::PubResponse(const ProtocolVersion protVersion, const PacketType packet_type, ReasonCodes reason_code, uint16_t packet_id) : |
| 235 | 220 | packet_type(packet_type), |
| 236 | 221 | protocol_version(protVersion), | ... | ... |
types.h
| ... | ... | @@ -192,6 +192,8 @@ public: |
| 192 | 192 | */ |
| 193 | 193 | class PublishBase |
| 194 | 194 | { |
| 195 | + friend class SessionsAndSubscriptionsDB; | |
| 196 | + | |
| 195 | 197 | bool hasExpireInfo = false; |
| 196 | 198 | std::chrono::time_point<std::chrono::steady_clock> createdAt; |
| 197 | 199 | std::chrono::seconds expiresAfter; |
| ... | ... | @@ -201,7 +203,7 @@ public: |
| 201 | 203 | std::string payload; |
| 202 | 204 | char qos = 0; |
| 203 | 205 | bool retain = false; // Note: existing subscribers don't get publishes of retained messages with retain=1. [MQTT-3.3.1-9] |
| 204 | - uint32_t will_delay = 0; // if will, this is the delay. Just storing here, to avoid having to make a WillMessage class | |
| 206 | + uint32_t will_delay = 0; // if will, this is the delay. | |
| 205 | 207 | bool splitTopic = true; |
| 206 | 208 | uint16_t topicAlias = 0; |
| 207 | 209 | bool skipTopic = false; |
| ... | ... | @@ -216,7 +218,6 @@ public: |
| 216 | 218 | bool hasUserProperties() const; |
| 217 | 219 | bool hasExpired() const; |
| 218 | 220 | |
| 219 | - void setCreatedAt(std::chrono::time_point<std::chrono::steady_clock> t); | |
| 220 | 221 | void setExpireAfter(uint32_t s); |
| 221 | 222 | bool getHasExpireInfo() const; |
| 222 | 223 | const std::chrono::time_point<std::chrono::steady_clock> getCreatedAt() const; |
| ... | ... | @@ -232,8 +233,6 @@ public: |
| 232 | 233 | Publish(const std::string &topic, const std::string &payload, char qos); |
| 233 | 234 | }; |
| 234 | 235 | |
| 235 | -bool WillDelayCompare(const std::shared_ptr<Publish> &a, const std::weak_ptr<Publish> &b); | |
| 236 | - | |
| 237 | 236 | class PubResponse |
| 238 | 237 | { |
| 239 | 238 | public: | ... | ... |