mirror of
https://github.com/samuelncui/acp.git
synced 2026-11-08 04:21:16 +00:00
120 lines
3.2 KiB
Go
120 lines
3.2 KiB
Go
package acp
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
mapset "github.com/deckarep/golang-set/v2"
|
|
sha256 "github.com/minio/sha256-simd"
|
|
)
|
|
|
|
const batchSize = 1 * 1024 * 1024
|
|
|
|
var sha256Pool = &sync.Pool{New: func() interface{} { return sha256.New() }}
|
|
|
|
// chunkBuffer shares immutable content; the final reference returns one backing allocation.
|
|
type chunkBuffer struct {
|
|
data []byte
|
|
refs int32
|
|
owner *readAhead
|
|
future bool
|
|
}
|
|
|
|
// New is also the private allocation seam used by resource-lifetime checks.
|
|
var chunkPool = sync.Pool{New: func() interface{} {
|
|
return &chunkBuffer{data: make([]byte, batchSize)}
|
|
}}
|
|
|
|
func (c *chunkBuffer) retain() *chunkBuffer {
|
|
atomic.AddInt32(&c.refs, 1)
|
|
return c
|
|
}
|
|
|
|
func (c *chunkBuffer) release() {
|
|
if atomic.AddInt32(&c.refs, -1) != 0 {
|
|
return
|
|
}
|
|
if c.owner != nil {
|
|
c.owner.release(c)
|
|
return
|
|
}
|
|
chunkPool.Put(c)
|
|
}
|
|
|
|
// copy admits bounded file lifetimes while data lanes and completion run independently.
|
|
func (c *StreamCopyer) copy(ctx context.Context, prepared <-chan *writeJob) <-chan *baseJob {
|
|
// One owner dispatches transfer order; public result delivery remains independent.
|
|
out := make(chan *baseJob, 128)
|
|
ahead := newReadAhead(c)
|
|
writing := make(chan struct{}, max(c.toDevice.threads, 1))
|
|
exhausted := mapset.NewSet[string]()
|
|
cntr := new(counter)
|
|
done := make(chan struct{})
|
|
reported := make(chan struct{})
|
|
go c.wrap(context.WithoutCancel(ctx), func() {
|
|
defer close(reported)
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
c.submit(&EventUpdateProgress{Bytes: atomic.LoadInt64(&cntr.bytes), Files: atomic.LoadInt64(&cntr.files)})
|
|
case <-done:
|
|
c.submit(&EventUpdateProgress{Bytes: atomic.LoadInt64(&cntr.bytes), Files: atomic.LoadInt64(&cntr.files), Finished: true})
|
|
return
|
|
}
|
|
}
|
|
})
|
|
|
|
// Managed children retain their own resources until completion; ordered turns end at data EOF.
|
|
go c.wrap(context.WithoutCancel(ctx), func() {
|
|
var children sync.WaitGroup
|
|
defer func() {
|
|
// Return backing only after all data, completion and cleanup owners have exited.
|
|
children.Wait()
|
|
ahead.close()
|
|
c.stopSpaceMonitors()
|
|
close(done)
|
|
<-reported
|
|
close(out)
|
|
}()
|
|
ready := make(chan struct{})
|
|
close(ready)
|
|
var readTurn, writeTurn <-chan struct{} = ready, ready
|
|
var offset int64
|
|
publication := newPublicationOrder()
|
|
for job := range prepared {
|
|
t := newTransfer(c, job, ahead, offset)
|
|
if job.stat != nil {
|
|
offset += max(job.stat.size, 0)
|
|
}
|
|
if c.fromDevice.linear {
|
|
t.readTurn, readTurn = readTurn, t.readDone
|
|
}
|
|
if c.toDevice.linear {
|
|
t.writeTurn, writeTurn = writeTurn, t.writeDone
|
|
}
|
|
t.reservePublications(publication)
|
|
|
|
// Reserve random-target writers in source dispatch order before their readers can hold backing.
|
|
// Linear targets instead keep speculative readers ahead of their single ordered data lane.
|
|
if !c.toDevice.linear {
|
|
select {
|
|
case writing <- struct{}{}:
|
|
t.admitted = true
|
|
case <-c.hardStop:
|
|
}
|
|
}
|
|
children.Add(1)
|
|
go c.wrap(context.WithoutCancel(ctx), func() {
|
|
defer children.Done()
|
|
defer job.releaseSlot()
|
|
t.run(ctx, out, writing, cntr, exhausted)
|
|
})
|
|
}
|
|
})
|
|
return out
|
|
}
|