This commit is contained in:
2026-08-16 13:41:43 -03:00
parent 7b8e7bfbd0
commit 96ea761b72
76 changed files with 1399 additions and 3847 deletions
+67 -23
View File
@@ -27,6 +27,7 @@ type chunkClientOptions struct {
pollDelay time.Duration
txnTimeout time.Duration
tcpBuffer int
minPipeline int
maxPipeline int
}
@@ -526,6 +527,7 @@ type chunkConn struct {
consumedOffset uint64
eof bool
pipeline int
minPipeline int
maxPipeline int
closeOnce sync.Once
@@ -556,6 +558,12 @@ func openChunkTunnel(serverAddr, token, targetHost string, targetPort int, opts
if opts.maxPipeline > 256 {
opts.maxPipeline = 256
}
if opts.minPipeline < 1 {
opts.minPipeline = 1
}
if opts.minPipeline > opts.maxPipeline {
opts.minPipeline = opts.maxPipeline
}
profile := getPathProfile(serverAddr, token, opts)
reconnect := opts.reconnectEvery
@@ -620,8 +628,11 @@ func openChunkTunnel(serverAddr, token, targetHost string, targetPort int, opts
downloadLane: newRequestLane(serverAddr, opts.tcpBuffer, reconnect, opts.txnTimeout),
// Start at the user-configured ceiling. On transport failures the
// pipeline is halved; successful data responses grow it back by one,
// always staying inside 1..maxPipeline. A ceiling of 1 is fixed.
// always staying inside minPipeline..maxPipeline. When the two bounds
// are equal the depth is pinned and never adapts, which is what paths
// that only work at one specific batch size need.
pipeline: opts.maxPipeline,
minPipeline: opts.minPipeline,
maxPipeline: opts.maxPipeline,
}
c.upSizer = newAdaptiveSizer("upload", upStart, opts)
@@ -629,6 +640,53 @@ func openChunkTunnel(serverAddr, token, targetHost string, targetPort int, opts
return c, nil
}
// pinnedBatch reports whether the batch depth is fixed. A pinned depth never
// grows or shrinks: some paths only deliver correctly at one specific number of
// records per request, so the adaptive controller must stay out of the way.
func (c *chunkConn) pinnedBatch() bool { return c.minPipeline >= c.maxPipeline }
// batchCount is how many records the next download request will ask for.
func (c *chunkConn) batchCount(chunk int) int {
count := c.pipeline
if count < c.minPipeline {
count = c.minPipeline
}
if count > c.maxPipeline {
count = c.maxPipeline
}
// Bound each batch to roughly 1 MiB of useful data, but never below the
// configured floor: a pinned depth is a path requirement, not a hint.
if maxCount := (1024 * 1024) / maxInt(chunk, 1); maxCount < count {
count = maxInt(maxCount, c.minPipeline)
}
return count
}
// growPipeline widens the batch by one after a successful data response.
func (c *chunkConn) growPipeline() {
if c.pinnedBatch() {
return
}
if c.pipeline < c.maxPipeline {
c.pipeline++
}
}
// shrinkPipeline halves the batch after a transport failure. It reports the old
// and new depth, and whether anything actually changed; when it returns false
// the caller should shrink the record size instead.
func (c *chunkConn) shrinkPipeline() (int, int, bool) {
if c.pinnedBatch() || c.pipeline <= c.minPipeline {
return c.pipeline, c.pipeline, false
}
old := c.pipeline
c.pipeline /= 2
if c.pipeline < c.minPipeline {
c.pipeline = c.minPipeline
}
return old, c.pipeline, old != c.pipeline
}
func (c *chunkConn) fillReadBuffer() error {
if c.eof {
return io.EOF
@@ -636,17 +694,7 @@ func (c *chunkConn) fillReadBuffer() error {
minFailures := 0
for len(c.readBuf) == 0 && !c.eof {
chunk := c.downSizer.Current()
count := c.pipeline
if count < 1 {
count = 1
}
if count > c.maxPipeline {
count = c.maxPipeline
}
// Bound each batch to roughly 1 MiB of useful data.
if maxCount := (1024 * 1024) / maxInt(chunk, 1); maxCount < count {
count = maxInt(maxCount, 1)
}
count := c.batchCount(chunk)
data, status, err := c.downloadLane.download(c.sid, c.downloadOffset, c.consumedOffset, chunk, count)
for _, part := range data {
@@ -655,20 +703,16 @@ func (c *chunkConn) fillReadBuffer() error {
}
if len(data) > 0 {
c.downSizer.Success(chunk)
if c.pipeline < c.maxPipeline {
c.pipeline++
}
c.growPipeline()
minFailures = 0
}
if err != nil {
if c.pipeline > 1 {
old := c.pipeline
c.pipeline /= 2
if c.pipeline < 1 {
c.pipeline = 1
}
if c.opts.adaptLog && old != c.pipeline {
fmt.Printf("adaptive download pipeline: %d -> %d after transport failure\n", old, c.pipeline)
// Shrink the batch first, then the record size. When the batch is
// pinned (min == max) the depth is left alone entirely and only the
// record size adapts.
if old, next, shrank := c.shrinkPipeline(); shrank {
if c.opts.adaptLog {
fmt.Printf("adaptive download batch: %d -> %d after transport failure\n", old, next)
}
} else {
old, next := c.downSizer.Failure(chunk)