src/ulsp/internal/persistent-notifier/manager.go (69 lines of code) (raw):
package notifier
import (
"context"
"fmt"
"sync"
ideclient "github.com/uber/scip-lsp/src/ulsp/gateway/ide-client"
"github.com/uber/scip-lsp/src/ulsp/repository/session"
"go.uber.org/zap"
)
// NotificationManagerParams are used to initialize a new NotificationManager.
type NotificationManagerParams struct {
Sessions session.Repository
IdeGateway ideclient.Gateway
Logger *zap.SugaredLogger
}
// NotificationManager is to manage multiple simultaneous NotificationHandlers.
// StartNotification will start a new NotificationHandler, or get the existing one if present for this workspaceRoot and title combination.
type NotificationManager interface {
StartNotification(ctx context.Context, workspaceRoot string, title string) (NotificationHandler, error)
Delete(id string)
}
// NewNotificationManager creates a new NotificationManager.
func NewNotificationManager(p NotificationManagerParams) NotificationManager {
return ¬ificationManagerImpl{
sessions: p.Sessions,
ideGateway: p.IdeGateway,
logger: p.Logger,
managers: make(map[string]NotificationHandler),
}
}
type notificationManagerImpl struct {
sessions session.Repository
ideGateway ideclient.Gateway
logger *zap.SugaredLogger
managers map[string]NotificationHandler
mu sync.Mutex
}
// StartNotification starts a new NotificationHandler, or gets the existing one if present for this workspaceRoot and title combination.
// The NotificationHandler will remain active until all callers have called Done() on the handler.
func (m *notificationManagerImpl) StartNotification(ctx context.Context, workspaceRoot string, title string) (NotificationHandler, error) {
// WorkspaceRoot and title are used to generate a unique identifier for the notification handler.
notificationID := fmt.Sprintf("%s-%s", workspaceRoot, title)
m.mu.Lock()
defer m.mu.Unlock()
// Ensure that we do not return an old handler that is already closed.
m.deleteIfClosed(notificationID)
existing, ok := m.managers[notificationID]
if ok {
// Existing handler for this notificationId. Add an additional caller and return it.
existing.Add(ctx)
return existing, nil
}
// No existing handler for this notificationId. Create a new one, store it, and return it.
h, err := NewNotificationHandler(ctx, notificationHandlerParams{
ParentManager: m,
Sessions: m.sessions,
IdeGateway: m.ideGateway,
Logger: m.logger,
WorkspaceRoot: workspaceRoot,
Title: title,
}, notificationID)
if err != nil {
return nil, err
}
m.managers[notificationID] = h
return h, nil
}
// Delete allows a NotificationHandler to remove itself from the NotificationHandler. This should be called by the NotificationHandler before closing its channel.
func (m *notificationManagerImpl) Delete(id string) {
m.mu.Lock()
defer m.mu.Unlock()
// Remove only if closed. This allows for flexible ordering of StartNotification and Delete calls.
m.deleteIfClosed(id)
}
// Removes a notification handler only if it is already closed.
func (m *notificationManagerImpl) deleteIfClosed(id string) {
if mgr, ok := m.managers[id]; ok {
if mgr.IsClosed() {
delete(m.managers, id)
}
}
}