-
Notifications
You must be signed in to change notification settings - Fork 4
Threading Model
znet owns its threads. You never start one, and your code runs on them rather than on the thread that created the server or client. This page is the contract.
Client: two, plus whatever the transport runs.
| Thread | Runs |
|---|---|
| Loop | Drives the session: reads, dispatches your handlers, flushes |
| Encoder | Serializes queued packets so encoding overlaps the flush |
Server: one acceptor plus a worker pool.
| Thread | Runs |
|---|---|
| Acceptor | Accepts connections, drives sessions until their handshake finishes, then hands each to a worker |
| Worker | Drives its assigned sessions: reads, dispatches your handlers, encodes and flushes |
A ZDT server adds a receive thread that takes datagrams off the shared UDP socket and routes them to the right session.
Workers get sessions by least-loaded assignment at promotion. A session stays with its worker for life, so all of one session's callbacks are serialized. But a session changes threads once, from the acceptor to its worker, when its handshake completes.
| Your code | Called on |
|---|---|
| Event callback | The thread that owns the session: a worker, the acceptor for a not-yet-promoted session, or the client's loop |
OnPacket handlers |
The same |
SerializeTyped |
Whichever thread encodes: the client's encoder, or a server worker |
DeserializeTyped |
The session's owning thread |
One session never runs two callbacks at once. You do not need a lock to protect state belonging to a single session.
Different sessions run concurrently, on different workers. Anything shared
across sessions, a room list, a player registry, a shared codec, is touched
concurrently and needs its own synchronization. A shared Codec is safe because
serializers are stateless and it is not mutated after setup; mutating one while
sessions are live is not.
Your handler blocks its session's thread. On a server that thread is also driving other sessions, so a slow handler delays them too. Hand long work to your own thread pool.
A session can move between threads once. The acceptor drives it during the handshake; a worker drives it afterwards. Since the two never overlap, ordinary non-atomic state is fine. But code that latches a thread id at construction and asserts it later will be wrong.
SendPacket is safe from any thread. It only queues, and the queue is
lock-free precisely so a game loop calling it never blocks behind a worker
mid-encode. Check the return value: false means full.
Close is safe from any thread.
SetCodec and SetHandler are meant to be called from the session's own
thread, which is what you get inside the connected event or inside an
OnPacket. Calling them from elsewhere while the session is live races the
thread that reads them.
metrics() is a snapshot and safe to read from any thread. Values may be
slightly stale; see Metrics.
Messages sent on one session arrive in the order you called SendPacket, per
channel, when the delivery mode says ordered. Sends from two threads to the same
session interleave in whatever order they reach the queue. The queue preserves
order, but "which came first" is decided by your threads, not by znet.
If ordering between two messages matters, send them from the same thread.
Sessions are shared_ptr. Holding one past a disconnect is safe: it stays valid
and IsAlive() returns false. This matters most in P2P, where the locator
returns and you keep the session it produced.
Destroying a Server or Client stops and joins its threads, so no callback can
be running once the destructor returns. Do not destroy either from inside its own
callback.
Debug builds check ZDT's thread rules at runtime: entering the transport's
update, flush or receive path from two threads at once aborts with
two threads entered one ZDT thread domain at once. These compile out entirely
in release, since the guard member itself is #ifndef NDEBUG, so object layout
is unchanged.
If you are doing something unusual with threads, run your tests against a debug build of znet at least once.