Artifact 03d809ebe062ed6027885fd07d574af72034c9b95a9f67ed17228ac5452afc79:
- File
internal/sync/receipt.go
— part of check-in
[9594cf3bae]
at
2026-10-29 03:06:23
on branch main
— perf(wasm): add a libfossil_nohttp build tag that leaves out net/http
Under TinyGo, importing net/http at all links its TLS and X.509 code (crypto/x509, encoding/asn1, math/big's integers, AES-GCM, SHA-3, netip) whether or not a request is ever made: a five-line program that calls only http.StatusText grows by about 430 KB raw. The library imports it for its HTTP client transport and its HTTP server, which a program that serves sync through HandleSyncWithOpts and supplies its own transport never calls.
Built with -tags libfossil_nohttp, the library leaves out NewHTTPTransport, WithHTTPClient, Repo.Pull, Repo.XferHandler and Repo.ServeHTTP, and the internal/sync client, server, redirect handling and peer-address trace they use. HandleSync, HandleSyncWithOpts, Sync over a host Transport and everything else are unchanged. Without the tag nothing changes. The retry classifier keeps the two statuses it read from net/http as constants, and DNS fault classification moves to its own file so it stays in the tagged build.
For the html-artifact-publisher engine (TinyGo 0.43.0, Go 1.26.0) the tag takes the gzipped binary from 1,100,251 to 1,003,279 bytes. A test builds the library, db and simio with the tag for the host and js/wasm and checks none links net/http, net/http/httptrace or crypto/tls, and that the untagged library still does. (user: [email protected], size: 3671) [annotate] [blame] [check-ins using] [more...]
package sync import ( "context" "fmt" "net" gosync "sync" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/simio" ) // A sync, a clone and a request the server answers are each one receipt, as // each is one fossil process holding one g.rcvid (src/content.c, // content_rcvid_init): the first content one stores records an rcvfrom row, // and everything it stores names that row. This file holds what fills those // rows the way fossil fills them. // peerAddresser is a Transport that knows the address of the peer it last // exchanged with, as fossil's client knows g.zIpAddr: the numeric address of // the socket it connected (src/http_socket.c). A transport that does not // know leaves the client's receipt without one, as fossil's file transport // does. type peerAddresser interface { PeerAddr() string } // PeerAddr records the IP address of the peer an HTTP request connected to. // The zero value is ready to use and safe for concurrent use. type PeerAddr struct { mu gosync.Mutex ip string } // Get returns the address last recorded, "" before any. func (p *PeerAddr) Get() string { if p == nil { panic("sync.PeerAddr.Get: receiver must not be nil") } p.mu.Lock() defer p.mu.Unlock() return p.ip } // hostOnly drops the port from a host:port address, as REMOTE_ADDR and // fossil's getnameinfo carry the address alone. func hostOnly(addr string) string { host, _, err := net.SplitHostPort(addr) if err != nil { return addr } return host } // withClientReceipt returns r recording what a sync or clone receives under // one receipt, credited to the local user named login (fossil's user_select) // and stamped by clock. A Repo already carrying a receipt keeps it, so the // pull a clone runs for its phantoms is part of the clone's receipt. func withClientReceipt(r *repo.Repo, login string, clock simio.Clock) (*repo.Repo, error) { if r.Receipt() != nil { return r, nil } uid, err := r.LocalUID(login) if err != nil { return nil, fmt.Errorf("receipt: %w", err) } return r.WithReceipt(&db.Receipt{UID: uid, Clock: clock}), nil } // notePeer gives the client's receipt the address of the peer t last // exchanged with. It matters only until the receipt's row is made, which is // when fossil reads g.zIpAddr. func notePeer(r *repo.Repo, t Transport) { rc := r.Receipt() if rc == nil { panic("sync.notePeer: the session's repo has no receipt") } if p, ok := t.(peerAddresser); ok { rc.IPAddr = p.PeerAddr() } } // remoteAddrKey is the context key for the address a request came from. type remoteAddrKey struct{} // WithRemoteAddr returns ctx carrying addr, the address a sync request came // from, for the server's receipt (fossil's REMOTE_ADDR). A host:port address // is recorded as the host alone. func WithRemoteAddr(ctx context.Context, addr string) context.Context { return context.WithValue(ctx, remoteAddrKey{}, hostOnly(addr)) } // serverReceipt is the receipt of one request the server answers: credited to // nobody until a login card names a user (src/login.c sets g.userUid to // nobody's uid before page_xfer reads any card), from the request's address. func serverReceipt(ctx context.Context, r *repo.Repo, opts HandleOpts) (*db.Receipt, error) { var uid int64 err := r.DB().QueryRow("SELECT coalesce((SELECT uid FROM user WHERE login='nobody'), 0)"). Scan(&uid) if err != nil { return nil, fmt.Errorf("receipt: nobody's uid: %w", err) } addr := hostOnly(opts.RemoteAddr) if addr == "" { addr, _ = ctx.Value(remoteAddrKey{}).(string) } return &db.Receipt{UID: uid, IPAddr: addr, Clock: opts.Clock}, nil }