feat: enhance API and session management with Nacos and Redis integration
- Add Nacos registry for service registration and deregistration. - Implement Redis registry for session management with heartbeat and session claiming. - Improve completion service with session handling and request validation. - Enhance WebSocket handling for completion requests with JSON-RPC support. - Add tests for new registry implementations and completion manager functionalities. - Refactor existing code for better readability and maintainability.
This commit is contained in:
@@ -13,35 +13,40 @@ import (
|
||||
var ErrUnsupportedLanguage = errors.New("unsupported language")
|
||||
var ErrTooManySessions = errors.New("too many active lsp sessions")
|
||||
|
||||
// RuntimeClient 是带生命周期管理能力的补全客户端。
|
||||
type RuntimeClient interface {
|
||||
Client
|
||||
Close() error
|
||||
}
|
||||
|
||||
// SessionRegistry 抽象跨实例会话归属协调能力(如 Redis)。
|
||||
type SessionRegistry interface {
|
||||
ClaimSession(ctx context.Context, language, sessionID string) (ownerID string, ownerEndpoint string, err error)
|
||||
ReleaseSession(ctx context.Context, language, sessionID string) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// LanguageServerSpec 描述某种语言对应的 LSP 启动参数。
|
||||
type LanguageServerSpec struct {
|
||||
Language string
|
||||
LanguageID string
|
||||
Command string
|
||||
Args []string
|
||||
Language string // 语言名(如 go/typescript),用于路由匹配。
|
||||
LanguageID string // 传给 LSP 的 languageId。
|
||||
Command string // LSP 可执行命令。
|
||||
Args []string // LSP 启动参数。
|
||||
}
|
||||
|
||||
type ClientFactory func(ctx context.Context, spec LanguageServerSpec, workspaceDir string) (RuntimeClient, error)
|
||||
|
||||
// ManagerConfig 控制会话池容量、TTL 与实例信息。
|
||||
type ManagerConfig struct {
|
||||
WorkspaceDir string
|
||||
MaxSessions int
|
||||
SessionTTL time.Duration
|
||||
CleanupInterval time.Duration
|
||||
InstanceID string
|
||||
Registry SessionRegistry
|
||||
WorkspaceDir string // LSP 进程工作区目录。
|
||||
MaxSessions int // 本实例会话上限。
|
||||
SessionTTL time.Duration // 会话空闲超时。
|
||||
CleanupInterval time.Duration // 会话清理周期。
|
||||
InstanceID string // 当前实例 ID。
|
||||
Registry SessionRegistry // 可选分布式会话注册中心。
|
||||
}
|
||||
|
||||
// Manager 按 language/session 复用 LSP 会话,并负责清理与淘汰。
|
||||
type Manager struct {
|
||||
mu sync.Mutex
|
||||
|
||||
@@ -63,6 +68,7 @@ type managedSession struct {
|
||||
createdAt time.Time
|
||||
}
|
||||
|
||||
// ErrSessionOwnedByOtherInstance 表示会话已被其他实例持有。
|
||||
type ErrSessionOwnedByOtherInstance struct {
|
||||
OwnerID string
|
||||
OwnerEndpoint string
|
||||
@@ -75,6 +81,7 @@ func (e *ErrSessionOwnedByOtherInstance) Error() string {
|
||||
return fmt.Sprintf("session owned by another instance: %s", e.OwnerID)
|
||||
}
|
||||
|
||||
// NewManager 构建会话管理器并启动后台清理协程。
|
||||
func NewManager(config ManagerConfig, specs []LanguageServerSpec, factory ClientFactory) *Manager {
|
||||
if config.MaxSessions <= 0 {
|
||||
config.MaxSessions = 256
|
||||
@@ -109,6 +116,7 @@ func NewManager(config ManagerConfig, specs []LanguageServerSpec, factory Client
|
||||
return m
|
||||
}
|
||||
|
||||
// Complete 处理补全请求,包含语言匹配、会话归属、会话复用/创建。
|
||||
func (m *Manager) Complete(ctx context.Context, req Request) (Response, error) {
|
||||
language := normalizeLanguage(req.Language)
|
||||
if language == "" {
|
||||
@@ -124,6 +132,7 @@ func (m *Manager) Complete(ctx context.Context, req Request) (Response, error) {
|
||||
sessionID := normalizeSessionID(req.SessionID)
|
||||
|
||||
if m.config.Registry != nil {
|
||||
// 分布式模式下先声明会话归属,避免多实例并发写同一会话。
|
||||
ownerID, ownerEndpoint, err := m.config.Registry.ClaimSession(ctx, language, sessionID)
|
||||
if err != nil {
|
||||
return Response{}, err
|
||||
@@ -154,6 +163,7 @@ func (m *Manager) Complete(ctx context.Context, req Request) (Response, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ActiveSessions 统计当前实例内各语言活跃会话数。
|
||||
func (m *Manager) ActiveSessions() map[string]int {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
@@ -165,6 +175,7 @@ func (m *Manager) ActiveSessions() map[string]int {
|
||||
return out
|
||||
}
|
||||
|
||||
// Close 停止后台任务并释放所有会话与注册中心资源。
|
||||
func (m *Manager) Close() error {
|
||||
m.stoppedOnce.Do(func() {
|
||||
close(m.stopCh)
|
||||
@@ -186,6 +197,7 @@ func (m *Manager) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupLoop 周期清理闲置会话。
|
||||
func (m *Manager) cleanupLoop() {
|
||||
ticker := time.NewTicker(m.config.CleanupInterval)
|
||||
defer ticker.Stop()
|
||||
@@ -200,6 +212,7 @@ func (m *Manager) cleanupLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupIdleSessions 关闭超过 TTL 未使用的会话。
|
||||
func (m *Manager) cleanupIdleSessions() {
|
||||
cutoff := time.Now().Add(-m.config.SessionTTL)
|
||||
|
||||
@@ -218,6 +231,7 @@ func (m *Manager) cleanupIdleSessions() {
|
||||
}
|
||||
}
|
||||
|
||||
// getOrCreateSession 返回已有会话,或按需新建一个会话。
|
||||
func (m *Manager) getOrCreateSession(
|
||||
ctx context.Context,
|
||||
sessionKey string,
|
||||
@@ -258,6 +272,7 @@ func (m *Manager) getOrCreateSession(
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if existing, ok := m.sessions[sessionKey]; ok {
|
||||
// 并发竞争下可能已经被其他协程创建,直接复用并关闭新 client。
|
||||
_ = client.Close()
|
||||
existing.lastUsed = now
|
||||
return existing, nil
|
||||
@@ -266,6 +281,7 @@ func (m *Manager) getOrCreateSession(
|
||||
return newSession, nil
|
||||
}
|
||||
|
||||
// evictLeastRecentlyUsedLocked 在达到上限时淘汰最久未使用会话。
|
||||
func (m *Manager) evictLeastRecentlyUsedLocked() bool {
|
||||
if len(m.sessions) == 0 {
|
||||
return false
|
||||
@@ -301,6 +317,7 @@ func buildSessionKey(language, sessionID string) string {
|
||||
return language + ":" + normalizeSessionID(sessionID)
|
||||
}
|
||||
|
||||
// normalizeSessionID 将空 session 归一为 default,便于复用同一会话键。
|
||||
func normalizeSessionID(sessionID string) string {
|
||||
sid := strings.TrimSpace(sessionID)
|
||||
if sid == "" {
|
||||
@@ -309,6 +326,7 @@ func normalizeSessionID(sessionID string) string {
|
||||
return sid
|
||||
}
|
||||
|
||||
// normalizeLanguage 统一语言名大小写和空白。
|
||||
func normalizeLanguage(language string) string {
|
||||
return strings.ToLower(strings.TrimSpace(language))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user