mirror of
https://github.com/luxfi/zap.git
synced 2026-07-26 22:53:31 +00:00
Three orthogonal additions, one wire format. v2 package — generic ad-hoc schema dispatch (~/work/lux/zap/v2): - README + IMPOSSIBILITY + BENCH_RESULTS document the v1↔v2 split. - builder/field/iter/list/pool/registry/schema/view + codegen package emit v1-equivalent fast paths from declarative .zap sources. - _compile_fail_test/ + examples/ cover negative and positive cases. - v2_test.go exercises the generic dispatch path. - doc.go in v1 marks v1 deprecated for *new* schema authoring; existing v1 buffers stay parseable by both v1 and v2. bufpool — pooled read slabs (bufpool.go + bufpool_test.go): - Message gains optional *bufRef; Release() returns the slab to its pool when sourced from the pooled read path, GC-managed otherwise. - ParseHeader returns (data, rootOffset) without allocating *Message, used by zapv2 to avoid two allocations per parse. - bench_read_test.go + node_quic_stream_test.go cover both paths. Transport allocator + UMA/RDMA cgo (transport/*): - Transport.Caps()/Buffer interface: capability + slab-typed buffer contract. Bytes() vs DevicePtr() decoupled so UMA and GPUDirect can share the same call surface. - DPDK PMD + GPUDirect RDMA + CUDA UMA real cgo backends gated behind build tags; stub paths remain default on darwin / non-Linux. - transport_test.go covers Caps reporting + Buffer lifecycle on the default transport. LLM.md + go.mod follow the additions.
154 lines
5.9 KiB
Go
154 lines
5.9 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
// Transport selects which network transport a Node uses.
|
|
//
|
|
// The default zero value, TransportTCP, preserves the historical
|
|
// behavior of NewNode (TCP + optional TLS via NodeConfig.TLS) so every
|
|
// existing caller keeps working untouched.
|
|
//
|
|
// TransportQUIC selects the QUIC transport defined in the quic
|
|
// subpackage. The quic subpackage must be imported anonymously by the
|
|
// process for TransportQUIC to be available; otherwise NewNode returns
|
|
// ErrTransportUnavailable.
|
|
type Transport int
|
|
|
|
const (
|
|
// TransportTCP is ZAP's original transport: framed Cap'n Proto
|
|
// over TCP, with optional TLS 1.3 supplied via NodeConfig.TLS.
|
|
// This is the default for backward compatibility.
|
|
TransportTCP Transport = iota
|
|
|
|
// TransportQUIC selects the QUIC transport from the quic
|
|
// subpackage. Requires:
|
|
//
|
|
// import _ "github.com/luxfi/zap/quic"
|
|
//
|
|
// in some package linked into the binary, so the QUIC factory
|
|
// registers itself at init time.
|
|
TransportQUIC
|
|
)
|
|
|
|
// ErrTransportUnavailable is returned by NewNode when the requested
|
|
// Transport has no registered factory. For TransportQUIC, this means
|
|
// the quic subpackage was not imported.
|
|
var ErrTransportUnavailable = errors.New("zap: transport unavailable (did you import _ \"github.com/luxfi/zap/quic\"?)")
|
|
|
|
// TransportFactory is the extension point the quic subpackage uses
|
|
// to register itself with this package at init time, avoiding an
|
|
// import cycle.
|
|
//
|
|
// A TransportFactory wraps the existing Node so all the higher-level
|
|
// APIs (Handle, Send, Call, Broadcast) work identically regardless of
|
|
// transport. The factory is responsible for:
|
|
//
|
|
// - Binding a listener on n.port (cfg.Port).
|
|
// - Yielding accepted connections via the supplied dispatch hook.
|
|
// - Implementing outbound dial via the supplied dispatch hook.
|
|
//
|
|
// Today only QUIC uses this hook; TCP is wired directly in node.go
|
|
// because the original Node embeds the TCP listener fields. This is
|
|
// pragmatic — once QUIC ships we can refactor TCP onto the same
|
|
// factory shape without a single user-visible change.
|
|
type TransportFactory interface {
|
|
// Listen starts a listener on the address derived from cfg.
|
|
// The listener calls onConn for each accepted, post-handshake
|
|
// connection. Listen must not block; it returns the bound
|
|
// address (so tests using port 0 can learn the kernel port) and
|
|
// a Close func.
|
|
Listen(ctx context.Context, cfg NodeConfig, onConn func(peerID string, conn TransportConn)) (string, func() error, error)
|
|
|
|
// Dial opens a connection to addr. The returned TransportConn
|
|
// is symmetric with the conns yielded by Listen.
|
|
Dial(ctx context.Context, cfg NodeConfig, addr string) (peerID string, conn TransportConn, err error)
|
|
}
|
|
|
|
// TransportConn is the transport-level abstraction over a single
|
|
// peer connection. It mirrors the existing TCP *Conn semantics
|
|
// (Send/Recv/Close) and is used by the transport-aware Node code
|
|
// path in node.go.
|
|
type TransportConn interface {
|
|
// Send writes one ZAP frame to the peer.
|
|
Send(frame []byte) error
|
|
|
|
// Recv blocks until the next ZAP frame arrives, or returns
|
|
// io.EOF when the peer cleanly closes.
|
|
Recv() ([]byte, error)
|
|
|
|
// Close performs a graceful close.
|
|
Close() error
|
|
|
|
// RemoteAddr returns the peer's network address (best effort —
|
|
// for QUIC this is the address at handshake time, not necessarily
|
|
// the latest after migration).
|
|
RemoteAddr() string
|
|
}
|
|
|
|
// TransportStreamer is an optional capability extension to
|
|
// TransportConn for transports that natively multiplex independent
|
|
// bidirectional streams (notably QUIC). When a TransportConn also
|
|
// implements TransportStreamer, Node.Call routes each request onto a
|
|
// fresh per-Call stream instead of serializing on the shared control
|
|
// stream — this lets concurrent Calls progress in parallel up to the
|
|
// peer-advertised stream limit (1024 for ZAP's QUIC config).
|
|
//
|
|
// TCP transport does NOT implement TransportStreamer; Node.Call
|
|
// transparently falls back to control-stream serialization on TCP.
|
|
type TransportStreamer interface {
|
|
// OpenCallStream opens a fresh bidirectional stream for a single
|
|
// request/response exchange. The caller writes exactly one frame
|
|
// (request) then reads exactly one frame (response) then closes.
|
|
OpenCallStream(ctx context.Context) (TransportStream, error)
|
|
|
|
// AcceptCallStream blocks until the peer opens a per-Call stream.
|
|
// Used by the server-side dispatch loop to fan out to handlers
|
|
// without blocking on a single serialized read.
|
|
AcceptCallStream(ctx context.Context) (TransportStream, error)
|
|
}
|
|
|
|
// TransportStream is one per-Call stream. Stream lifecycle:
|
|
//
|
|
// WriteFrame(req) // single request
|
|
// ReadFrame() // single response
|
|
// Close() // release stream ID back to the QUIC pool
|
|
//
|
|
// Both directions are length-prefixed ZAP frames identical to the
|
|
// control-stream wire format — so byte-for-byte interop with the
|
|
// TCP transport and with control-stream Call paths is preserved.
|
|
type TransportStream interface {
|
|
WriteFrame(frame []byte) error
|
|
ReadFrame() ([]byte, error)
|
|
Close() error
|
|
}
|
|
|
|
// transportRegistry holds the registered factories, keyed by
|
|
// Transport. Read-only after init.
|
|
var transportRegistry = map[Transport]TransportFactory{}
|
|
|
|
// RegisterTransport plugs a TransportFactory into the registry. The
|
|
// quic subpackage calls this in its init function.
|
|
//
|
|
// Registration is idempotent — re-registering the same Transport
|
|
// overwrites — but in practice each Transport has exactly one
|
|
// factory linked into the binary.
|
|
func RegisterTransport(t Transport, f TransportFactory) {
|
|
transportRegistry[t] = f
|
|
}
|
|
|
|
// lookupTransport returns the registered factory for t or
|
|
// ErrTransportUnavailable.
|
|
func lookupTransport(t Transport) (TransportFactory, error) {
|
|
f, ok := transportRegistry[t]
|
|
if !ok {
|
|
return nil, ErrTransportUnavailable
|
|
}
|
|
return f, nil
|
|
}
|