Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

188 lines
5.6 KiB
Go

package lsp
import (
"container/list"
"os"
"sync"
)
// docSession shares one bounded set of open documents across every phase
// of a single enrichment pass. It builds ONLY on the bare
// enrichOpenDoc / enrichCloseDoc lifecycle (never the shared-state
// openDocument / sourceCache tables) and owns its own per-entry content
// cache, so a file read from disk once serves every phase that touches it
// and no phase writes the provider's unsynchronised sourceCache.
//
// A file is didOpen'd at most once per (client, path) while its entry
// lives; release unpins it and leaves it warm in an LRU tail so a later
// phase reuses the already-open document instead of reopening it. When the
// documents open on a client would exceed cap, acquire first didCloses the
// oldest unpinned entries. Pinned entries are never in the LRU and so never
// close, which means a fully-pinned working set may briefly exceed cap —
// pin volume is bounded externally by fileSem, so the overshoot is small.
type docSession struct {
p *Provider
cap int // simultaneously-open ceiling per client
mu sync.Mutex
perClient map[*Client]*clientDocs
// Telemetry, all guarded by mu.
didOpens int
evictions int
curOpen int
peakOpen int
openCounts map[string]int // absPath → number of didOpens across the pass
}
// clientDocs tracks the documents open on one client. lru holds the
// refcount-0 (unpinned) entries with the oldest at the front, so eviction
// pops the least-recently-released document first. The list element Value
// is the entry's absPath.
type clientDocs struct {
open map[string]*docEntry
lru *list.List
}
// docEntry is one open document. refs counts the live acquire pins; when it
// falls to zero the entry moves onto its client's lru and becomes
// evictable. content is the file's bytes, read from disk once per entry.
type docEntry struct {
refs int
elem *list.Element
content []byte
}
// newDocSession builds a session sized to hold 2*maxParallel documents
// open per client (floor 4). The pinned working set is bounded by fileSem
// at maxParallel, so the extra headroom is the warm LRU tail that carries
// recently-released documents across phases.
func newDocSession(p *Provider) *docSession {
cp := 2 * p.maxParallel
if cp < 4 {
cp = 4
}
return &docSession{
p: p,
cap: cp,
perClient: map[*Client]*clientDocs{},
openCounts: map[string]int{},
}
}
// acquire pins absPath open on c, sending its didOpen once per entry
// lifetime and reading its bytes from disk once. The returned release
// unpins the entry; a refcount-0 entry stays open on the server (warm in
// the lru) until it is evicted or closeAll runs. err is non-nil only when
// the disk read or the didOpen notify failed — release is a no-op then.
func (s *docSession) acquire(c *Client, absPath string) ([]byte, func(), error) {
s.mu.Lock()
defer s.mu.Unlock()
cd := s.perClient[c]
if cd == nil {
cd = &clientDocs{open: map[string]*docEntry{}, lru: list.New()}
s.perClient[c] = cd
}
if e, ok := cd.open[absPath]; ok {
// Already open on this client — pin it, pulling it back off the
// lru if it was sitting there unpinned.
if e.refs == 0 && e.elem != nil {
cd.lru.Remove(e.elem)
e.elem = nil
}
e.refs++
return e.content, s.releaseFunc(c, absPath), nil
}
// Read the file before touching the server so a read error leaves no
// half-open entry behind.
content, err := os.ReadFile(absPath)
if err != nil {
return nil, func() {}, err
}
// Make room: didClose the oldest unpinned entries so this open keeps
// the client at or under cap. Evicting before the new didOpen keeps the
// server's open count from spiking above cap. A fully-pinned client has
// an empty lru and simply overshoots.
for len(cd.open) >= s.cap {
front := cd.lru.Front()
if front == nil {
break
}
evPath := front.Value.(string)
cd.lru.Remove(front)
delete(cd.open, evPath)
_ = s.p.enrichCloseDoc(c, evPath)
s.curOpen--
s.evictions++
}
if err := s.p.enrichOpenDoc(c, absPath, content); err != nil {
return nil, func() {}, err
}
cd.open[absPath] = &docEntry{refs: 1, content: content}
s.didOpens++
s.openCounts[absPath]++
s.curOpen++
if s.curOpen > s.peakOpen {
s.peakOpen = s.curOpen
}
return content, s.releaseFunc(c, absPath), nil
}
// releaseFunc returns a release closure for one pin on (c, absPath).
// Callers defer it exactly once per successful acquire; a refcount-0 entry
// re-joins the lru so a later acquire can reuse (or evict) it.
func (s *docSession) releaseFunc(c *Client, absPath string) func() {
return func() {
s.mu.Lock()
defer s.mu.Unlock()
cd := s.perClient[c]
if cd == nil {
return
}
e := cd.open[absPath]
if e == nil || e.refs == 0 {
return
}
e.refs--
if e.refs == 0 {
e.elem = cd.lru.PushBack(absPath)
}
}
}
// closeAll didCloses every still-open document on every client,
// best-effort (a doc opened on a client that later died still gets its
// close attempt, error ignored). Deferred once at the end of the pass.
func (s *docSession) closeAll() {
s.mu.Lock()
defer s.mu.Unlock()
for c, cd := range s.perClient {
for path := range cd.open {
_ = s.p.enrichCloseDoc(c, path)
s.curOpen--
}
cd.open = map[string]*docEntry{}
cd.lru.Init()
}
}
// stats returns the pass telemetry: total didOpens, the number of distinct
// paths opened more than once, LRU evictions, and the peak simultaneously
// open documents.
func (s *docSession) stats() (didOpens, reopenedFiles, evictions, peakOpen int) {
s.mu.Lock()
defer s.mu.Unlock()
reopened := 0
for _, cnt := range s.openCounts {
if cnt > 1 {
reopened++
}
}
return s.didOpens, reopened, s.evictions, s.peakOpen
}