Files
rclone/lib/filepool/filepool.go
T
613b335962 filepool: add generic file handle pool in lib/filepool
Factor the connection-backed write handle pool out of the smb backend
into a generic lib/filepool.Pool[T] with its own tests, so it can be
reused by other backends that implement fs.OpenWriterAter over a
connection pool.

The smb backend keeps its behaviour, opening and releasing handles
through small closures passed to the pool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Splainte <r.wycke@hotmail.fr>
2026-08-30 13:06:06 +01:00

79 lines
2.1 KiB
Go

// Package filepool keeps a set of reusable write handles open on a single
// remote path, one per connection, so several goroutines can write to the same
// file at once without sharing a handle.
//
// It is used by backends that implement fs.OpenWriterAter over a connection
// pool, where the core writes the chunks of a large file concurrently at
// non-overlapping offsets.
package filepool
import (
"context"
"sync"
"golang.org/x/sync/errgroup"
)
// Pool hands out handles of type T for a single file. Handles are reused when
// free and opened on demand otherwise. It is safe for concurrent use.
//
// The zero value is not usable; call New.
type Pool[T any] struct {
ctx context.Context
open func(context.Context) (T, error)
release func(handle T, err error) error
mu sync.Mutex
free []T
}
// New returns a Pool.
//
// open opens a fresh handle on its own connection. release closes a handle and
// returns its connection: err is the error that made the handle unusable (nil
// when the handle is simply being drained) and the returned error is the result
// of closing it.
func New[T any](ctx context.Context, open func(context.Context) (T, error), release func(handle T, err error) error) *Pool[T] {
return &Pool[T]{ctx: ctx, open: open, release: release}
}
// Get returns a free handle, opening a new one if none are free.
func (p *Pool[T]) Get() (T, error) {
p.mu.Lock()
if n := len(p.free); n > 0 {
h := p.free[n-1]
p.free = p.free[:n-1]
p.mu.Unlock()
return h, nil
}
p.mu.Unlock()
return p.open(p.ctx)
}
// Put returns a handle to the pool. If err is non-nil the write that used the
// handle failed, so the handle is released instead of being reused.
func (p *Pool[T]) Put(handle T, err error) {
if err != nil {
_ = p.release(handle, err)
return
}
p.mu.Lock()
p.free = append(p.free, handle)
p.mu.Unlock()
}
// Drain releases every free handle, closing them concurrently, and returns the
// first error encountered.
func (p *Pool[T]) Drain() error {
p.mu.Lock()
free := p.free
p.free = nil
p.mu.Unlock()
g := new(errgroup.Group)
for _, h := range free {
g.Go(func() error { return p.release(h, nil) })
}
return g.Wait()
}