Documentation
¶
Overview ¶
Package lspool manages Language Server child processes as warm workers with lifecycle management, share-until-dirty policy, and circuit breaking.
Index ¶
- Constants
- Variables
- func IsMutation(method string) bool
- type ArgsModifier
- type CircuitBreaker
- func (cb *CircuitBreaker) BackoffDuration() time.Duration
- func (cb *CircuitBreaker) CanAttempt() bool
- func (cb *CircuitBreaker) CircuitOpenErr() *serr.Error
- func (cb *CircuitBreaker) Failures() int
- func (cb *CircuitBreaker) RecordFailure()
- func (cb *CircuitBreaker) RecordSuccess()
- func (cb *CircuitBreaker) State() float64
- type CircuitHealth
- type ClangdAdapter
- func (c *ClangdAdapter) InitOptions(workDir string) map[string]any
- func (c *ClangdAdapter) NormalizeSymbolName(name string) string
- func (c *ClangdAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (c *ClangdAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- type DefaultQuirkAdapter
- func (d *DefaultQuirkAdapter) InitOptions(_ string) map[string]any
- func (d *DefaultQuirkAdapter) NormalizeSymbolName(name string) string
- func (d *DefaultQuirkAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (d *DefaultQuirkAdapter) PostInitialize(_ context.Context, _ *LSAdapter) error
- type ExperimentalCapabilities
- type GoplsAdapter
- type HealthReport
- type IntelephenseAdapter
- func (i *IntelephenseAdapter) InitOptions(_ string) map[string]any
- func (i *IntelephenseAdapter) NormalizeSymbolName(name string) string
- func (i *IntelephenseAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (i *IntelephenseAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- type JdtlsAdapter
- func (j *JdtlsAdapter) ExtraArgs(workDir string, args []string) []string
- func (j *JdtlsAdapter) InitOptions(workDir string) map[string]any
- func (j *JdtlsAdapter) NormalizeSymbolName(name string) string
- func (j *JdtlsAdapter) NotificationHandlers() map[string]func(json.RawMessage)
- func (j *JdtlsAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- func (j *JdtlsAdapter) WaitUntilJavaReady(ctx context.Context) error
- type LSAdapter
- func (a *LSAdapter) Definition(ctx context.Context, params gen.DefinitionParams) ([]gen.Location, error)
- func (a *LSAdapter) DocumentSymbol(ctx context.Context, params gen.DocumentSymbolParams) ([]gen.DocumentSymbol, error)
- func (a *LSAdapter) Formatting(ctx context.Context, params gen.DocumentFormattingParams) ([]gen.TextEdit, error)
- func (a *LSAdapter) Hover(ctx context.Context, params gen.HoverParams) (*gen.Hover, error)
- func (a *LSAdapter) Implementation(ctx context.Context, params gen.ImplementationParams) ([]gen.Location, error)
- func (a *LSAdapter) Initialize(ctx context.Context, params gen.InitializeParams) (*gen.InitializeResult, error)
- func (a *LSAdapter) References(ctx context.Context, params gen.ReferenceParams) ([]gen.Location, error)
- func (a *LSAdapter) Rename(ctx context.Context, params gen.RenameParams) (*gen.WorkspaceEdit, error)
- func (a *LSAdapter) TypeDefinition(ctx context.Context, params gen.TypeDefinitionParams) ([]gen.Location, error)
- func (a *LSAdapter) WorkspaceSymbol(ctx context.Context, params gen.WorkspaceSymbolParams) ([]gen.SymbolInformation, error)
- type LinuxMemoryPressure
- type MarkdownAdapter
- func (m *MarkdownAdapter) InitOptions(_ string) map[string]any
- func (m *MarkdownAdapter) NormalizeSymbolName(name string) string
- func (m *MarkdownAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (m *MarkdownAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- type MemoryPressure
- type MetricsSink
- type NoopSink
- type Pool
- func (p *Pool) AcquireLease(ctx context.Context, sessionID string, wsKey workspace.WorkspaceKey, ...) (*WorkerLease, error)
- func (p *Pool) HealthSnapshot() HealthReport
- func (p *Pool) LeaseCount() int
- func (p *Pool) PromoteToDirty(ctx context.Context, sessionID string) (*WorkerLease, error)
- func (p *Pool) ReleaseLease(sessionID string)
- func (p *Pool) Run(ctx context.Context) error
- func (p *Pool) WorkerCount() int
- func (p *Pool) WorkerForTests(language, workDir string) *Worker
- type PoolConfig
- type PressureLevel
- type ProcessHandle
- func (p *ProcessHandle) Conn() *jsonrpc.Conn
- func (p *ProcessHandle) Done() <-chan struct{}
- func (p *ProcessHandle) LastStderr() []string
- func (p *ProcessHandle) Pid() int
- func (p *ProcessHandle) Start(ctx context.Context, sessionPrefix string) error
- func (p *ProcessHandle) StartListen(ctx context.Context)
- func (p *ProcessHandle) Stop(ctx context.Context) error
- func (p *ProcessHandle) Wait() error
- type PyrightAdapter
- func (p *PyrightAdapter) InitOptions(_ string) map[string]any
- func (p *PyrightAdapter) NormalizeSymbolName(name string) string
- func (p *PyrightAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (p *PyrightAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- type QuirkAdapter
- type RustAnalyzerAdapter
- func (r *RustAnalyzerAdapter) ExperimentalCapabilities() map[string]any
- func (r *RustAnalyzerAdapter) InitOptions(_ string) map[string]any
- func (r *RustAnalyzerAdapter) NormalizeSymbolName(name string) string
- func (r *RustAnalyzerAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (r *RustAnalyzerAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- func (r *RustAnalyzerAdapter) WaitUntilRenameReady(ctx context.Context) bool
- type SourceKitAdapter
- func (s *SourceKitAdapter) InitOptions(_ string) map[string]any
- func (s *SourceKitAdapter) NormalizeSymbolName(name string) string
- func (s *SourceKitAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (s *SourceKitAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- type TypeScriptAdapter
- func (t *TypeScriptAdapter) InitOptions(_ string) map[string]any
- func (t *TypeScriptAdapter) NormalizeSymbolName(name string) string
- func (t *TypeScriptAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
- func (t *TypeScriptAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
- type VueAdapter
- type Worker
- func (w *Worker) Capabilities() gen.ServerCapabilities
- func (w *Worker) Command() string
- func (w *Worker) Conn() *jsonrpc.Conn
- func (w *Worker) ID() string
- func (w *Worker) Language() string
- func (w *Worker) Metrics() *WorkerMetrics
- func (w *Worker) NormalizeSymbolName(name string) string
- func (w *Worker) Notify(ctx context.Context, method string, params interface{}) error
- func (w *Worker) Pid() int
- func (w *Worker) Quirks() QuirkAdapter
- func (w *Worker) Request(ctx context.Context, method string, params interface{}, result interface{}) error
- func (w *Worker) SetQuirks(q QuirkAdapter)
- func (w *Worker) Start(ctx context.Context) error
- func (w *Worker) State() WorkerState
- func (w *Worker) Stop(ctx context.Context) error
- func (w *Worker) WorkDir() string
- type WorkerHealth
- type WorkerLease
- type WorkerMetrics
- type WorkerState
- type WorkspaceHealth
- type ZlsAdapter
Constants ¶
const ( EvictIdle = "idle" EvictPressure = "pressure" EvictCrash = "crash" EvictShutdown = "shutdown" )
Eviction reason constants — closed enum per D-13. The label space for helix_lspool_evictions_total{reason} MUST stay bounded to this set so the CI cardinality lint can prove the bound.
const ( CircuitClosed float64 = 0 CircuitHalfOpen float64 = 1 CircuitOpen float64 = 2 )
Circuit state codes per D-14. Kept as float64 so sink implementations can call Gauge.Set directly without a conversion step.
const ( LookupHit = "hit" LookupMiss = "miss" )
Lookup result constants — closed enum per Phase 53 D-04. The label space for helix_lspool_lookups_total{result} MUST stay bounded to this set so the cardinality bound test in internal/obs/metrics_labels_test.go can prove the bound 2 × N_languages.
Variables ¶
var ErrMaxWorkersReached = errors.New("maximum number of workers reached")
ErrMaxWorkersReached is returned when the pool has reached its maximum worker count.
var ErrNotSupported = errors.New("capability not supported by language server")
ErrNotSupported is returned when the LS does not support a requested capability.
var ErrWorkerNotReady = errors.New("worker is not in Ready state")
ErrWorkerNotReady is returned when a request is made to a non-Ready worker.
Functions ¶
func IsMutation ¶
IsMutation returns true for LSP methods that mutate document state.
Types ¶
type ArgsModifier ¶
ArgsModifier is an optional interface that QuirkAdapters can implement to inject extra command-line arguments when starting the LS process.
type CircuitBreaker ¶
type CircuitBreaker struct {
// contains filtered or unexported fields
}
CircuitBreaker implements decorrelated jitter backoff with restart budget and single-probe half-open for crashy LS workers (D-04, D-05, D-06).
func NewCircuitBreaker ¶
func NewCircuitBreaker(language string, maxBackoff time.Duration, restartBudget int, sink MetricsSink) *CircuitBreaker
NewCircuitBreaker creates a circuit breaker with the given language label, maximum backoff duration, restart budget, and metrics sink. A nil sink is replaced by NoopSink. restartBudget <= 0 defaults to 3. The initial state is CircuitClosed and is published immediately so that the gauge has an entry for the language from birth (D-14).
func (*CircuitBreaker) BackoffDuration ¶
func (cb *CircuitBreaker) BackoffDuration() time.Duration
BackoffDuration returns the current backoff duration.
func (*CircuitBreaker) CanAttempt ¶
func (cb *CircuitBreaker) CanAttempt() bool
CanAttempt returns true if the circuit allows an attempt. Implements:
- D-05: restart budget exhausted -> circuit stays open permanently
- D-06: single-probe half-open via atomic CAS
func (*CircuitBreaker) CircuitOpenErr ¶
func (cb *CircuitBreaker) CircuitOpenErr() *serr.Error
CircuitOpenErr creates a typed *serr.Error with Kind CircuitOpen from the current circuit state. The former CircuitOpenError metadata (language, failures, backoff, retry-after) is flattened into the Detail string per D-05. Thread-safe.
func (*CircuitBreaker) Failures ¶
func (cb *CircuitBreaker) Failures() int
Failures returns the current failure count.
func (*CircuitBreaker) RecordFailure ¶
func (cb *CircuitBreaker) RecordFailure()
RecordFailure records a failure and computes decorrelated jitter backoff per D-04: sleep = min(cap, random_between(base, prevSleep*3)). Transitions the circuit to CircuitOpen and resets the probe flag (D-06).
func (*CircuitBreaker) RecordSuccess ¶
func (cb *CircuitBreaker) RecordSuccess()
RecordSuccess resets the failure count, backoff, and probe flag. Transitions the circuit to CircuitClosed.
func (*CircuitBreaker) State ¶
func (cb *CircuitBreaker) State() float64
State returns the current circuit state (CircuitClosed, CircuitHalfOpen, or CircuitOpen).
type CircuitHealth ¶
type CircuitHealth struct {
Language string `json:"language"`
State string `json:"state"` // "closed", "half_open", "open"
Failures int `json:"failures"`
}
CircuitHealth describes the health state of a circuit breaker for a language.
type ClangdAdapter ¶
type ClangdAdapter struct {
Entry langregistry.LSEntry
}
ClangdAdapter provides C/C++ quirks for clangd. Detects compile_commands.json in the workspace.
func (*ClangdAdapter) InitOptions ¶
func (c *ClangdAdapter) InitOptions(workDir string) map[string]any
func (*ClangdAdapter) NormalizeSymbolName ¶
func (c *ClangdAdapter) NormalizeSymbolName(name string) string
func (*ClangdAdapter) NotificationHandlers ¶
func (c *ClangdAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*ClangdAdapter) PostInitialize ¶
func (c *ClangdAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
type DefaultQuirkAdapter ¶
type DefaultQuirkAdapter struct {
Entry langregistry.LSEntry
}
DefaultQuirkAdapter is a no-op implementation for low-quirk languages. It returns the entry's InitOptions unchanged and provides identity symbol normalization.
func (*DefaultQuirkAdapter) InitOptions ¶
func (d *DefaultQuirkAdapter) InitOptions(_ string) map[string]any
func (*DefaultQuirkAdapter) NormalizeSymbolName ¶
func (d *DefaultQuirkAdapter) NormalizeSymbolName(name string) string
func (*DefaultQuirkAdapter) NotificationHandlers ¶
func (d *DefaultQuirkAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*DefaultQuirkAdapter) PostInitialize ¶
func (d *DefaultQuirkAdapter) PostInitialize(_ context.Context, _ *LSAdapter) error
type ExperimentalCapabilities ¶
ExperimentalCapabilities is an optional interface that QuirkAdapters can implement to advertise LS-specific experimental client capabilities during the initialize handshake. The returned map is merged into ClientCapabilities.Experimental before "initialize" is dispatched. Used by RustAnalyzerAdapter to opt into rust-analyzer's experimental/serverStatus notification (see Phase 47 / BUG-02).
type GoplsAdapter ¶
type GoplsAdapter struct {
Entry langregistry.LSEntry
}
GoplsAdapter provides Go-specific quirks for gopls. Adds experimentalWorkspaceModule to init options and strips package prefixes from symbols.
func (*GoplsAdapter) InitOptions ¶
func (g *GoplsAdapter) InitOptions(_ string) map[string]any
func (*GoplsAdapter) NormalizeSymbolName ¶
func (g *GoplsAdapter) NormalizeSymbolName(name string) string
NormalizeSymbolName strips package prefix from Go symbols (e.g. "pkg.Foo" -> "Foo").
func (*GoplsAdapter) NotificationHandlers ¶
func (g *GoplsAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*GoplsAdapter) PostInitialize ¶
func (g *GoplsAdapter) PostInitialize(_ context.Context, _ *LSAdapter) error
type HealthReport ¶
type HealthReport struct {
Workspaces []WorkspaceHealth `json:"workspaces"`
Summary string `json:"summary,omitempty"`
}
HealthReport is the top-level health snapshot returned by Pool.HealthSnapshot().
func (*HealthReport) TotalWorkers ¶
func (r *HealthReport) TotalWorkers() int
TotalWorkers returns the total number of workers across all workspaces.
type IntelephenseAdapter ¶
type IntelephenseAdapter struct {
Entry langregistry.LSEntry
}
IntelephenseAdapter provides PHP-specific quirks for intelephense. Intelephense requires textDocument/didOpen before textDocument/* operations work.
func (*IntelephenseAdapter) InitOptions ¶
func (i *IntelephenseAdapter) InitOptions(_ string) map[string]any
func (*IntelephenseAdapter) NormalizeSymbolName ¶
func (i *IntelephenseAdapter) NormalizeSymbolName(name string) string
func (*IntelephenseAdapter) NotificationHandlers ¶
func (i *IntelephenseAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*IntelephenseAdapter) PostInitialize ¶
func (i *IntelephenseAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
type JdtlsAdapter ¶
type JdtlsAdapter struct {
Entry langregistry.LSEntry
// contains filtered or unexported fields
}
JdtlsAdapter provides Java-specific quirks for Eclipse JDT Language Server. Creates workspace data directory and configures jdtls-specific init options.
Phase 56 readiness extension: observes language/status notifications and exposes WaitUntilJavaReady so callers can block until BOTH ServiceReady and ProjectStatus=OK have been seen (D-07, D-08, D-09).
func (*JdtlsAdapter) ExtraArgs ¶
func (j *JdtlsAdapter) ExtraArgs(workDir string, args []string) []string
ExtraArgs injects -data <dir> so jdtls has a workspace-specific data directory. Without this, jdtls may fail to index or conflict across workspaces.
Test-only override: if the environment variable HELIX_TEST_JDTLS_DATA_DIR is set to a non-empty path, it is used verbatim in place of workDir/.jdtls-data. This lets integration tests (test/integration/jdtlscache) share a warm jdtls workspace across test runs (Phase 48, BUG-03). Production never sets this variable.
func (*JdtlsAdapter) InitOptions ¶
func (j *JdtlsAdapter) InitOptions(workDir string) map[string]any
func (*JdtlsAdapter) NormalizeSymbolName ¶
func (j *JdtlsAdapter) NormalizeSymbolName(name string) string
func (*JdtlsAdapter) NotificationHandlers ¶
func (j *JdtlsAdapter) NotificationHandlers() map[string]func(json.RawMessage)
NotificationHandlers handles jdtls language/status notifications. Payload schema (per legacy/src/solidlsp/language_servers/eclipse_jdtls.py:861-867): {type: string, message: string}. Phase 56 D-06: handler runs synchronously on the Listen goroutine — operations are limited to json.Unmarshal + mutex-guarded channel close. No I/O, no LSP calls back to the same conn.
func (*JdtlsAdapter) PostInitialize ¶
func (j *JdtlsAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
PostInitialize opens every Java source file so jdtls has each document available for textDocument/* operations. Opening only the first file is insufficient: workspace/symbol works off the project index, but hover, references, and replace_symbol_body need the specific file to have been didOpen'd first — otherwise jdtls returns an empty hover ({"contents":""}) and "symbol not found" for replace_symbol_body on un-opened files.
func (*JdtlsAdapter) WaitUntilJavaReady ¶
func (j *JdtlsAdapter) WaitUntilJavaReady(ctx context.Context) error
WaitUntilJavaReady blocks until BOTH ServiceReady AND ProjectStatus=OK have been observed via language/status notifications, ctx is cancelled, or javaReadinessTimeout elapses (whichever fires first). Returns nil iff both gates closed. Phase 56 D-08 + D-09.
type LSAdapter ¶
type LSAdapter struct {
// contains filtered or unexported fields
}
LSAdapter provides typed LSP methods on top of a Worker. Each method delegates to worker.Request and handles capability checking.
func NewLSAdapter ¶
NewLSAdapter creates a new LSP adapter wrapping the given worker.
func (*LSAdapter) Definition ¶
func (a *LSAdapter) Definition(ctx context.Context, params gen.DefinitionParams) ([]gen.Location, error)
Definition returns the definition location(s) for the symbol at the given position.
func (*LSAdapter) DocumentSymbol ¶
func (a *LSAdapter) DocumentSymbol(ctx context.Context, params gen.DocumentSymbolParams) ([]gen.DocumentSymbol, error)
DocumentSymbol returns the document symbols (outline) for the given document.
func (*LSAdapter) Formatting ¶
func (a *LSAdapter) Formatting(ctx context.Context, params gen.DocumentFormattingParams) ([]gen.TextEdit, error)
Formatting formats the entire document.
func (*LSAdapter) Implementation ¶
func (a *LSAdapter) Implementation(ctx context.Context, params gen.ImplementationParams) ([]gen.Location, error)
Implementation returns implementation locations for the symbol at the given position.
func (*LSAdapter) Initialize ¶
func (a *LSAdapter) Initialize(ctx context.Context, params gen.InitializeParams) (*gen.InitializeResult, error)
Initialize sends the LSP initialize request. Typically called by Worker.Start() internally.
func (*LSAdapter) References ¶
func (a *LSAdapter) References(ctx context.Context, params gen.ReferenceParams) ([]gen.Location, error)
References returns all reference locations for the symbol at the given position.
func (*LSAdapter) Rename ¶
func (a *LSAdapter) Rename(ctx context.Context, params gen.RenameParams) (*gen.WorkspaceEdit, error)
Rename performs a rename refactoring across the workspace.
func (*LSAdapter) TypeDefinition ¶
func (a *LSAdapter) TypeDefinition(ctx context.Context, params gen.TypeDefinitionParams) ([]gen.Location, error)
TypeDefinition returns the type definition location(s) for the symbol at the given position.
func (*LSAdapter) WorkspaceSymbol ¶
func (a *LSAdapter) WorkspaceSymbol(ctx context.Context, params gen.WorkspaceSymbolParams) ([]gen.SymbolInformation, error)
WorkspaceSymbol searches for symbols across the workspace.
type LinuxMemoryPressure ¶
type LinuxMemoryPressure struct{}
LinuxMemoryPressure reads memory pressure from Linux PSI and /proc.
func NewLinuxMemoryPressure ¶
func NewLinuxMemoryPressure() *LinuxMemoryPressure
NewLinuxMemoryPressure creates a Linux-specific memory pressure detector.
func (*LinuxMemoryPressure) Level ¶
func (lp *LinuxMemoryPressure) Level() PressureLevel
Level reads /proc/pressure/memory and maps PSI avg10 to a PressureLevel.
type MarkdownAdapter ¶
type MarkdownAdapter struct {
Entry langregistry.LSEntry
}
MarkdownAdapter provides Markdown-specific quirks for marksman. marksman requires textDocument/didOpen before textDocument/* operations work.
func (*MarkdownAdapter) InitOptions ¶
func (m *MarkdownAdapter) InitOptions(_ string) map[string]any
func (*MarkdownAdapter) NormalizeSymbolName ¶
func (m *MarkdownAdapter) NormalizeSymbolName(name string) string
func (*MarkdownAdapter) NotificationHandlers ¶
func (m *MarkdownAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*MarkdownAdapter) PostInitialize ¶
func (m *MarkdownAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
type MemoryPressure ¶
type MemoryPressure interface {
// Level returns the current system memory pressure level.
Level() PressureLevel
// WorkerRSS returns the resident set size in bytes for the given process ID.
WorkerRSS(pid int) (uint64, error)
}
MemoryPressure provides system memory pressure information.
type MetricsSink ¶
type MetricsSink interface {
// LSPoolWorkersSet adjusts the active worker gauge for a language by
// delta. Positive on spawn, negative on destroy/evict.
LSPoolWorkersSet(language string, delta float64)
// LSPoolEviction increments the eviction counter for a language and
// reason. reason must be one of the EvictXxx constants below (D-13).
LSPoolEviction(language, reason string)
// LSPoolCircuitStateSet publishes the circuit breaker state for a
// language: CircuitClosed / CircuitHalfOpen / CircuitOpen (D-14).
LSPoolCircuitStateSet(language string, state float64)
// LSPoolRestart increments the restart counter for a language (D-15).
LSPoolRestart(language string)
// LSPoolLookup increments the helix_lspool_lookups_total counter for a
// (language, result) pair. result must be one of the LookupXxx constants
// below (Phase 53 D-04 closed enum). Emitted at the share-vs-spawn
// boundary in Pool.AcquireLease (Phase 53 D-02). Phase 53 D-14.
LSPoolLookup(language, result string)
}
MetricsSink is the minimal surface lspool needs from the observability layer. Implemented by *obs.Metrics (checked at wire-up time in internal/daemon); lspool itself never imports internal/obs (D-08).
Method signatures are frozen to match the *obs.Metrics helpers declared in internal/obs/metrics.go. A compile-time assertion in internal/daemon/ wiring_test.go pins the two together.
type NoopSink ¶
type NoopSink struct{}
NoopSink is used by tests and bootstrap paths where metrics are not wired. All methods are lock-free no-ops; a value-receiver keeps them trivially inlinable.
func (NoopSink) LSPoolCircuitStateSet ¶
LSPoolCircuitStateSet implements MetricsSink.
func (NoopSink) LSPoolEviction ¶
LSPoolEviction implements MetricsSink.
func (NoopSink) LSPoolLookup ¶
LSPoolLookup implements MetricsSink.
func (NoopSink) LSPoolRestart ¶
LSPoolRestart implements MetricsSink.
func (NoopSink) LSPoolWorkersSet ¶
LSPoolWorkersSet implements MetricsSink.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool manages a pool of LS workers with TTL, pressure eviction, and share-until-dirty policy.
func NewPool ¶
func NewPool(cfg PoolConfig, registry *langregistry.Registry, installer *langregistry.Installer, pressure MemoryPressure, logger *slog.Logger, metrics MetricsSink, tracer trace.Tracer) *Pool
NewPool creates a new LS worker pool. The registry provides language server resolution for worker creation. The installer uses three-tier resolution (PATH/download/error) to find LS binaries. metrics is the MetricsSink receiving worker lifecycle and circuit state events; pass NoopSink{} (or nil, which is converted) to disable.
func (*Pool) AcquireLease ¶
func (p *Pool) AcquireLease(ctx context.Context, sessionID string, wsKey workspace.WorkspaceKey, dirty bool) (*WorkerLease, error)
AcquireLease creates a lease binding a session to an LS worker. Per D-02: clean sessions share warm workers; dirty sessions get dedicated workers.
func (*Pool) HealthSnapshot ¶
func (p *Pool) HealthSnapshot() HealthReport
HealthSnapshot captures the current health state of all workers and circuits under a single RLock acquisition. The returned HealthReport contains value copies -- no references to pool internals are retained.
func (*Pool) LeaseCount ¶
LeaseCount returns the number of active leases.
func (*Pool) PromoteToDirty ¶
PromoteToDirty promotes a clean shared lease to a dirty dedicated worker. Per D-08: spawns new worker, transfers session, releases old shared lease.
func (*Pool) ReleaseLease ¶
ReleaseLease releases a session's lease. If the worker has no remaining leases, its idle TTL countdown begins.
func (*Pool) Run ¶
Run starts background goroutines for TTL checks and pressure eviction. Blocks until ctx is cancelled.
func (*Pool) WorkerCount ¶
WorkerCount returns the number of active workers.
func (*Pool) WorkerForTests ¶
WorkerForTests returns the first worker matching (language, workDir). TEST-ONLY — used by integration tests to reach into adapter readiness state (e.g. Phase 56 TestRustAnalyzer_NotificationDispatchEndToEnd, waitJavaReady). Do NOT use this in production code paths; use AcquireLease instead.
type PoolConfig ¶
type PoolConfig struct {
BaseTTL int // seconds, default 300
CeilingTTL int // seconds, default 3600
MaxWorkers int // default 10
RSSHardCapMB int // default 2048
PressureCheckInterval int // seconds, default 10
RestartBudget int // default 3, consecutive crashes before circuit stays open (D-05)
}
PoolConfig holds configuration for the LS worker pool.
func DefaultPoolConfig ¶
func DefaultPoolConfig() PoolConfig
DefaultPoolConfig returns the default pool configuration.
type PressureLevel ¶
type PressureLevel int
PressureLevel represents the system memory pressure level.
const ( // PressureNone indicates no memory pressure. PressureNone PressureLevel = iota // PressureLow indicates low memory pressure. PressureLow // PressureMedium indicates medium memory pressure. PressureMedium // PressureHigh indicates high memory pressure -- eviction should start. PressureHigh // PressureCritical indicates critical memory pressure -- aggressive eviction. PressureCritical )
func (PressureLevel) String ¶
func (p PressureLevel) String() string
String returns the string representation of the pressure level.
type ProcessHandle ¶
type ProcessHandle struct {
// contains filtered or unexported fields
}
ProcessHandle manages an LS child process with safe pipe I/O goroutine separation. Per research Pattern 1 (Pitfall 1): never do pipe I/O from the Wait/reaper goroutine. Separate goroutines for reading, writing, and waiting.
func NewProcessHandle ¶
func NewProcessHandle(command string, args []string, workDir string, env []string, logger *slog.Logger, tracer trace.Tracer) *ProcessHandle
NewProcessHandle creates a new ProcessHandle for the given LS command.
tracer is propagated to the jsonrpc.Conn created during Start. A nil tracer falls back to a noop tracer (Phase 55 D-01).
func (*ProcessHandle) Conn ¶
func (p *ProcessHandle) Conn() *jsonrpc.Conn
Conn returns the JSON-RPC connection. Only valid after Start.
func (*ProcessHandle) Done ¶
func (p *ProcessHandle) Done() <-chan struct{}
Done returns a channel that is closed when the process exits.
func (*ProcessHandle) LastStderr ¶
func (p *ProcessHandle) LastStderr() []string
LastStderr returns the last captured stderr lines for crash diagnostics.
func (*ProcessHandle) Pid ¶
func (p *ProcessHandle) Pid() int
Pid returns the process ID, or -1 if not started.
func (*ProcessHandle) Start ¶
func (p *ProcessHandle) Start(ctx context.Context, sessionPrefix string) error
Start spawns the LS process and creates the jsonrpc.Conn, but does NOT begin reading frames. Callers MUST invoke StartListen(ctx) AFTER setting Conn().OnNotification (if needed) to begin dispatch. Phase 56 D-02.
The sessionPrefix is used for JSON-RPC request ID generation to avoid collisions.
func (*ProcessHandle) StartListen ¶
func (p *ProcessHandle) StartListen(ctx context.Context)
StartListen begins the JSON-RPC dispatch loop. Caller MUST set Conn().OnNotification (if needed) before invoking StartListen — otherwise notifications received before assignment are silently dropped (this is the exact bug Phase 56 fixes; see jsonrpc.Conn.Listen comment).
func (*ProcessHandle) Stop ¶
func (p *ProcessHandle) Stop(ctx context.Context) error
Stop performs graceful shutdown: LSP shutdown request -> exit notification -> SIGTERM -> SIGKILL.
func (*ProcessHandle) Wait ¶
func (p *ProcessHandle) Wait() error
Wait blocks until the process exits.
type PyrightAdapter ¶
type PyrightAdapter struct {
Entry langregistry.LSEntry
}
PyrightAdapter provides Python-specific quirks for pyright-langserver. Pyright needs a file opened via didOpen before workspace/symbol returns results.
func (*PyrightAdapter) InitOptions ¶
func (p *PyrightAdapter) InitOptions(_ string) map[string]any
func (*PyrightAdapter) NormalizeSymbolName ¶
func (p *PyrightAdapter) NormalizeSymbolName(name string) string
func (*PyrightAdapter) NotificationHandlers ¶
func (p *PyrightAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*PyrightAdapter) PostInitialize ¶
func (p *PyrightAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
type QuirkAdapter ¶
type QuirkAdapter interface {
// InitOptions returns language-specific initialization options.
// workDir is the workspace root for workspace-dependent options.
InitOptions(workDir string) map[string]any
// NotificationHandlers returns handlers for LS-specific notifications,
// keyed by JSON-RPC method name (e.g. "experimental/serverStatus",
// "language/status"). Returning nil or an empty map means this adapter
// declines to observe any notifications.
//
// Phase 56 D-06 contract: each handler runs synchronously on the
// jsonrpc.Conn.Listen goroutine in the worker. Handlers MUST be
// non-blocking — atomic ops, channel close/signal, or mutex-guarded
// flag flips only. Handlers MUST NOT do I/O, MUST NOT call back into
// the same conn (e.g. issuing another LSP request via the worker), and
// MUST NOT block on user-controlled timing. A misbehaving handler will
// stall every subsequent notification on this worker. Panics are
// recovered by the dispatcher (see lspool.buildDispatcher) and logged
// at Error level, but the originating handler is still considered buggy
// and should be fixed.
NotificationHandlers() map[string]func(params json.RawMessage)
// NormalizeSymbolName adjusts symbol names for language conventions.
NormalizeSymbolName(name string) string
// PostInitialize is called after successful LSP initialize handshake.
PostInitialize(ctx context.Context, adapter *LSAdapter) error
}
QuirkAdapter provides per-language behavioral hooks for LS workers. Languages with no special behavior use DefaultQuirkAdapter.
func GetQuirkAdapter ¶
func GetQuirkAdapter(entry langregistry.LSEntry) QuirkAdapter
GetQuirkAdapter returns the language-specific QuirkAdapter for the given entry. If no specific adapter exists, returns a DefaultQuirkAdapter wrapping the entry.
type RustAnalyzerAdapter ¶
type RustAnalyzerAdapter struct {
Entry langregistry.LSEntry
// contains filtered or unexported fields
}
RustAnalyzerAdapter provides Rust-specific quirks for rust-analyzer. Enables cargo build scripts in initialization options and wires the experimental/serverStatus notification handler so edit.RenameSymbol can wait for rename-relevant quiescence before dispatching textDocument/rename (BUG-02). Per the Phase 47 RCA, rust-analyzer 1.90 returns "No references found at position" from textDocument/rename whenever the per-position analysis is still cold; the quiescent signal is the deterministic readiness gate.
Semantic-accuracy note (D-05): when the client-side rename fallback (introduced in Plan 02) runs, cross-crate trait-impl discovery and macro-expansion rename corners are NOT matched with native rust-analyzer fidelity. See BUG-DEFER-02.
func (*RustAnalyzerAdapter) ExperimentalCapabilities ¶
func (r *RustAnalyzerAdapter) ExperimentalCapabilities() map[string]any
ExperimentalCapabilities opts rust-analyzer into the experimental/serverStatus notification stream. Without this advertisement, rust-analyzer will not emit the quiescent signal and the rename readiness gate degrades to timeout-only.
func (*RustAnalyzerAdapter) InitOptions ¶
func (r *RustAnalyzerAdapter) InitOptions(_ string) map[string]any
func (*RustAnalyzerAdapter) NormalizeSymbolName ¶
func (r *RustAnalyzerAdapter) NormalizeSymbolName(name string) string
func (*RustAnalyzerAdapter) NotificationHandlers ¶
func (r *RustAnalyzerAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
NotificationHandlers returns a handler for experimental/serverStatus that flips the adapter's readiness flag. Payload schema (per rust-analyzer LSP extensions docs): {health: "ok"|"warning"|"error", quiescent: bool, message: string}. Unknown fields are discarded; malformed payloads are a no-op (T-47-01).
func (*RustAnalyzerAdapter) PostInitialize ¶
func (r *RustAnalyzerAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
func (*RustAnalyzerAdapter) WaitUntilRenameReady ¶
func (r *RustAnalyzerAdapter) WaitUntilRenameReady(ctx context.Context) bool
WaitUntilRenameReady blocks until rust-analyzer reports quiescent=true via experimental/serverStatus, ctx is cancelled, or renameReadinessTimeout elapses. Returns true iff the server reached quiescence before the timeout or cancellation. Safe to call concurrently; Plan 02 consumes this via optional-interface type assertion from internal/kernel/edit.
type SourceKitAdapter ¶
type SourceKitAdapter struct {
Entry langregistry.LSEntry
}
SourceKitAdapter provides Swift-specific quirks for sourcekit-lsp. sourcekit-lsp requires textDocument/didOpen before textDocument/* operations work.
func (*SourceKitAdapter) InitOptions ¶
func (s *SourceKitAdapter) InitOptions(_ string) map[string]any
func (*SourceKitAdapter) NormalizeSymbolName ¶
func (s *SourceKitAdapter) NormalizeSymbolName(name string) string
func (*SourceKitAdapter) NotificationHandlers ¶
func (s *SourceKitAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*SourceKitAdapter) PostInitialize ¶
func (s *SourceKitAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
type TypeScriptAdapter ¶
type TypeScriptAdapter struct {
Entry langregistry.LSEntry
}
TypeScriptAdapter provides TypeScript-specific quirks for typescript-language-server. tsserver only creates a "project" after a file is opened via didOpen. Without this, workspace/symbol fails with "No Project" until a file is opened.
func (*TypeScriptAdapter) InitOptions ¶
func (t *TypeScriptAdapter) InitOptions(_ string) map[string]any
func (*TypeScriptAdapter) NormalizeSymbolName ¶
func (t *TypeScriptAdapter) NormalizeSymbolName(name string) string
func (*TypeScriptAdapter) NotificationHandlers ¶
func (t *TypeScriptAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*TypeScriptAdapter) PostInitialize ¶
func (t *TypeScriptAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error
type VueAdapter ¶
type VueAdapter struct {
Entry langregistry.LSEntry
}
VueAdapter provides Vue-specific quirks. Vue language servers often require a companion TypeScript server.
func (*VueAdapter) InitOptions ¶
func (v *VueAdapter) InitOptions(_ string) map[string]any
func (*VueAdapter) NormalizeSymbolName ¶
func (v *VueAdapter) NormalizeSymbolName(name string) string
func (*VueAdapter) NotificationHandlers ¶
func (v *VueAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*VueAdapter) PostInitialize ¶
func (v *VueAdapter) PostInitialize(_ context.Context, _ *LSAdapter) error
type Worker ¶
type Worker struct {
// contains filtered or unexported fields
}
Worker represents a single LS worker with a 5-state machine.
func NewWorker ¶
func NewWorker(id, language, workDir string, lsCommand string, lsArgs []string, logger *slog.Logger, tracer trace.Tracer) *Worker
NewWorker creates a new LS worker.
tracer is propagated to the underlying jsonrpc.Conn so each outbound LS JSON-RPC frame produces a lspool.lsp.{method} child span. A nil tracer falls back to a noop tracer (Phase 55 D-01).
func (*Worker) Capabilities ¶
func (w *Worker) Capabilities() gen.ServerCapabilities
Capabilities returns the server capabilities reported during initialization.
func (*Worker) Metrics ¶
func (w *Worker) Metrics() *WorkerMetrics
Metrics returns a pointer to the worker metrics.
func (*Worker) NormalizeSymbolName ¶
NormalizeSymbolName delegates symbol name normalization to the QuirkAdapter.
func (*Worker) Notify ¶
Notify sends a notification to the LS. Per Pitfall 2: if Initializing and method is textDocument/didOpen, buffer it.
func (*Worker) Quirks ¶
func (w *Worker) Quirks() QuirkAdapter
Quirks returns the QuirkAdapter configured on this worker, or nil if none. Exposed for optional-interface type assertions by callers that need to detect per-LS capabilities (e.g., edit.RenameSymbol dispatching on *RustAnalyzerAdapter for the rename readiness gate and override path).
func (*Worker) Request ¶
func (w *Worker) Request(ctx context.Context, method string, params interface{}, result interface{}) error
Request sends a request to the LS, gated on Ready state. When the calling context carries a recording span (i.e. tracing is sampled), an "ls.request" span event is emitted with lsp.method, lsp.language, and lsp.duration_ms attributes. Gated on span.IsRecording() so the tracing-off path allocates nothing (D-17 budget). This is a span EVENT, not a child span, per D-06.
func (*Worker) SetQuirks ¶
func (w *Worker) SetQuirks(q QuirkAdapter)
SetQuirks sets the QuirkAdapter for this worker. Must be called before Start.
type WorkerHealth ¶
type WorkerHealth struct {
ID string `json:"id"`
Language string `json:"language"`
WorkDir string `json:"work_dir"`
Command string `json:"command"`
State string `json:"state"` // "healthy", "healthy (indexing)", "degraded", "failed"
Capabilities []string `json:"capabilities"` // e.g. ["hover", "definition", "references"]
Indexing bool `json:"indexing"`
IndexPct int `json:"index_pct,omitempty"`
}
WorkerHealth describes the health state of a single LS worker.
type WorkerLease ¶
type WorkerLease struct {
SessionID string
Worker *Worker
Dirty bool
// contains filtered or unexported fields
}
WorkerLease binds a session to an LS worker. Per D-11: mutations are serialized via write lock, reads are parallel via read lock.
func NewWorkerLease ¶
func NewWorkerLease(sessionID string, worker *Worker, dirty bool) *WorkerLease
NewWorkerLease creates a new lease binding a session to a worker.
func (*WorkerLease) Adapter ¶
func (l *WorkerLease) Adapter() QuirkAdapter
Adapter returns the QuirkAdapter of the lease's worker, or nil if the lease or its worker is unset. Exposed so higher layers (e.g., edit.RenameSymbol) can perform optional-interface type assertions to select per-LS behaviour without reaching through Worker directly.
type WorkerMetrics ¶
type WorkerMetrics struct {
StartedAt time.Time
LastUsedAt time.Time
UseCount int64
ReuseTimes []time.Time // last N reuse timestamps for gap calculation
ReuseScore float64 // decaying score per D-03
ColdStartMs int64 // time from Start to Ready in milliseconds
// contains filtered or unexported fields
}
WorkerMetrics tracks usage statistics for adaptive TTL computation.
func (*WorkerMetrics) IdleDuration ¶
func (m *WorkerMetrics) IdleDuration() time.Duration
IdleDuration returns the duration since last use.
func (*WorkerMetrics) OnReuse ¶
func (m *WorkerMetrics) OnReuse()
OnReuse records a reuse event and updates the decaying score.
func (*WorkerMetrics) TTL ¶
func (m *WorkerMetrics) TTL(baseTTL, ceilingTTL int) int
TTL computes the adaptive TTL based on decaying reuse score (per D-03). Returns the TTL in seconds.
type WorkerState ¶
type WorkerState int32
WorkerState represents the lifecycle state of an LS worker.
const ( // WorkerStarting is the initial state before the process is launched. WorkerStarting WorkerState = iota // WorkerInitializing is the state during LSP initialize/initialized handshake. WorkerInitializing // WorkerReady means the worker is fully initialized and can handle requests. WorkerReady // WorkerShuttingDown means a graceful shutdown is in progress. WorkerShuttingDown // WorkerStopped means the worker has fully stopped. WorkerStopped )
func (WorkerState) String ¶
func (s WorkerState) String() string
String returns the string representation of the worker state.
type WorkspaceHealth ¶
type WorkspaceHealth struct {
Root string `json:"root"`
Languages []string `json:"languages"`
Workers []WorkerHealth `json:"workers"`
Circuits []CircuitHealth `json:"circuits"`
}
WorkspaceHealth groups workers and circuits for a single workspace root.
type ZlsAdapter ¶
type ZlsAdapter struct {
Entry langregistry.LSEntry
}
ZlsAdapter provides Zig-specific quirks for zls. zls requires textDocument/didOpen before textDocument/* operations work.
func (*ZlsAdapter) InitOptions ¶
func (z *ZlsAdapter) InitOptions(_ string) map[string]any
func (*ZlsAdapter) NormalizeSymbolName ¶
func (z *ZlsAdapter) NormalizeSymbolName(name string) string
func (*ZlsAdapter) NotificationHandlers ¶
func (z *ZlsAdapter) NotificationHandlers() map[string]func(params json.RawMessage)
func (*ZlsAdapter) PostInitialize ¶
func (z *ZlsAdapter) PostInitialize(ctx context.Context, adapter *LSAdapter) error