ZeroMQ
Asynchronous Events
Node Management
Networking
Event Handling

ZeroMQ How to handle non-message-related, asynchronous events in a ZeroMQ node?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

In ZeroMQ, message traffic is only one part of a node's behavior. Real nodes also need to react to timers, shutdown signals, connection state changes, and other I/O sources, so the right solution is usually to place ZeroMQ sockets inside a broader event loop rather than treating messaging as the only event source.

Use zmq_poll as the Basic Multiplexer

The lowest-level tool for mixing ZeroMQ socket events with other readiness events is zmq_poll. It can wait on ZeroMQ sockets and, depending on the platform and binding, standard file descriptors as well.

c
1#include <stdio.h>
2#include <zmq.h>
3
4int main(void) {
5    void *context = zmq_ctx_new();
6    void *socket = zmq_socket(context, ZMQ_REP);
7    zmq_bind(socket, "tcp://*:5555");
8
9    zmq_pollitem_t items[] = {
10        { socket, 0, ZMQ_POLLIN, 0 }
11    };
12
13    while (1) {
14        int rc = zmq_poll(items, 1, 1000);
15        if (rc == -1) {
16            break;
17        }
18
19        if (items[0].revents & ZMQ_POLLIN) {
20            char buffer[32];
21            zmq_recv(socket, buffer, sizeof(buffer), 0);
22            zmq_send(socket, "ok", 2, 0);
23        }
24
25        printf("timer tick or housekeeping work\n");
26    }
27
28    zmq_close(socket);
29    zmq_ctx_term(context);
30    return 0;
31}

The timeout parameter is useful even when no other file descriptor is involved. It gives the loop a chance to run periodic tasks such as health checks, cache cleanup, or graceful shutdown checks.

Distinguish Message Events from Socket State Events

If you need to know when a socket connects, disconnects, or retries a connection, ordinary receive loops are not enough. ZeroMQ exposes those transport-level events through socket monitoring.

The official zmq_socket_monitor API creates a stream of event notifications on an inproc endpoint. Your application then connects a ZMQ_PAIR socket to that endpoint and reads the event frames like any other message source.

c
1void *context = zmq_ctx_new();
2void *client = zmq_socket(context, ZMQ_REQ);
3void *monitor = zmq_socket(context, ZMQ_PAIR);
4
5zmq_socket_monitor(client, "inproc://client-monitor", ZMQ_EVENT_ALL);
6zmq_connect(monitor, "inproc://client-monitor");

That is the right mechanism for non-message-related socket events such as connection establishment or reconnect attempts. It is more precise than trying to infer transport state from application-level timeouts alone.

Fold Timers and External Signals Into the Same Loop

A common design is to run one thread as the node reactor. That thread polls ZeroMQ sockets, watches a stop signal, and uses the poll timeout as a periodic timer. Other threads communicate with the reactor through inproc sockets rather than touching the same socket objects directly.

This model fits ZeroMQ well because most socket types are not designed for arbitrary concurrent use from many threads. A single-threaded event loop with message passing between threads is often simpler than shared-state coordination.

If you are using CZMQ, higher-level helpers such as zloop or zpoller can reduce boilerplate. They are wrappers around the same event-driven idea: one loop owns the sockets and reacts to whichever event becomes ready next.

Integrating with an External Event Loop

In larger applications, ZeroMQ may be only one subsystem among many. You might already have an existing reactor from libuv, Boost.Asio, or a GUI framework. In those cases, integration is possible through polling support and ZeroMQ socket readiness information.

One detail matters here: ZeroMQ readiness integration is subtle. The official socket options documentation notes that the underlying file descriptor is edge-triggered and that applications should inspect ZMQ_EVENTS rather than assuming the raw descriptor alone tells the full story. That means a clean abstraction layer is worth the effort.

Use direct file-descriptor integration only if you truly need it. For many services, a dedicated ZeroMQ event loop thread plus an internal queue is easier to maintain.

Common Pitfalls

  • Treating transport events and application messages as if they were the same category of event.
  • Blocking forever in a receive loop and leaving no chance for timers, shutdown checks, or maintenance work.
  • Sharing a socket across threads instead of centralizing socket ownership in one event loop.
  • Inferring connection state indirectly when zmq_socket_monitor can provide actual socket-event notifications.
  • Using raw file-descriptor integration without handling the ZMQ_EVENTS semantics correctly.

Summary

  • Use zmq_poll as the core mechanism for multiplexing ZeroMQ socket events.
  • Use poll timeouts or a reactor helper to run timers and housekeeping tasks.
  • Use zmq_socket_monitor for connection-related events such as connect and disconnect notifications.
  • Prefer a single event-loop thread that owns the sockets and reacts to all event sources.
  • Integrate with external event loops only when necessary, and handle ZeroMQ readiness semantics carefully.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.