mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-30 11:45:42 +00:00
* filer: synchronize stream pins and release them on transitions Guard chunkStream.cacher with the ReaderCache lock everywhere: mount sections share one ChunkReadAt across concurrent reads, and unsynchronized release could double-unpin. Reads served from the chunk cache now detach the stream's pin instead of retaining the previous chunk. Eviction prefers unpinned downloaders so a pinned buffer is not dropped mid-stream. A new ReleaseStream lets callers drop their pin without destroying the shared cache; S3 and WebDAV readers use it. lastChunkFid becomes atomic since concurrent mount reads can update it. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: keep eviction bounded when every downloader is pinned Both eviction paths still fall back to a pinned victim when no unpinned one exists, so abandoned stream pins cannot bypass the downloader limit or stall the memory budget. Budget eviction also rechecks the pin under the ReaderCache lock at removal time: a stream that pinned the selected victim in between keeps it mapped and the selection retries. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * filer: restore budget bookkeeping when a victim gets pinned mid-eviction removeUnpinned losing the pin race left the victim out of the idle list while still holding its reservation, making it unevictable even as the pinned fallback. Push it back when the reservation is still live. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
126 lines
2.9 KiB
Go
126 lines
2.9 KiB
Go
package filer
|
|
|
|
import (
|
|
"container/list"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/util/mem"
|
|
)
|
|
|
|
const DefaultReaderCacheMemoryLimit = 256 << 20
|
|
|
|
type ReaderCacheBudget struct {
|
|
sync.Mutex
|
|
limit int64
|
|
used int64
|
|
reservations map[*SingleChunkCacher]int64
|
|
idle list.List
|
|
idleEntries map[*SingleChunkCacher]*list.Element
|
|
changed chan struct{}
|
|
}
|
|
|
|
func NewReaderCacheBudget(limit int64) *ReaderCacheBudget {
|
|
if limit <= 0 {
|
|
limit = DefaultReaderCacheMemoryLimit
|
|
}
|
|
return &ReaderCacheBudget{
|
|
limit: limit,
|
|
reservations: make(map[*SingleChunkCacher]int64),
|
|
idleEntries: make(map[*SingleChunkCacher]*list.Element),
|
|
changed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (b *ReaderCacheBudget) reserve(s *SingleChunkCacher) error {
|
|
if s.chunkSize < 0 {
|
|
return fmt.Errorf("invalid chunk size %d", s.chunkSize)
|
|
}
|
|
if b == nil {
|
|
return nil
|
|
}
|
|
size := int64(mem.AllocationSize(s.chunkSize))
|
|
if size > b.limit {
|
|
return fmt.Errorf("chunk buffer needs %d bytes, exceeding reader cache budget %d; increase the readerCacheSizeMB budget", size, b.limit)
|
|
}
|
|
for {
|
|
b.Lock()
|
|
if size <= b.limit-b.used {
|
|
b.used += size
|
|
b.reservations[s] = size
|
|
b.Unlock()
|
|
return nil
|
|
}
|
|
// Prefer evicting an idle chunk no stream is positioned in; fall back
|
|
// to the oldest pinned one so abandoned pins cannot block the budget.
|
|
var victim *SingleChunkCacher
|
|
var entry *list.Element
|
|
pinnedVictim := false
|
|
for e := b.idle.Front(); e != nil; e = e.Next() {
|
|
c := e.Value.(*SingleChunkCacher)
|
|
if atomic.LoadInt32(&c.pins) == 0 {
|
|
victim, entry = c, e
|
|
pinnedVictim = false
|
|
break
|
|
}
|
|
if victim == nil {
|
|
victim, entry = c, e
|
|
pinnedVictim = true
|
|
}
|
|
}
|
|
if entry != nil {
|
|
b.idle.Remove(entry)
|
|
delete(b.idleEntries, victim)
|
|
b.Unlock()
|
|
if pinnedVictim {
|
|
victim.parent.remove(victim)
|
|
} else if !victim.parent.removeUnpinned(victim) {
|
|
// The victim was pinned between selection and removal: keep it
|
|
// evictable so a pin abandoned in that gap cannot wedge the
|
|
// budget, then retry the selection.
|
|
b.Lock()
|
|
if _, ok := b.reservations[victim]; ok && b.idleEntries[victim] == nil {
|
|
b.idleEntries[victim] = b.idle.PushBack(victim)
|
|
}
|
|
b.Unlock()
|
|
}
|
|
continue
|
|
}
|
|
changed := b.changed
|
|
b.Unlock()
|
|
<-changed
|
|
}
|
|
}
|
|
|
|
func (b *ReaderCacheBudget) complete(s *SingleChunkCacher) {
|
|
if b == nil {
|
|
return
|
|
}
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
if _, found := b.reservations[s]; found && b.idleEntries[s] == nil {
|
|
b.idleEntries[s] = b.idle.PushBack(s)
|
|
close(b.changed)
|
|
b.changed = make(chan struct{})
|
|
}
|
|
}
|
|
|
|
func (b *ReaderCacheBudget) release(s *SingleChunkCacher) {
|
|
if b == nil {
|
|
return
|
|
}
|
|
b.Lock()
|
|
defer b.Unlock()
|
|
if size, found := b.reservations[s]; found {
|
|
b.used -= size
|
|
delete(b.reservations, s)
|
|
if entry := b.idleEntries[s]; entry != nil {
|
|
b.idle.Remove(entry)
|
|
delete(b.idleEntries, s)
|
|
}
|
|
close(b.changed)
|
|
b.changed = make(chan struct{})
|
|
}
|
|
}
|