Add service registry and ntp service

This commit is contained in:
世界 2023-04-10 14:48:43 +08:00
parent b7cd741872
commit 20b4148381
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
5 changed files with 237 additions and 0 deletions

16
common/ntp/context.go Normal file
View file

@ -0,0 +1,16 @@
package ntp
import (
"context"
"time"
"github.com/sagernet/sing/common/service"
)
func TimeFuncFromContext(ctx context.Context) func() time.Time {
timeService := service.FromContext[TimeService](ctx)
if timeService == nil {
return nil
}
return timeService.TimeFunc()
}

123
common/ntp/service.go Normal file
View file

@ -0,0 +1,123 @@
package ntp
import (
"context"
"os"
"time"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
const TimeLayout = "2006-01-02 15:04:05 -0700"
type TimeService interface {
TimeFunc() func() time.Time
}
type Options struct {
Context context.Context
Server M.Socksaddr
Interval time.Duration
Dialer N.Dialer
Logger logger.Logger
}
var _ TimeService = (*Service)(nil)
type Service struct {
ctx context.Context
cancel common.ContextCancelCauseFunc
server M.Socksaddr
dialer N.Dialer
logger logger.Logger
ticker *time.Ticker
clockOffset time.Duration
}
func NewService(options Options) *Service {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
ctx, cancel := common.ContextWithCancelCause(ctx)
destination := options.Server
if !destination.IsValid() {
destination = M.Socksaddr{
Fqdn: "time.google.com",
}
}
if destination.Port == 0 {
destination.Port = 123
}
var interval time.Duration
if options.Interval > 0 {
interval = options.Interval
} else {
interval = 30 * time.Minute
}
var dialer N.Dialer
if options.Dialer != nil {
dialer = options.Dialer
} else {
dialer = N.SystemDialer
}
return &Service{
ctx: ctx,
cancel: cancel,
server: destination,
dialer: dialer,
logger: options.Logger,
ticker: time.NewTicker(interval),
}
}
func (s *Service) Start() error {
err := s.update()
if err != nil {
return E.Cause(err, "initialize time")
}
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(TimeLayout))
go s.loopUpdate()
return nil
}
func (s *Service) Close() error {
s.ticker.Stop()
s.cancel(os.ErrClosed)
return nil
}
func (s *Service) TimeFunc() func() time.Time {
return func() time.Time {
return time.Now().Add(s.clockOffset)
}
}
func (s *Service) loopUpdate() {
for {
select {
case <-s.ctx.Done():
return
case <-s.ticker.C:
}
err := s.update()
if err == nil {
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(TimeLayout))
} else {
s.logger.Warn("update time: ", err)
}
}
}
func (s *Service) update() error {
response, err := Exchange(s.ctx, s.dialer, s.server)
if err != nil {
return err
}
s.clockOffset = response.ClockOffset
return nil
}

63
common/service/context.go Normal file
View file

@ -0,0 +1,63 @@
package service
import (
"context"
"github.com/sagernet/sing/common"
)
func ContextWithRegistry(ctx context.Context, registry Registry) context.Context {
return context.WithValue(ctx, common.DefaultValue[*Registry](), registry)
}
func RegistryFromContext(ctx context.Context) Registry {
registry := ctx.Value(common.DefaultValue[*Registry]())
if registry == nil {
return nil
}
return registry.(Registry)
}
func FromContext[T any](ctx context.Context) T {
registry := RegistryFromContext(ctx)
if registry == nil {
return common.DefaultValue[T]()
}
service := registry.Get(common.DefaultValue[*T]())
if service == nil {
return common.DefaultValue[T]()
}
return service.(T)
}
func PtrFromContext[T any](ctx context.Context) *T {
registry := RegistryFromContext(ctx)
if registry == nil {
return nil
}
servicePtr := registry.Get(common.DefaultValue[*T]())
if servicePtr == nil {
return nil
}
return servicePtr.(*T)
}
func ContextWith[T any](ctx context.Context, service T) context.Context {
registry := RegistryFromContext(ctx)
if registry == nil {
registry = NewRegistry()
ctx = ContextWithRegistry(ctx, registry)
}
registry.Register(common.DefaultValue[*T](), service)
return ctx
}
func ContextWithPtr[T any](ctx context.Context, servicePtr *T) context.Context {
registry := RegistryFromContext(ctx)
if registry == nil {
registry = NewRegistry()
ctx = ContextWithRegistry(ctx, registry)
}
registry.Register(common.DefaultValue[*T](), servicePtr)
return ctx
}

View file

@ -0,0 +1,35 @@
package service
import (
"sync"
)
type Registry interface {
Register(serviceType any, service any) any
Get(serviceType any) any
}
func NewRegistry() Registry {
return &defaultRegistry{
serviceTypes: make(map[any]any),
}
}
type defaultRegistry struct {
serviceTypes map[any]any
access sync.RWMutex
}
func (r *defaultRegistry) Register(serviceType any, service any) any {
r.access.Lock()
defer r.access.Unlock()
oldService := r.serviceTypes[serviceType]
r.serviceTypes[serviceType] = service
return oldService
}
func (r *defaultRegistry) Get(serviceType any) any {
r.access.RLock()
defer r.access.RUnlock()
return r.serviceTypes[serviceType]
}