Module: Pgbus::Web::Streamer::HubProtocol
- Defined in:
- lib/pgbus/web/streamer/hub_protocol.rb
Overview
Framing for the master-hub Unix socket (issue #382): 4-byte big-endian payload length + UTF-8 JSON. Unlike the job-side wake pipes (1-byte, lossy-by-design — Process::WakePipe), stream frames can carry an ephemeral broadcast's ONLY copy of its HTML, so the transport is length-prefixed and lossless; drop decisions are made per-message by the MasterHub, never by the wire format.
Message shapes (JSON objects; "t" is the discriminator):
worker → master: {t:"sub", q:} subscribe, synchronous — master acks
{t:"unsub", q:} unsubscribe, fire-and-forget
master → worker: {t:"ack", q:} sub acknowledged (LISTEN active)
{t:"wake", q:, p: <String|nil>} durable (p:nil) or ephemeral wake
{t:"status", healthy: <bool>} listener health broadcast
Reads are blocking (each side owns a dedicated reader thread); a short read means the peer died mid-frame and is reported as EOF (nil), never as a truncated message.
Defined Under Namespace
Classes: ProtocolError
Constant Summary collapse
- HEADER_BYTES =
4- MAX_FRAME_BYTES =
Generous ceiling for ephemeral HTML payloads; a frame announcing more than this is a corrupt stream or a runaway producer — sever rather than allocate.
4 * 1024 * 1024
Class Method Summary collapse
- .encode(message) ⇒ Object
-
.read_exactly(io, count) ⇒ Object
Blocking read of exactly
countbytes; nil on EOF (including EOF partway through — IO#read returns the short tail once, then nil). -
.read_frame(io) ⇒ Object
Returns the decoded Hash, or nil on EOF — clean close, peer death mid-frame, OR a connection reset: an abrupt close can surface as ECONNRESET instead of orderly EOF depending on unread data and platform (Ruby 4.0 reports it deterministically where 3.x saw EOF), and both mean the same thing here: the peer is gone.
Class Method Details
.encode(message) ⇒ Object
36 37 38 39 40 41 42 43 |
# File 'lib/pgbus/web/streamer/hub_protocol.rb', line 36 def encode() json = JSON.generate() bytes = json.b raise ProtocolError, "frame too large: #{bytes.bytesize} bytes (max #{MAX_FRAME_BYTES})" if bytes.bytesize > MAX_FRAME_BYTES [bytes.bytesize].pack("N") + bytes end |
.read_exactly(io, count) ⇒ Object
Blocking read of exactly count bytes; nil on EOF (including EOF
partway through — IO#read returns the short tail once, then nil).
76 77 78 79 80 81 |
# File 'lib/pgbus/web/streamer/hub_protocol.rb', line 76 def read_exactly(io, count) data = io.read(count) return nil if data.nil? || data.bytesize < count data end |
.read_frame(io) ⇒ Object
Returns the decoded Hash, or nil on EOF — clean close, peer death mid-frame, OR a connection reset: an abrupt close can surface as ECONNRESET instead of orderly EOF depending on unread data and platform (Ruby 4.0 reports it deterministically where 3.x saw EOF), and both mean the same thing here: the peer is gone. Raises ProtocolError on an oversized announcement or malformed JSON.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/pgbus/web/streamer/hub_protocol.rb', line 51 def read_frame(io) header = read_exactly(io, HEADER_BYTES) return nil unless header length = header.unpack1("N") raise ProtocolError, "frame too large: #{length} bytes (max #{MAX_FRAME_BYTES})" if length > MAX_FRAME_BYTES body = read_exactly(io, length) return nil unless body body = body.force_encoding(Encoding::UTF_8) raise ProtocolError, "malformed frame: invalid UTF-8" unless body.valid_encoding? decoded = JSON.parse(body) raise ProtocolError, "malformed frame: expected a JSON object, got #{decoded.class}" unless decoded.is_a?(Hash) decoded rescue JSON::ParserError => e raise ProtocolError, "malformed frame: #{e.}" rescue Errno::ECONNRESET nil end |