syncthing/lib/model/service_map.go

104 lines
3.1 KiB
Go

// Copyright (C) 2023 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package model
import (
"context"
"fmt"
"time"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/thejerf/suture/v4"
)
// A serviceMap is a utility map of arbitrary keys to a suture.Service of
// some kind, where adding and removing services ensures they are properly
// started and stopped on the given Supervisor. The serviceMap is itself a
// suture.Service and should be added to a Supervisor.
// Not safe for concurrent use.
type serviceMap[K comparable, S suture.Service] struct {
services map[K]S
tokens map[K]suture.ServiceToken
supervisor *suture.Supervisor
eventLogger events.Logger
}
func newServiceMap[K comparable, S suture.Service](eventLogger events.Logger) *serviceMap[K, S] {
m := &serviceMap[K, S]{
services: make(map[K]S),
tokens: make(map[K]suture.ServiceToken),
eventLogger: eventLogger,
}
m.supervisor = suture.New(m.String(), svcutil.SpecWithDebugLogger(l))
return m
}
// Add adds a service to the map, starting it on the supervisor. If there is
// already a service at the given key, it is removed first.
func (s *serviceMap[K, S]) Add(k K, v S) {
if tok, ok := s.tokens[k]; ok {
// There is already a service at this key, remove it first.
s.supervisor.Remove(tok)
s.eventLogger.Log(events.Failure, fmt.Sprintf("%s replaced service at key %v", s, k))
}
s.services[k] = v
s.tokens[k] = s.supervisor.Add(v)
}
// Get returns the service at the given key, or the empty value and false if
// there is no service at that key.
func (s *serviceMap[K, S]) Get(k K) (v S, ok bool) {
v, ok = s.services[k]
return
}
// Remove removes the service at the given key, stopping it on the supervisor.
// If there is no service at the given key, nothing happens. The return value
// indicates whether a service was removed.
func (s *serviceMap[K, S]) Remove(k K) (found bool) {
if tok, ok := s.tokens[k]; ok {
found = true
s.supervisor.Remove(tok)
}
delete(s.services, k)
delete(s.tokens, k)
return
}
// RemoveAndWait removes the service at the given key, stopping it on the
// supervisor. If there is no service at the given key, nothing happens. The
// return value indicates whether a service was removed.
func (s *serviceMap[K, S]) RemoveAndWait(k K, timeout time.Duration) (found bool) {
if tok, ok := s.tokens[k]; ok {
found = true
s.supervisor.RemoveAndWait(tok, timeout)
}
delete(s.services, k)
delete(s.tokens, k)
return found
}
// Each calls the given function for each service in the map.
func (s *serviceMap[K, S]) Each(fn func(K, S)) {
for key, svc := range s.services {
fn(key, svc)
}
}
// Suture implementation
func (s *serviceMap[K, S]) Serve(ctx context.Context) error {
return s.supervisor.Serve(ctx)
}
func (s *serviceMap[K, S]) String() string {
var kv K
var sv S
return fmt.Sprintf("serviceMap[%T, %T]@%p", kv, sv, s)
}