OTP ERLANG Wiregrid Plainwire · Manual 1.0

Internals

Architecture

Lifecycle work and fanout work are separate. Wiregrid.Runtime serializes changes that move several indexes together: sessions, subscriptions, rooms, presence watches, resume snapshots, and drain state. One runtime process per instance keeps those ETS updates ordered. Publishing does not enter that process. Wiregrid.Fanout reads the indexes and walks recipients in bounded batches, so a busy channel does not have to wait behind session setup.

Processes and tables

Wiregrid.Tables owns the per-instance ETS tables and is the place Wiregrid.Tables.get/1 reads config from. Wiregrid.Delivery owns reservations, acknowledgements, and the slow-consumer decision. Wiregrid.Expiry sweeps TTL rows on a tick (expiry_tick_ms 100, batches of expiry_batch_size 2,048) instead of a timer per row. Wiregrid.Storage and Wiregrid.Cache are the adapter boundaries. Wiregrid.Cluster is absent from the supervision tree when cluster is false.

An instance supervisor uses rest_for_one. If the table owner dies, the runtime restarts against fresh tables. Restarting only the runtime rebuilds monitors and leaves the tables in place. The top-level application supervisor starts the instance dynamic supervisor and the task supervisor the gateway and isolated adapters use.

Hot counters that fanout touches live in src/wiregrid_hot.erl, including the session pending counter delivery increments and rolls back. That module is a small C-free Erlang helper, not a NIF.

A publish, end to end

  1. Reject the call if the instance is missing or draining.
  2. Validate options, the topic, and the event size.
  3. Authorize when a session id is present. Skip the callback when it is nil.
  4. Encode once with the configured codec (Wiregrid.Codec.Term unless replaced).
  5. Append to storage when persist is true.
  6. Fan out locally. Each recipient is :sent, :dropped, :evicted, :gone, :excluded, or :overloaded.
  7. Forward to cluster shards when clustering is enabled for the call.

The owner then receives {:"$wiregrid", envelope}. Durable class holds a reservation until ack. Ephemeral class may be dropped once soft_queue (500) is passed. Past hard_queue (2,000), durable admission stops. Wiregrid.Chat.say/4 always takes the persist path for channel messages. whisper/4 persists on {:user, user_id}.

Prepared handles skip the encode on later calls. They are signed for one instance. Dispatch plans skip repeated target validation. Both are in-process values.

Adapters and callbacks

Inline mode calls the storage or cache module in the caller. Isolated mode wraps the call in a supervised task with adapter_timeout_ms and max_adapter_pending. Overflow is {:error, :adapter_overloaded}. The same isolation switch exists for the authorizer (authorizer_mode, callback_timeout_ms, max_callback_pending). Health includes those failures, timeouts, and rejections.

Where LFE sits

The LFE modules in bindings/lfe are workers, macros, and mailbox helpers on top of wiregrid_api. They do not own tables. Installing LFE does not create a second source of truth, and omitting it does not disable any Elixir API. See the LFE chapter for which file does which job.

Clients outside the VM

Wiregrid.Foreign.Gateway accepts TCP, runs hello, and attaches a normal session. From that point the connection process is an owner like any other: subscriptions, acks, presence, and pressure use the runtime. Frames are version 1 TLV, documented in the protocol chapter. Cowboy is the same idea over WebSocket, with authentication moved to the HTTP upgrade. The browser kit is presentation only. It does not open a protocol Wiregrid would treat as trusted.

What a restart forgets

Memory storage, the memory cache, rate-limit buckets, delivery reservations, and presence are process-local. Postgres and Scylla keep the streams you persisted. Resume snapshots live until resume_ttl_ms or until the table owner restarts. Plan a durable adapter for history you cannot rebuild, and treat a node restart as a reason for clients to resume or reconnect rather than as a pause in an in-memory log.

Wiregrid 1.0 · Plainwire · github.com/Plainwire-development/Wiregrid