mirror of
https://github.com/luxfi/zap.git
synced 2026-10-30 15:04:16 +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.
145 lines
4.0 KiB
Go
145 lines
4.0 KiB
Go
// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
|
|
zap "github.com/luxfi/zap"
|
|
)
|
|
|
|
// init registers the QUIC TransportFactory with the parent zap
|
|
// package. Importing this package — even with a blank import — is
|
|
// sufficient to enable TransportQUIC.
|
|
func init() {
|
|
zap.RegisterTransport(zap.TransportQUIC, &factory{})
|
|
}
|
|
|
|
// factory is the zap.TransportFactory implementation backed by this
|
|
// package's Server + Client.
|
|
//
|
|
// One ZAP node has one factory invocation: one Server, one Client.
|
|
// Per-peer dials reuse the Client's single UDP socket so all
|
|
// connections share connection-migration state.
|
|
type factory struct {
|
|
mu sync.Mutex
|
|
srv *Server
|
|
client *Client
|
|
}
|
|
|
|
// Listen binds a QUIC listener on cfg.Port and dispatches each
|
|
// accepted post-handshake connection via onConn.
|
|
func (f *factory) Listen(ctx context.Context, cfg zap.NodeConfig, onConn func(peerID string, c zap.TransportConn)) (string, func() error, error) {
|
|
if cfg.TLS == nil {
|
|
return "", nil, errors.New("zap/quic: NodeConfig.TLS required for TransportQUIC (must contain server Certificates or GetCertificate)")
|
|
}
|
|
|
|
srvCfg := ServerConfig{
|
|
NodeID: cfg.NodeID,
|
|
Addr: fmt.Sprintf(":%d", cfg.Port),
|
|
TLS: cfg.TLS,
|
|
Logger: cfg.Logger,
|
|
}
|
|
if cfg.QUICConfig != nil {
|
|
if qc, ok := cfg.QUICConfig.(*Config); ok {
|
|
srvCfg.QUIC = qc.QUIC
|
|
srvCfg.RejectEarlyData = qc.RejectEarlyData
|
|
}
|
|
}
|
|
|
|
srv, err := Listen(srvCfg)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
cli, err := NewClient(ClientConfig{
|
|
NodeID: cfg.NodeID,
|
|
TLS: cloneClientTLS(cfg.TLS),
|
|
Logger: cfg.Logger,
|
|
})
|
|
if err != nil {
|
|
_ = srv.Close()
|
|
return "", nil, err
|
|
}
|
|
|
|
f.mu.Lock()
|
|
f.srv = srv
|
|
f.client = cli
|
|
f.mu.Unlock()
|
|
|
|
// Accept loop. Each accepted *Conn is wrapped in a transportConnAdapter
|
|
// so the parent package sees the TransportConn interface only.
|
|
go func() {
|
|
for {
|
|
c, err := srv.Accept(ctx)
|
|
if err != nil {
|
|
return
|
|
}
|
|
onConn(c.PeerID, &transportConnAdapter{c: c})
|
|
}
|
|
}()
|
|
|
|
return srv.Addr().String(), func() error {
|
|
_ = srv.Close()
|
|
_ = cli.Close()
|
|
return nil
|
|
}, nil
|
|
}
|
|
|
|
// Dial opens a QUIC connection to addr.
|
|
func (f *factory) Dial(ctx context.Context, cfg zap.NodeConfig, addr string) (string, zap.TransportConn, error) {
|
|
f.mu.Lock()
|
|
cli := f.client
|
|
f.mu.Unlock()
|
|
if cli == nil {
|
|
return "", nil, errors.New("zap/quic: Dial called before Listen (no client transport)")
|
|
}
|
|
c, err := cli.Dial(ctx, addr)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return c.PeerID, &transportConnAdapter{c: c}, nil
|
|
}
|
|
|
|
// transportConnAdapter wraps a *Conn so it satisfies both
|
|
// zap.TransportConn and zap.TransportStreamer (the latter unlocks
|
|
// per-Call streams on the QUIC fast path).
|
|
//
|
|
// The TransportStreamer methods (OpenCallStream / AcceptCallStream)
|
|
// route to *Conn.OpenStream and *Conn.AcceptStream — each return
|
|
// a *Stream which already implements WriteFrame / ReadFrame / Close
|
|
// at the same wire format as the control-stream frames.
|
|
type transportConnAdapter struct {
|
|
c *Conn
|
|
}
|
|
|
|
func (a *transportConnAdapter) Send(frame []byte) error { return a.c.Send(frame) }
|
|
func (a *transportConnAdapter) Recv() ([]byte, error) { return a.c.Recv() }
|
|
func (a *transportConnAdapter) Close() error { return a.c.Close() }
|
|
func (a *transportConnAdapter) RemoteAddr() string { return a.c.RemoteAddr.String() }
|
|
|
|
// OpenCallStream opens a fresh QUIC bidirectional stream for one
|
|
// request/response Call. The returned *Stream wraps quic-go's per-
|
|
// stream flow control.
|
|
func (a *transportConnAdapter) OpenCallStream(ctx context.Context) (zap.TransportStream, error) {
|
|
s, err := a.c.OpenStream(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// AcceptCallStream blocks until the peer opens a new bidirectional
|
|
// stream and returns it ready for ReadFrame.
|
|
func (a *transportConnAdapter) AcceptCallStream(ctx context.Context) (zap.TransportStream, error) {
|
|
s, err := a.c.AcceptStream(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|