svcutil.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // Copyright (C) 2016 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package svcutil
  7. import (
  8. "context"
  9. "errors"
  10. "fmt"
  11. "time"
  12. "github.com/syncthing/syncthing/lib/logger"
  13. "github.com/syncthing/syncthing/lib/sync"
  14. "github.com/thejerf/suture/v4"
  15. )
  16. const ServiceTimeout = 10 * time.Second
  17. type FatalErr struct {
  18. Err error
  19. Status ExitStatus
  20. }
  21. // AsFatalErr wraps the given error creating a FatalErr. If the given error
  22. // already is of type FatalErr, it is not wrapped again.
  23. func AsFatalErr(err error, status ExitStatus) *FatalErr {
  24. var ferr *FatalErr
  25. if errors.As(err, &ferr) {
  26. return ferr
  27. }
  28. return &FatalErr{
  29. Err: err,
  30. Status: status,
  31. }
  32. }
  33. func (e *FatalErr) Error() string {
  34. return e.Err.Error()
  35. }
  36. func (e *FatalErr) Unwrap() error {
  37. return e.Err
  38. }
  39. func (e *FatalErr) Is(target error) bool {
  40. return target == suture.ErrTerminateSupervisorTree
  41. }
  42. // NoRestartErr wraps the given error err (which may be nil) to make sure that
  43. // `errors.Is(err, suture.ErrDoNotRestart) == true`.
  44. func NoRestartErr(err error) error {
  45. if err == nil {
  46. return suture.ErrDoNotRestart
  47. }
  48. return &noRestartErr{err}
  49. }
  50. type noRestartErr struct {
  51. err error
  52. }
  53. func (e *noRestartErr) Error() string {
  54. return e.err.Error()
  55. }
  56. func (e *noRestartErr) Unwrap() error {
  57. return e.err
  58. }
  59. func (e *noRestartErr) Is(target error) bool {
  60. return target == suture.ErrDoNotRestart
  61. }
  62. type ExitStatus int
  63. const (
  64. ExitSuccess ExitStatus = 0
  65. ExitError ExitStatus = 1
  66. ExitNoUpgradeAvailable ExitStatus = 2
  67. ExitRestart ExitStatus = 3
  68. ExitUpgrade ExitStatus = 4
  69. )
  70. func (s ExitStatus) AsInt() int {
  71. return int(s)
  72. }
  73. type ServiceWithError interface {
  74. suture.Service
  75. fmt.Stringer
  76. Error() error
  77. }
  78. // AsService wraps the given function to implement suture.Service. In addition
  79. // it keeps track of the returned error and allows querying that error.
  80. func AsService(fn func(ctx context.Context) error, creator string) ServiceWithError {
  81. return &service{
  82. creator: creator,
  83. serve: fn,
  84. mut: sync.NewMutex(),
  85. }
  86. }
  87. type service struct {
  88. creator string
  89. serve func(ctx context.Context) error
  90. err error
  91. mut sync.Mutex
  92. }
  93. func (s *service) Serve(ctx context.Context) error {
  94. s.mut.Lock()
  95. s.err = nil
  96. s.mut.Unlock()
  97. err := s.serve(ctx)
  98. s.mut.Lock()
  99. s.err = err
  100. s.mut.Unlock()
  101. return err
  102. }
  103. func (s *service) Error() error {
  104. s.mut.Lock()
  105. defer s.mut.Unlock()
  106. return s.err
  107. }
  108. func (s *service) String() string {
  109. return fmt.Sprintf("Service@%p created by %v", s, s.creator)
  110. }
  111. type doneService func()
  112. func (fn doneService) Serve(ctx context.Context) error {
  113. <-ctx.Done()
  114. fn()
  115. return nil
  116. }
  117. // OnSupervisorDone calls fn when sup is done.
  118. func OnSupervisorDone(sup *suture.Supervisor, fn func()) {
  119. sup.Add(doneService(fn))
  120. }
  121. func SpecWithDebugLogger(l logger.Logger) suture.Spec {
  122. return spec(func(e suture.Event) { l.Debugln(e) })
  123. }
  124. func SpecWithInfoLogger(l logger.Logger) suture.Spec {
  125. return spec(func(e suture.Event) { l.Infoln(e) })
  126. }
  127. func spec(eventHook suture.EventHook) suture.Spec {
  128. return suture.Spec{
  129. EventHook: eventHook,
  130. Timeout: ServiceTimeout,
  131. PassThroughPanics: true,
  132. DontPropagateTermination: false,
  133. }
  134. }