package mount

// evictOneWritableChunk is the callback the WriteBufferAccountant invokes
// when Reserve would otherwise block. It walks open file handles and asks
// the first one that has a writable chunk to force-seal it. Sealing moves
// the chunk into the async upload path, whose completion Releases a slot
// on the accountant and unblocks the waiter.
//
// Without this hook, workloads that open many files for write but never
// fill or close any single chunk (e.g. fio 4k randwrite across nrfiles
// where nrfiles * chunkSizeLimit > writeBufferSizeMB) deadlock
// permanently: every writable chunk reserves a full chunkSize slot on
// creation, and the pipeline only releases the slot when the chunk
// finishes uploading, which only happens after the chunk fills or the
// file closes.
//
// Performance note: this does a linear scan of fhMap.inode2fh looking
// for the first handle with a writable chunk. The scan is bounded by
// the number of open-for-write file handles at the moment Reserve
// blocks, not by nrfiles in the workload: handles that have no dirty
// pages fall through EvictOneWritableChunk in O(1) under their own
// chunksLock, so the hot-path cost is just the map walk. The scan is
// already serialized by the caller's single-flight `evicting` flag
// (one eviction across the entire accountant at a time), and happens
// only when Reserve is about to block — i.e., when the cap is actually
// exhausted and we would otherwise park a writer — so the O(N) walk is
// paid at most once per chunkSize drained, not per write. Replacing
// the map with an LRU / dirty-set would trade this for per-AddPage
// bookkeeping overhead on a hotter path; we keep the scan until a
// profile shows it mattering at the scale users actually hit.
//
// Called from WriteBufferAccountant.Reserve with accountant.mu dropped.
// Must not call anything that takes accountant.mu back; see the caller's
// single-flight `evicting` flag for safety against concurrent invocation.
func (wfs *WFS) evictOneWritableChunk(needBytes int64) bool {
	wfs.fhMap.RLock()
	defer wfs.fhMap.RUnlock()
	for _, fh := range wfs.fhMap.inode2fh {
		if fh == nil || fh.dirtyPages == nil {
			continue
		}
		if fh.dirtyPages.EvictOneWritableChunk() {
			return true
		}
	}
	return false
}
