You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@bifromq.apache.org by "EverWang (via GitHub)" <gi...@apache.org> on 2026/06/03 09:04:53 UTC

[I] QoS0 outbound PUBLISH refreshes MQTT keepalive idle timer and delays LWT for half-open clients [bifromq]

EverWang opened a new issue, #260:
URL: https://github.com/apache/bifromq/issues/260

   ### **Describe the bug**
   
   BifroMQ appears to refresh the MQTT session keepalive idle timestamp when a QoS0 outbound PUBLISH is successfully written to the Netty channel.
   
   This may delay MQTT Keep Alive timeout and Last Will delivery for half-open clients.
   
   In our scenario, an IoT device loses power and no longer sends any MQTT packets, including PINGREQ. However, another client keeps publishing QoS0 messages to a topic subscribed by the device. BifroMQ continues sending outbound QoS0 PUBLISH packets to the device connection, and the session is not closed around `keepAlive * 1.5`.
   
   For a device Keep Alive of 120 seconds, we expected the connection to be closed and LWT to be triggered at around 180seconds. Instead, the connection was closed at about 369 seconds.
   
   Expected behavior:
   
   MQTT Keep Alive timeout should be based on client-to-server MQTT packet activity. Broker-to-client outbound QoS0 PUBLISH write success should not reset the MQTT keepalive idle timer, because a successful Netty write does not prove that the powered-off or half-open client is still alive.
   
   #### **Environment**
   
   - Version: 4.0.0
   - JVM Version: OpenJDK17
   - Hardware Spec: 8C32G
   - OS: Rocky Linux 9
   - Deployment: standalone
   
   #### **Reproducible Steps**
   
   1. Start BifroMQ with default or near-default configuration.
   
      Important configuration:
      - No custom plugins
      - Debug-level logs enabled if available
      - [attach or paste the exact BifroMQ configuration here]
   
   2. Connect a device client to BifroMQ.
   
      ***SUB Client Parameters***:
   
      - MQTT Connection:
        - ClientIdentifier: `device-202`
        - Clean Session: false
        - Keep Alive: `120`
        - Last Will:
          - Topic:  connect/xxx
          - QoS: 0
          - Retain: true
          - Payload: {"xxx":"xxx"}
      - MQTT Sub:
        - TopicFilter: `projectAlive/202`
        - QoS: `0`
   
   3. Start another MQTT client that continuously publishes QoS0 messages to the topic subscribed by the device.
   
      ***PUB Client Parameters***:
   
      - MQTT Connection:
        - ClientIdentifier: `app-publisher`
      - MQTT Pub:
        - Topic: `projectAlive/202`
        - QoS: `0`
        - Retain: `false`
        - Payload size: size is not important
        - Publish interval / QPS: 1 msg / 10s
   
   4. Simulate unexpected device power loss.
   
      For example:
      - Power off the physical device directly, or
      - Drop the client network path so that the broker does not immediately receive TCP FIN/RST.
   
      The important condition is that the device sends no further MQTT packets after this point.
   
   5. Keep the publisher running so that BifroMQ continues to send QoS0 outbound PUBLISH packets to the device connection.
   
   6. Observe when BifroMQ closes the device connection and triggers LWT.
   
      Observed:
      - Device Keep Alive: `120s`
      - Expected timeout: around `120 * 1.5 = 180s`
      - Actual close/LWT time:   more than 180s 
   
      Please note that the actual value may vary depending on TCP/network behavior and publish rate, but the key issue is that continuous outbound QoS0 PUBLISH traffic appears to postpone the MQTT keepalive timeout.
   
   **Additional context**
   
   The issue seems related to `MQTTSessionHandler`.
   
   Inbound MQTT packets refresh `lastActiveAtNanos`, which is expected:
   
   ```java
   public final void channelRead(ChannelHandlerContext ctx, Object msg) {
       ...
       if (mqttMessage.decoderResult().isSuccess()) {
           tenantMeter.recordSummary(MqttIngressBytes, sizer.sizeByHeader(mqttMessage.fixedHeader()));
           lastActiveAtNanos = sessionCtx.nanoTime();
   ```
   
   However, QoS0 outbound PUBLISH also refreshes lastActiveAtNanos after Netty write success:
   
   ```java
   write(pubMsg).addListener(f -> {
       memUsage.addAndGet(-msgSize);
       if (f.isSuccess()) {
           lastActiveAtNanos = sessionCtx.nanoTime();
   ```
   This is in the QoS0 outbound path, sendQoS0SubMessage.
   
   The idle check uses the same timestamp:
   ```java
   private void checkIdle() {
       if (sessionCtx.nanoTime() - lastActiveAtNanos > idleTimeoutNanos) {
           idleTimeoutTask.cancel(true);
           handleProtocolResponse(helper().onIdleTimeout(keepAliveTimeSeconds));
       }
   }
   ```
   idleTimeoutNanos is calculated as:
   ```java
   this.idleTimeoutNanos = Duration.ofMillis(keepAliveTimeSeconds * 1500L).toNanos(); // x1.5
   ```
   This means successful broker-to-client QoS0 writes can affect the MQTT Keep Alive idle judgment.
   
   From the MQTT Keep Alive/LWT perspective, this seems problematic because a successful Netty write does not necessarily mean the client is alive, especially for half-open TCP connections after device power loss.
   
   Suggested fix:
   
   Do not refresh the MQTT Keep Alive idle timestamp on outbound QoS0 PUBLISH write success.
   Keep the keepalive idle timestamp based on inbound MQTT packets from the client.
   If outbound activity tracking is needed, use a separate timestamp that is not used for Keep Alive / LWT timeout.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@bifromq.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] QoS0 outbound PUBLISH refreshes MQTT keepalive idle timer and delays LWT for half-open clients [bifromq]

Posted by "popduke (via GitHub)" <gi...@apache.org>.
popduke commented on issue #260:
URL: https://github.com/apache/bifromq/issues/260#issuecomment-4618408354

   > However, QoS0 outbound PUBLISH also refreshes lastActiveAtNanos after Netty write success:
   > 
   > ```java
   > write(pubMsg).addListener(f -> {
   >     memUsage.addAndGet(-msgSize);
   >     if (f.isSuccess()) {
   >         lastActiveAtNanos = sessionCtx.nanoTime(); // to be removed
   > ```
   > This is in the QoS0 outbound path, sendQoS0SubMessage.
   
   Thanks for the report, it's a valid issue, and I think it should be a simple one-line fix: remove the lastActiveAtNanos update after a successful QoS0 send. Would you mind submitting a PR?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@bifromq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [I] QoS0 outbound PUBLISH refreshes MQTT keepalive idle timer and delays LWT for half-open clients [bifromq]

Posted by "popduke (via GitHub)" <gi...@apache.org>.
popduke closed issue #260: QoS0 outbound PUBLISH refreshes MQTT keepalive idle timer and delays LWT for half-open clients
URL: https://github.com/apache/bifromq/issues/260


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@bifromq.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org