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
188 lines
5.6 KiB
Go
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
|
|
}
|