fix: 修复若干问题,添加java lsp
This commit is contained in:
180
backend/internal/monitor/lsp_status_monitor.go
Normal file
180
backend/internal/monitor/lsp_status_monitor.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"monica-go-completion-backend/internal/completion"
|
||||
)
|
||||
|
||||
// Config 控制探测间隔与下线阈值。
|
||||
type Config struct {
|
||||
ProbeInterval time.Duration
|
||||
ProbeTimeout time.Duration
|
||||
FailureThreshold int
|
||||
}
|
||||
|
||||
// LanguageStatus 表示单语言探测状态。
|
||||
type LanguageStatus struct {
|
||||
Language string `json:"language"`
|
||||
Online bool `json:"online"`
|
||||
LastCheckedAt time.Time `json:"lastCheckedAt"`
|
||||
LastSuccessAt time.Time `json:"lastSuccessAt,omitempty"`
|
||||
ConsecutiveFailures int `json:"consecutiveFailures"`
|
||||
LastError string `json:"lastError,omitempty"`
|
||||
}
|
||||
|
||||
// LSPStatusMonitor 周期性探测各语言 LSP 是否可用。
|
||||
type LSPStatusMonitor struct {
|
||||
specs []completion.LanguageServerSpec
|
||||
workspaceDir string
|
||||
factory completion.ClientFactory
|
||||
cfg Config
|
||||
|
||||
mu sync.RWMutex
|
||||
statuses map[string]LanguageStatus
|
||||
|
||||
stopCh chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// NewLSPStatusMonitor 创建并启动探测协程。
|
||||
func NewLSPStatusMonitor(
|
||||
specs []completion.LanguageServerSpec,
|
||||
workspaceDir string,
|
||||
factory completion.ClientFactory,
|
||||
cfg Config,
|
||||
) *LSPStatusMonitor {
|
||||
if cfg.ProbeInterval <= 0 {
|
||||
cfg.ProbeInterval = 8 * time.Second
|
||||
}
|
||||
if cfg.ProbeTimeout <= 0 {
|
||||
cfg.ProbeTimeout = 5 * time.Second
|
||||
}
|
||||
if cfg.FailureThreshold <= 0 {
|
||||
cfg.FailureThreshold = 2
|
||||
}
|
||||
|
||||
normalized := make([]completion.LanguageServerSpec, 0, len(specs))
|
||||
statuses := make(map[string]LanguageStatus)
|
||||
for _, spec := range specs {
|
||||
lang := normalizeLanguage(spec.Language)
|
||||
if lang == "" {
|
||||
continue
|
||||
}
|
||||
spec.Language = lang
|
||||
normalized = append(normalized, spec)
|
||||
statuses[lang] = LanguageStatus{
|
||||
Language: lang,
|
||||
Online: false,
|
||||
}
|
||||
}
|
||||
|
||||
m := &LSPStatusMonitor{
|
||||
specs: normalized,
|
||||
workspaceDir: workspaceDir,
|
||||
factory: factory,
|
||||
cfg: cfg,
|
||||
statuses: statuses,
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
go m.loop()
|
||||
return m
|
||||
}
|
||||
|
||||
// LspServiceStatus 返回按语言聚合的状态快照。
|
||||
func (m *LSPStatusMonitor) LspServiceStatus() map[string]any {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
out := make(map[string]any, len(m.statuses))
|
||||
for language, status := range m.statuses {
|
||||
out[language] = status
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Close 停止后台探测循环。
|
||||
func (m *LSPStatusMonitor) Close() {
|
||||
m.stopOnce.Do(func() {
|
||||
close(m.stopCh)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *LSPStatusMonitor) loop() {
|
||||
// 启动后先做一轮探测,避免首次读取全是未知状态。
|
||||
m.probeAll()
|
||||
|
||||
ticker := time.NewTicker(m.cfg.ProbeInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
m.probeAll()
|
||||
case <-m.stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LSPStatusMonitor) probeAll() {
|
||||
for _, spec := range m.specs {
|
||||
m.probeOne(spec)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *LSPStatusMonitor) probeOne(spec completion.LanguageServerSpec) {
|
||||
language := normalizeLanguage(spec.Language)
|
||||
if language == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), m.cfg.ProbeTimeout)
|
||||
defer cancel()
|
||||
|
||||
client, err := m.factory(ctx, spec, m.workspaceDir)
|
||||
if err != nil {
|
||||
m.markFailure(language, err)
|
||||
return
|
||||
}
|
||||
_ = client.Close()
|
||||
m.markSuccess(language)
|
||||
}
|
||||
|
||||
func (m *LSPStatusMonitor) markSuccess(language string) {
|
||||
now := time.Now()
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
st := m.statuses[language]
|
||||
st.LastCheckedAt = now
|
||||
st.LastSuccessAt = now
|
||||
st.ConsecutiveFailures = 0
|
||||
st.LastError = ""
|
||||
st.Online = true
|
||||
m.statuses[language] = st
|
||||
}
|
||||
|
||||
func (m *LSPStatusMonitor) markFailure(language string, err error) {
|
||||
now := time.Now()
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
st := m.statuses[language]
|
||||
st.LastCheckedAt = now
|
||||
st.ConsecutiveFailures++
|
||||
if err != nil {
|
||||
st.LastError = err.Error()
|
||||
}
|
||||
if st.ConsecutiveFailures >= m.cfg.FailureThreshold {
|
||||
st.Online = false
|
||||
}
|
||||
m.statuses[language] = st
|
||||
}
|
||||
|
||||
func normalizeLanguage(language string) string {
|
||||
return strings.ToLower(strings.TrimSpace(language))
|
||||
}
|
||||
Reference in New Issue
Block a user