lspool

package
v1.10.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package lspool manages Language Server child processes as warm workers with lifecycle management, share-until-dirty policy, and circuit breaking.

Index

Constants

View Source
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.

View Source
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.

View Source
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

View Source
var ErrMaxWorkersReached = errors.New("maximum number of workers reached")

ErrMaxWorkersReached is returned when the pool has reached its maximum worker count.

View Source
var ErrNotSupported = errors.New("capability not supported by language server")

ErrNotSupported is returned when the LS does not support a requested capability.

View Source
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

func IsMutation(method string) bool

IsMutation returns true for LSP methods that mutate document state.

Types

type ArgsModifier

type ArgsModifier interface {
	ExtraArgs(workDir string, args []string) []string
}

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

type ExperimentalCapabilities interface {
	ExperimentalCapabilities() map[string]any
}

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

func NewLSAdapter(worker *Worker) *LSAdapter

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) Hover

func (a *LSAdapter) Hover(ctx context.Context, params gen.HoverParams) (*gen.Hover, error)

Hover returns hover information for the symbol at the given position.

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.

func (*LinuxMemoryPressure) WorkerRSS

func (lp *LinuxMemoryPressure) WorkerRSS(pid int) (uint64, error)

WorkerRSS reads the VmRSS field from /proc/{pid}/status.

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

func (NoopSink) LSPoolCircuitStateSet(string, float64)

LSPoolCircuitStateSet implements MetricsSink.

func (NoopSink) LSPoolEviction

func (NoopSink) LSPoolEviction(string, string)

LSPoolEviction implements MetricsSink.

func (NoopSink) LSPoolLookup

func (NoopSink) LSPoolLookup(string, string)

LSPoolLookup implements MetricsSink.

func (NoopSink) LSPoolRestart

func (NoopSink) LSPoolRestart(string)

LSPoolRestart implements MetricsSink.

func (NoopSink) LSPoolWorkersSet

func (NoopSink) LSPoolWorkersSet(string, float64)

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

func (p *Pool) LeaseCount() int

LeaseCount returns the number of active leases.

func (*Pool) PromoteToDirty

func (p *Pool) PromoteToDirty(ctx context.Context, sessionID string) (*WorkerLease, error)

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

func (p *Pool) ReleaseLease(sessionID string)

ReleaseLease releases a session's lease. If the worker has no remaining leases, its idle TTL countdown begins.

func (*Pool) Run

func (p *Pool) Run(ctx context.Context) error

Run starts background goroutines for TTL checks and pressure eviction. Blocks until ctx is cancelled.

func (*Pool) WorkerCount

func (p *Pool) WorkerCount() int

WorkerCount returns the number of active workers.

func (*Pool) WorkerForTests

func (p *Pool) WorkerForTests(language, workDir string) *Worker

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) Command

func (w *Worker) Command() string

Command returns the LS command string.

func (*Worker) Conn

func (w *Worker) Conn() *jsonrpc.Conn

Conn returns the underlying JSON-RPC connection (for adapter use).

func (*Worker) ID

func (w *Worker) ID() string

ID returns the worker ID.

func (*Worker) Language

func (w *Worker) Language() string

Language returns the language this worker serves.

func (*Worker) Metrics

func (w *Worker) Metrics() *WorkerMetrics

Metrics returns a pointer to the worker metrics.

func (*Worker) NormalizeSymbolName

func (w *Worker) NormalizeSymbolName(name string) string

NormalizeSymbolName delegates symbol name normalization to the QuirkAdapter.

func (*Worker) Notify

func (w *Worker) Notify(ctx context.Context, method string, params interface{}) error

Notify sends a notification to the LS. Per Pitfall 2: if Initializing and method is textDocument/didOpen, buffer it.

func (*Worker) Pid

func (w *Worker) Pid() int

Pid returns the LS process ID, or -1 if not started.

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.

func (*Worker) Start

func (w *Worker) Start(ctx context.Context) error

Start launches the LS process and performs the LSP initialize handshake.

func (*Worker) State

func (w *Worker) State() WorkerState

State returns the current worker state.

func (*Worker) Stop

func (w *Worker) Stop(ctx context.Context) error

Stop performs graceful shutdown of the LS worker.

func (*Worker) WorkDir

func (w *Worker) WorkDir() string

WorkDir returns the working directory this worker is rooted at.

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.

func (*WorkerLease) Notify

func (l *WorkerLease) Notify(ctx context.Context, method string, params interface{}) error

Notify sends a notification through the lease.

func (*WorkerLease) Request

func (l *WorkerLease) Request(ctx context.Context, method string, params, result interface{}) error

Request sends a request through the lease with concurrency control. Mutations acquire a write lock; reads acquire a read lock.

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL