• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lif0 / go-gracefully / 18635932339

19 Oct 2025 08:50PM UTC coverage: 68.783% (-1.9%) from 70.69%
18635932339

push

github

lif0
feat: introduced RegisterFunc;

28 of 36 new or added lines in 2 files covered. (77.78%)

1 existing line in 1 file now uncovered.

130 of 189 relevant lines covered (68.78%)

518.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.64
/registry.go
1
package gracefully
2

3
import (
4
        "context"
5
        "reflect"
6
        "sync"
7
        "sync/atomic"
8
        "unsafe"
9

10
        "github.com/lif0/pkg/utils/errx"
11
)
12

NEW
13
var stubFunc = func(context.Context) error {
×
NEW
14
        return nil
×
NEW
15
}
×
16

17
// Registry is a thread-safe registry for instances which should be can graceful shutdown.
18
//
19
// Use NewRegister to create a new instance.
20
type Registry struct {
21
        mu sync.Mutex
22

23
        gsiHash map[unsafe.Pointer]int
24
        gsFunc  []func(context.Context) error
25

26
        // chan shutdown done
27
        chsd     chan struct{}
28
        disposed atomic.Bool
29
}
30

31
// NewRegistry creates and returns a new initialized Registerer.
32
//
33
// If you want to set new Registerer as Global, use gogracefully.SetGlobal()
34
// (e.g. for testing purposes).
35
func NewRegistry() *Registry {
17✔
36
        return &Registry{
17✔
37
                mu:       sync.Mutex{},
17✔
38
                gsiHash:  make(map[unsafe.Pointer]int),
17✔
39
                gsFunc:   make([]func(context.Context) error, 0),
17✔
40
                disposed: atomic.Bool{},
17✔
41
                chsd:     make(chan struct{}),
17✔
42
        }
17✔
43
}
17✔
44

45
// Register implements Registerer.
46
func (r *Registry) Register(igs GracefulShutdownObject) error {
46✔
47
        if err := r.isDisposed(); err != nil {
47✔
48
                return err
1✔
49
        }
1✔
50

51
        r.mu.Lock()
45✔
52
        defer r.mu.Unlock()
45✔
53

45✔
54
        if err := r.isDisposed(); err != nil {
45✔
55
                return err
×
56
        }
×
57

58
        ptr := reflect.ValueOf(igs).UnsafePointer()
45✔
59
        if _, ok := r.gsiHash[ptr]; ok {
78✔
60
                return ErrAlreadyRegistered
33✔
61
        }
33✔
62

63
        r.gsFunc = append(r.gsFunc, igs.GracefulShutdown)
12✔
64
        r.gsiHash[ptr] = len(r.gsFunc) - 1
12✔
65
        return nil
12✔
66
}
67

68
// RegisterFunc implements Registerer.
69
func (r *Registry) RegisterFunc(f func(context.Context) error) error {
36✔
70
        if err := r.isDisposed(); err != nil {
37✔
71
                return err
1✔
72
        }
1✔
73

74
        r.mu.Lock()
35✔
75
        defer r.mu.Unlock()
35✔
76

35✔
77
        if err := r.isDisposed(); err != nil {
35✔
NEW
78
                return err
×
UNCOV
79
        }
×
80

81
        r.gsFunc = append(r.gsFunc, f)
35✔
82
        return nil
35✔
83
}
84

85
// Unregister implements Registerer.
86
func (r *Registry) Unregister(igs GracefulShutdownObject) bool {
9✔
87
        if r.isDisposed() != nil {
10✔
88
                return false
1✔
89
        }
1✔
90

91
        r.mu.Lock()
8✔
92
        defer r.mu.Unlock()
8✔
93

8✔
94
        if r.isDisposed() != nil {
8✔
95
                return false
×
96
        }
×
97

98
        ptr := reflect.ValueOf(igs).UnsafePointer()
8✔
99
        if i, ok := r.gsiHash[ptr]; ok {
13✔
100
                r.gsFunc[i] = stubFunc
5✔
101
                delete(r.gsiHash, ptr)
5✔
102
                return true
5✔
103
        }
5✔
104

105
        return false
3✔
106
}
107

108
// MustRegister implements Registerer.
109
// It panics if any instance is already registered.
110
func (r *Registry) MustRegister(igss ...GracefulShutdownObject) {
3✔
111
        for i := 0; i < len(igss); i++ {
8✔
112
                err := r.Register(igss[i])
5✔
113
                if err != nil {
6✔
114
                        panic(err)
1✔
115
                }
116
        }
117
}
118

119
// Shutdown implements Registerer.
120
func (r *Registry) Shutdown(ctx context.Context) errx.MultiError {
10✔
121
        if err := r.isDisposed(); err != nil {
13✔
122
                return errx.MultiError{err}
3✔
123
        }
3✔
124

125
        r.mu.Lock()
7✔
126
        defer r.mu.Unlock()
7✔
127

7✔
128
        if !r.disposed.CompareAndSwap(false, true) {
7✔
NEW
129
                return errx.MultiError{ErrShutdownCalled}
×
130
        }
×
131

132
        errs := errx.MultiError{}
7✔
133
        for _, f := range r.gsFunc {
11✔
134
                gsErr := f(ctx)
4✔
135
                if gsErr != nil {
5✔
136
                        errs.Append(gsErr)
1✔
137
                }
1✔
138
        }
139

140
        // broadcast for all who call WaitShutdown
141
        close(r.chsd)
7✔
142
        return errs
7✔
143
}
144

145
// WaitShutdown implements Registerer.
146
func (r *Registry) WaitShutdown() {
2✔
147
        <-r.chsd
2✔
148
}
2✔
149

150
// isDisposed ...
151
func (r *Registry) isDisposed() error {
189✔
152
        if r.disposed.Load() {
195✔
153
                return ErrShutdownCalled
6✔
154
        }
6✔
155
        return nil
183✔
156
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc