Skip to main content

Message Ordering for OnMessageReceived

MQTT 5.0 section 4.6 (Message Ordering) requires that QoS 1 and QoS 2 application messages be delivered to a client in the order they were sent for a given Topic Filter.

HiveMQtt preserves that guarantee at the OnMessageReceived event boundary for QoS 1 and QoS 2 messages.

What is guaranteed​

For QoS 1 and QoS 2 publishes:

  • Handlers are started in FIFO order on a single per-client dispatch queue.
  • Within one message, global OnMessageReceived handlers run first (in registration order), then per-subscription handlers (in subscription order, respecting OverlappingSubscriptionBehavior).
  • Handlers run on a dedicated message dispatch thread (not arbitrary thread-pool workers).

The guarantee is invocation (start) order, not completion order.

Async handlers​

OnMessageReceived uses the standard .NET EventHandler<T> signature, which returns void. If you register an async lambda, it becomes async void:

client.OnMessageReceived += async (sender, args) =>
{
await SaveToDatabaseAsync(args); // ordering does NOT extend past the first await
};

Only synchronous work before the first await and handler start order are ordered. Work that continues after await may overlap with later messages.

Recommendation: keep handlers thin and synchronous — parse the payload, enqueue to your own worker/channel, and return. Perform I/O and heavy logic on application-owned consumers.

QoS 0​

QoS 0 has no ordering requirement in the MQTT specification. HiveMQtt may invoke QoS 0 OnMessageReceived handlers concurrently via the thread pool.

Disconnect behavior​

When the client disconnects, the library quiesces the dispatch queue (similar to Eclipse Paho):

  • The in-flight handler (if any) is allowed to finish.
  • Pending handlers that have not started are dropped.
  • New messages are not dispatched after quiesce begins.

After a successful reconnect, dispatch resumes for new messages.

Manual acknowledgement​

If you use manual ack, unacknowledged messages consume Receive Maximum slots until you call AckAsync. Ordered dispatch with slow handlers can cause the broker to stop sending new messages sooner — that is expected back-pressure. See the manual ack guide for details.

See also​