V14
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user