X-Git-Url: https://git.quassel-irc.org/?a=blobdiff_plain;f=src%2Fcore%2Fcorenetwork.cpp;h=30896aa7d7211b96aaa8a33b3dc39e97f43e4f73;hb=97a9b1646bb0d6362cef14bac3a577481cc01e49;hp=76f430cbc7a68b4453620f97453b98d8f7c77a4d;hpb=5013eef8eb17221e8f5866977f02e970e30ec0ac;p=quassel.git diff --git a/src/core/corenetwork.cpp b/src/core/corenetwork.cpp index 76f430cb..30896aa7 100644 --- a/src/core/corenetwork.cpp +++ b/src/core/corenetwork.cpp @@ -72,7 +72,7 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session) connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect())); connect(&_autoWhoTimer, SIGNAL(timeout()), this, SLOT(sendAutoWho())); connect(&_autoWhoCycleTimer, SIGNAL(timeout()), this, SLOT(startAutoWhoCycle())); - connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(fillBucketAndProcessQueue())); + connect(&_tokenBucketTimer, SIGNAL(timeout()), this, SLOT(checkTokenBucket())); connect(&socket, SIGNAL(connected()), this, SLOT(socketInitialized())); connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); @@ -84,6 +84,13 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session) #endif connect(this, SIGNAL(newEvent(Event *)), coreSession()->eventManager(), SLOT(postEvent(Event *))); + // Custom rate limiting + // These react to the user changing settings in the client + connect(this, SIGNAL(useCustomMessageRateSet(bool)), SLOT(updateRateLimiting())); + connect(this, SIGNAL(messageRateBurstSizeSet(quint32)), SLOT(updateRateLimiting())); + connect(this, SIGNAL(messageRateDelaySet(quint32)), SLOT(updateRateLimiting())); + connect(this, SIGNAL(unlimitedMessageRateSet(bool)), SLOT(updateRateLimiting())); + // IRCv3 capability handling // These react to CAP messages from the server connect(this, SIGNAL(capAdded(QString)), this, SLOT(serverCapAdded(QString))); @@ -301,12 +308,18 @@ void CoreNetwork::userInput(BufferInfo buf, QString msg) void CoreNetwork::putRawLine(const QByteArray s, const bool prepend) { - if (_tokenBucket > 0) { + if (_tokenBucket > 0 || (_skipMessageRates && _msgQueue.size() == 0)) { + // If there's tokens remaining, ... + // Or rate limits don't apply AND no messages are in queue (to prevent out-of-order), ... + // Send the message now. writeToSocket(s); } else { + // Otherwise, queue the message for later if (prepend) { + // Jump to the start, skipping other messages _msgQueue.prepend(s); } else { + // Add to back, waiting in order _msgQueue.append(s); } } @@ -529,11 +542,15 @@ void CoreNetwork::socketInitialized() socket.setSocketOption(QAbstractSocket::KeepAliveOption, true); - // TokenBucket to avoid sending too much at once - _messageDelay = 2200; // this seems to be a safe value (2.2 seconds delay) - _burstSize = 5; - _tokenBucket = _burstSize; // init with a full bucket - _tokenBucketTimer.start(_messageDelay); + // Update the TokenBucket, force-enabling unlimited message rates for initial registration and + // capability negotiation. networkInitialized() will call updateRateLimiting() without the + // force flag to apply user preferences. When making changes, ensure that this still happens! + // As Quassel waits for CAP ACK/NAK and AUTHENTICATE replies, this shouldn't ever fill the IRC + // server receive queue and cause a kill. "Shouldn't" being the operative word; the real world + // is a scary place. + updateRateLimiting(true); + // Fill up the token bucket as we're connecting from scratch + resetTokenBucket(); // Request capabilities as per IRCv3.2 specifications // Older servers should ignore this; newer servers won't downgrade to RFC1459 @@ -628,6 +645,10 @@ void CoreNetwork::networkInitialized() _disconnectExpected = false; _quitRequested = false; + // Update the TokenBucket with specified rate-limiting settings, removing the force-unlimited + // flag used for initial registration and capability negotiation. + updateRateLimiting(); + if (useAutoReconnect()) { // reset counter _autoReconnectCount = unlimitedReconnectRetries() ? -1 : autoReconnectRetries(); @@ -916,6 +937,88 @@ void CoreNetwork::setPingInterval(int interval) _pingTimer.setInterval(interval * 1000); } + +/******** Custom Rate Limiting ********/ + +void CoreNetwork::updateRateLimiting(const bool forceUnlimited) +{ + // Verify and apply custom rate limiting options, always resetting the delay and burst size + // (safe-guarding against accidentally starting the timer), but don't reset the token bucket as + // this may be called while connected to a server. + + if (useCustomMessageRate() || forceUnlimited) { + // Custom message rates enabled, or chosen by means of forcing unlimited. Let's go for it! + + _messageDelay = messageRateDelay(); + + _burstSize = messageRateBurstSize(); + if (_burstSize < 1) { + qWarning() << "Invalid messageRateBurstSize data, cannot have zero message burst size!" + << _burstSize; + // Can't go slower than one message at a time + _burstSize = 1; + } + + if (_tokenBucket > _burstSize) { + // Don't let the token bucket exceed the maximum + _tokenBucket = _burstSize; + // To fill up the token bucket, use resetRateLimiting(). Don't do that here, otherwise + // changing the rate-limit settings while connected to a server will incorrectly reset + // the token bucket. + } + + // Toggle the timer according to whether or not rate limiting is enabled + // If we're here, either useCustomMessageRate or forceUnlimited is true. Thus, the logic is + // _skipMessageRates = ((useCustomMessageRate && unlimitedMessageRate) || forceUnlimited) + // Override user preferences if called with force unlimited, only used during connect. + _skipMessageRates = (unlimitedMessageRate() || forceUnlimited); + if (_skipMessageRates) { + // If the message queue already contains messages, they need sent before disabling the + // timer. Set the timer to a rapid pace and let it disable itself. + if (_msgQueue.size() > 0) { + qDebug() << "Outgoing message queue contains messages while disabling rate " + "limiting. Sending remaining queued messages..."; + // Promptly run the timer again to clear the messages. Rate limiting is disabled, + // so nothing should cause this to block.. in theory. However, don't directly call + // fillBucketAndProcessQueue() in order to keep it on a separate thread. + // + // TODO If testing shows this isn't needed, it can be simplified to a direct call. + // Hesitant to change it without a wide variety of situations to verify behavior. + _tokenBucketTimer.start(100); + } else { + // No rate limiting, disable the timer + _tokenBucketTimer.stop(); + } + } else { + // Rate limiting enabled, enable the timer + _tokenBucketTimer.start(_messageDelay); + } + } else { + // Custom message rates disabled. Go for the default. + + _skipMessageRates = false; // Enable rate-limiting by default + _messageDelay = 2200; // This seems to be a safe value (2.2 seconds delay) + _burstSize = 5; // 5 messages at once + if (_tokenBucket > _burstSize) { + // TokenBucket to avoid sending too much at once. Don't let the token bucket exceed the + // maximum. + _tokenBucket = _burstSize; + // To fill up the token bucket, use resetRateLimiting(). Don't do that here, otherwise + // changing the rate-limit settings while connected to a server will incorrectly reset + // the token bucket. + } + // Rate limiting enabled, enable the timer + _tokenBucketTimer.start(_messageDelay); + } +} + +void CoreNetwork::resetTokenBucket() +{ + // Fill up the token bucket to the maximum + _tokenBucket = _burstSize; +} + + /******** IRCv3 Capability Negotiation ********/ void CoreNetwork::serverCapAdded(const QString &capability) @@ -1274,12 +1377,30 @@ void CoreNetwork::sslErrors(const QList &sslErrors) #endif // HAVE_SSL +void CoreNetwork::checkTokenBucket() +{ + if (_skipMessageRates) { + if (_msgQueue.size() == 0) { + // Message queue emptied; stop the timer and bail out + _tokenBucketTimer.stop(); + return; + } + // Otherwise, we're emptying the queue, continue on as normal + } + + // Process whatever messages are pending + fillBucketAndProcessQueue(); +} + + void CoreNetwork::fillBucketAndProcessQueue() { + // If there's less tokens than burst size, refill the token bucket by 1 if (_tokenBucket < _burstSize) { _tokenBucket++; } + // As long as there's tokens available and messages remaining, sending messages from the queue while (_msgQueue.size() > 0 && _tokenBucket > 0) { writeToSocket(_msgQueue.takeFirst()); } @@ -1290,7 +1411,10 @@ void CoreNetwork::writeToSocket(const QByteArray &data) { socket.write(data); socket.write("\r\n"); - _tokenBucket--; + if (!_skipMessageRates) { + // Only subtract from the token bucket if message rate limiting is enabled + _tokenBucket--; + } }