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

alexferl / zerohttp / 22933751888

11 Mar 2026 02:25AM UTC coverage: 93.708% (+0.08%) from 93.628%
22933751888

Pull #79

github

web-flow
Merge 022b376d4 into 31c14afe4
Pull Request #79: refactor: split server.go into smaller files

397 of 449 new or added lines in 8 files covered. (88.42%)

2 existing lines in 1 file now uncovered.

8176 of 8725 relevant lines covered (93.71%)

59.75 hits per line

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

89.16
/server_lifecycle.go
1
package zerohttp
2

3
import (
4
        "context"
5
        "sync"
6

7
        "github.com/alexferl/zerohttp/config"
8
        "github.com/alexferl/zerohttp/log"
9
)
10

11
// RegisterPreShutdownHook registers a hook to run before server shutdown begins.
12
// Pre-shutdown hooks execute sequentially in registration order.
13
//
14
// Hooks must respect context cancellation by checking ctx.Done().
15
// If a hook blocks without respecting the context, shutdown will hang.
16
//
17
// Example:
18
//
19
//        app.RegisterPreShutdownHook("health", func(ctx context.Context) error {
20
//            health.SetUnhealthy()
21
//            return nil
22
//        })
23
func (s *Server) RegisterPreShutdownHook(name string, hook config.ShutdownHook) {
6✔
24
        s.mu.Lock()
6✔
25
        defer s.mu.Unlock()
6✔
26
        s.preShutdownHooks = append(s.preShutdownHooks, config.ShutdownHookConfig{Name: name, Hook: hook})
6✔
27
}
6✔
28

29
// RegisterShutdownHook registers a hook to run concurrently with server shutdown.
30
// Shutdown hooks execute concurrently alongside server shutdown.
31
//
32
// Hooks must respect context cancellation by checking ctx.Done().
33
// If a hook blocks without respecting the context, shutdown will hang.
34
//
35
// Example:
36
//
37
//        app.RegisterShutdownHook("close-db", func(ctx context.Context) error {
38
//            return db.Close()
39
//        })
40
func (s *Server) RegisterShutdownHook(name string, hook config.ShutdownHook) {
3✔
41
        s.mu.Lock()
3✔
42
        defer s.mu.Unlock()
3✔
43
        s.shutdownHooks = append(s.shutdownHooks, config.ShutdownHookConfig{Name: name, Hook: hook})
3✔
44
}
3✔
45

46
// RegisterPostShutdownHook registers a hook to run after servers are shut down.
47
// Post-shutdown hooks execute sequentially in registration order.
48
//
49
// Hooks must respect context cancellation by checking ctx.Done().
50
// If a hook blocks without respecting the context, shutdown will hang.
51
//
52
// Example:
53
//
54
//        app.RegisterPostShutdownHook("cleanup", func(ctx context.Context) error {
55
//            return os.RemoveAll("/tmp/app-*")
56
//        })
57
func (s *Server) RegisterPostShutdownHook(name string, hook config.ShutdownHook) {
3✔
58
        s.mu.Lock()
3✔
59
        defer s.mu.Unlock()
3✔
60
        s.postShutdownHooks = append(s.postShutdownHooks, config.ShutdownHookConfig{Name: name, Hook: hook})
3✔
61
}
3✔
62

63
// runPreShutdownHooks executes pre-shutdown hooks sequentially in registration order.
64
func (s *Server) runPreShutdownHooks(ctx context.Context) error {
20✔
65
        s.mu.RLock()
20✔
66
        hooks := s.preShutdownHooks
20✔
67
        s.mu.RUnlock()
20✔
68

20✔
69
        if len(hooks) == 0 {
36✔
70
                return nil
16✔
71
        }
16✔
72

73
        s.logger.Debug("Running pre-shutdown hooks", log.F("count", len(hooks)))
4✔
74

4✔
75
        for _, hook := range hooks {
10✔
76
                select {
6✔
77
                case <-ctx.Done():
1✔
78
                        s.logger.Warn("Pre-shutdown hook aborted due to context cancellation", log.F("hook", hook.Name))
1✔
79
                        return ctx.Err()
1✔
80
                default:
5✔
81
                }
82

83
                s.logger.Debug("Running pre-shutdown hook", log.F("hook", hook.Name))
5✔
84
                if err := hook.Hook(ctx); err != nil {
6✔
85
                        s.logger.Error("Pre-shutdown hook failed", log.F("hook", hook.Name), log.E(err))
1✔
86
                        // Continue with other hooks despite error
1✔
87
                }
1✔
88
        }
89

90
        return nil
3✔
91
}
92

93
// startShutdownHooks starts shutdown hooks concurrently and returns a WaitGroup and error channel.
94
// The caller must wait on the returned WaitGroup and then close the error channel.
95
func (s *Server) startShutdownHooks(ctx context.Context) (*sync.WaitGroup, chan error) {
19✔
96
        s.mu.RLock()
19✔
97
        hooks := s.shutdownHooks
19✔
98
        s.mu.RUnlock()
19✔
99

19✔
100
        var wg sync.WaitGroup
19✔
101
        errCh := make(chan error, len(hooks))
19✔
102

19✔
103
        if len(hooks) == 0 {
36✔
104
                return &wg, errCh
17✔
105
        }
17✔
106

107
        s.logger.Debug("Starting shutdown hooks", log.F("count", len(hooks)))
2✔
108

2✔
109
        for _, hook := range hooks {
5✔
110
                wg.Add(1)
3✔
111
                go func(h config.ShutdownHookConfig) {
6✔
112
                        defer wg.Done()
3✔
113

3✔
114
                        s.logger.Debug("Running shutdown hook", log.F("hook", h.Name))
3✔
115
                        if err := h.Hook(ctx); err != nil {
3✔
NEW
116
                                s.logger.Error("Shutdown hook failed", log.F("hook", h.Name), log.E(err))
×
NEW
117
                                errCh <- err
×
NEW
118
                        }
×
119
                }(hook)
120
        }
121

122
        return &wg, errCh
2✔
123
}
124

125
// runPostShutdownHooks executes post-shutdown hooks sequentially in registration order.
126
func (s *Server) runPostShutdownHooks(ctx context.Context) error {
19✔
127
        s.mu.RLock()
19✔
128
        hooks := s.postShutdownHooks
19✔
129
        s.mu.RUnlock()
19✔
130

19✔
131
        if len(hooks) == 0 {
36✔
132
                return nil
17✔
133
        }
17✔
134

135
        s.logger.Debug("Running post-shutdown hooks", log.F("count", len(hooks)))
2✔
136

2✔
137
        for _, hook := range hooks {
5✔
138
                select {
3✔
NEW
139
                case <-ctx.Done():
×
NEW
140
                        s.logger.Warn("Post-shutdown hook aborted due to context cancellation", log.F("hook", hook.Name))
×
NEW
141
                        return ctx.Err()
×
142
                default:
3✔
143
                }
144

145
                s.logger.Debug("Running post-shutdown hook", log.F("hook", hook.Name))
3✔
146
                if err := hook.Hook(ctx); err != nil {
3✔
NEW
147
                        s.logger.Error("Post-shutdown hook failed", log.F("hook", hook.Name), log.E(err))
×
NEW
148
                        // Continue with other hooks despite error
×
NEW
149
                }
×
150
        }
151

152
        return nil
2✔
153
}
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

© 2026 Coveralls, Inc