feat: add content signature xattr cache

This commit is contained in:
Samuel Cui
2026-08-31 21:17:41 +08:00
parent 4eecd3fe31
commit cc5248ea33
19 changed files with 1211 additions and 46 deletions
+63 -44
View File
@@ -12,11 +12,13 @@ import (
)
func (c *Copyer) prepare(ctx context.Context, indexed <-chan *baseJob) <-chan *writeJob {
// Disable read-ahead when a linear source must wait for each consumer.
chanLen := 32
if c.fromDevice.linear {
chanLen = 0
}
// Close the prepared stream only after every source worker exits.
var wg sync.WaitGroup
ch := make(chan *writeJob, chanLen)
defer func() {
@@ -24,14 +26,16 @@ func (c *Copyer) prepare(ctx context.Context, indexed <-chan *baseJob) <-chan *w
defer close(ch)
wg.Wait()
})
}()
}()
// Prepare source readers with the configured source-device concurrency.
for idx := 0; idx < c.fromDevice.threads; idx++ {
wg.Add(1)
go wrap(ctx, func() {
defer wg.Done()
go wrap(ctx, func() {
defer wg.Done()
for {
// Consume indexed jobs until cancellation or source exhaustion.
for {
select {
case <-ctx.Done():
return
@@ -40,56 +44,71 @@ func (c *Copyer) prepare(ctx context.Context, indexed <-chan *baseJob) <-chan *w
return
}
if c.linearTargetStopped() {
continue
}
job.setStatus(jobStatusPreparing)
file, size, err := func(path string) (io.ReadCloser, int64, error) {
if c.fromDevice.linear {
file, err := os.Open(path)
if err != nil {
return nil, 0, fmt.Errorf("open src file fail, %w", err)
}
fileInfo, err := file.Stat()
if err != nil {
_ = file.Close()
return nil, 0, fmt.Errorf("get src file stat fail, %w", err)
}
return file, fileInfo.Size(), nil
continue
}
readerAt, err := mmap.Open(path)
// Enter preparation and let eligible targetless jobs reuse a valid cache entry.
job.setStatus(jobStatusPreparing)
var file io.ReadCloser
var size int64
cacheEligible := c.signatures != nil && !c.forceRehash && len(job.targets) == 0
if cacheEligible {
if hash, ok := c.signatures.lookup(job.path, job.stat); ok {
job.setCachedHash(hash)
file = io.NopCloser(bytes.NewReader(nil))
size = job.stat.size
}
}
// Cache misses and every transfer open the real source content.
if file == nil {
var err error
file, size, err = func(path string) (io.ReadCloser, int64, error) {
// Keep linear sources on ordinary descriptors for ordered reads.
if c.fromDevice.linear {
file, err := os.Open(path)
if err != nil {
return nil, 0, fmt.Errorf("open src file fail, %w", err)
}
fileInfo, err := file.Stat()
if err != nil {
_ = file.Close()
return nil, 0, fmt.Errorf("get src file stat fail, %w", err)
}
return file, fileInfo.Size(), nil
}
// Use mmap-backed readers for random-access sources, with an empty-file fallback.
readerAt, err := mmap.Open(path)
if err != nil {
return nil, 0, fmt.Errorf("open src file by mmap fail, %w", err)
}
if readerAt.Len() == 0 {
if err := readerAt.Close(); err != nil {
return nil, 0, fmt.Errorf("close empty src file by mmap fail, %w", err)
}
return io.NopCloser(bytes.NewReader(nil)), 0, nil
}
return mmap.NewReader(readerAt), int64(readerAt.Len()), nil
}(job.path)
if err != nil {
return nil, 0, fmt.Errorf("open src file by mmap fail, %w", err)
}
if readerAt.Len() == 0 {
if err := readerAt.Close(); err != nil {
return nil, 0, fmt.Errorf("close empty src file by mmap fail, %w", err)
c.reportError(job.path, "", err)
job.fail("", err)
job.setStatus(jobStatusFinished)
continue
}
return io.NopCloser(bytes.NewReader(nil)), 0, nil
}
return mmap.NewReader(readerAt), int64(readerAt.Len()), nil
}(job.path)
if err != nil {
c.reportError(job.path, "", err)
job.fail("", err)
job.setStatus(jobStatusFinished)
continue
}
wj := newWriteJob(job, file, size, c.fromDevice.linear)
// Publish the prepared reader and preserve linear-source consumption order.
wj := newWriteJob(job, file, size, c.fromDevice.linear)
select {
case ch <- wj:
case <-ctx.Done():
wj.finishSource()
return
}
if !wj.waitConsumed(ctx) {
return
}
if !wj.waitConsumed(ctx) {
return
}
}