Documentation
¶
Overview ¶
Package delayproxy is a TCP proxy that holds every write of its clients back by a fixed time before it forwards it: a store that stands far away, made on the loopback. A test reaches it through testredis.Far, a person through tools/fardelay; both run this one proxy.
WHAT IS DELAYED. The direction from the client to the target, and only it. A reply goes straight back, so one command and its reply cost the delay once: a delay of 128ms is a store 128ms away by round trip.
A WRITE IS WHAT ONE READ RETURNS. The proxy cannot see a client's writes, it sees what a read of the client's connection returns. A client's write of up to ChunkBytes (16 KiB) is one read on the loopback, so it is held once: a pipeline the client writes in one go pays the delay once however many commands it holds, up to the window below, and three commands sent one after the other, each waiting for its reply, pay it three times. A write larger than ChunkBytes is several reads.
A LINE, NOT A QUEUE. Every read is stamped with the time it arrived and is forwarded when arrival + delay is reached, never a delay after the read before it. A large write that comes in as several reads leaves as a run and pays the delay once, the way a long wire delays every bit by the same time and not each bit by the time of the one ahead of it. Order is kept.
A WINDOW, LIKE A LINK. A connection queues at most 256 reads between the client and the target, WindowBytes (4 MiB) of full reads, and has two more in hand: the one the forwarding half waits to send and the one the reading half cannot yet queue. A pipeline of up to the window pays the delay once. A client that writes more is held back by TCP until the reads ahead of it have gone, and what it writes then is stamped when it is read: each further window pays the delay once more, so a pipeline of 100,000 commands of 45 bytes (4.3 MiB) pays it twice, and one of 11.5 MiB three times, as it would across a long link with a window of that size.
BOUNDED. The proxy runs one goroutine to accept, and GoroutinesPerConn (three) for each open connection: the replies back, the client's reads, the delay. At most Options.MaxConns connections are open; a client past the bound waits in the listener's backlog until one ends, and is never served out of turn. If the listener itself fails, the proxy closes it, so a client is refused and not left waiting in a backlog nothing reads.
IT ENDS WHEN ASKED. Stop closes the listener and every connection and returns when every goroutine the proxy started has returned: nothing it started runs after Stop. A connection ends when either side ends it; a client that hangs up its sending side still gets the replies to what it sent, including what was still held.
Nothing here names a host, a store or a tool. Who may be listened on or forwarded to is the caller's rule: see Loopback.
Index ¶
Constants ¶
const ( // DefaultMaxConns is the open connections a proxy serves at once when // Options.MaxConns is not set: three goroutines each. DefaultMaxConns = 1024 // MaxDelay is the longest delay Serve accepts. A store is never 30 seconds // away; a larger number is a unit typed wrong. MaxDelay = 30 * time.Second // ChunkBytes is the most one read of a client returns, and so the largest // write that is held once. ChunkBytes = 16 << 10 // GoroutinesPerConn is what an open connection runs: one to copy the // replies back, one to read the client and one to hold and forward. GoroutinesPerConn = 3 // WindowBytes is what the queue of a connection holds when its reads are // full, inFlight reads of ChunkBytes: a pipeline of up to this pays the delay // once, and each further WindowBytes pays it once more. WindowBytes = inFlight * ChunkBytes )
Variables ¶
This section is empty.
Functions ¶
func Loopback ¶
Loopback refuses a host:port to listen on or forward to that is not on this machine's loopback: an IP literal in 127.0.0.0/8 or ::1, or the name localhost, with a port from 0 (the kernel picks) to 65535. It is the rule for a proxy that a test or a tool must not turn into a way out of the machine, or a way in to it; the proxy itself forwards wherever it is told.
Types ¶
type Clock ¶
type Clock struct {
// Now is the time. Nil is time.Now.
Now func() time.Time
// Wait returns when d has passed and reports true, or returns at once and
// reports false when stop is closed first. Nil waits on a timer.
Wait func(stop <-chan struct{}, d time.Duration) bool
}
Clock is what the proxy asks the time of. The zero Clock is the real one; a test puts its own in, so that "each write is delayed once" is a count of waits and never a measurement of a machine's load.
type Options ¶
type Options struct {
// MaxConns is the open connections served at once. Zero is
// DefaultMaxConns; a negative number is refused.
MaxConns int
// Clock is the time the proxy uses. The zero Clock is the real one.
Clock Clock
// Dial connects one client to the target: network is "tcp" and address is
// the target Serve was given, and ctx ends when the connection has not
// been made in time or the proxy stops. Nil dials TCP. A test puts its own
// in, so that no port is dialled.
Dial func(ctx context.Context, network, address string) (net.Conn, error)
// Logf is told what the proxy cannot tell a client: a target that did
// not answer, a listener that failed. It may be called from any of the
// proxy's goroutines and never after Stop returns. Nil says nothing.
Logf func(format string, args ...any)
// Live counts the goroutines the proxy is running, the same number it
// updates: one to accept, and GoroutinesPerConn for each open connection,
// and 0 once Stop has returned. Nil means the proxy keeps the count
// privately. A test sets it before the proxy starts and reads the same
// number the proxy updates, from any goroutine.
Live *atomic.Int64
}
Options are what Serve may be asked for beyond the target and the delay.
type Proxy ¶
type Proxy struct {
// contains filtered or unexported fields
}
Proxy is one running proxy: a listener, its target and its delay.
func Listen ¶
Listen listens on addr, a host:port, and serves it: Serve over net.Listen. Port 0 lets the kernel pick; Addr says which.
func Serve ¶
Serve accepts connections on ln and forwards each to target, a host:port dialled once per client connection, holding every write of the client back by delay. It owns ln from here on: Stop closes it. It refuses a target that is not a host and a port, a delay below zero or above MaxDelay, and a negative MaxConns, and starts nothing.
func (*Proxy) Shortest ¶
Shortest is the least time any forwarded write was held, by the proxy's own clock, from the read that returned it to the moment it was sent on: never less than the delay while the proxy works. Zero before the first write.