Codebase list golang-github-cloudflare-tableflip / da537c3
New upstream version 1.2.0 Pirate Praveen 3 years ago
21 changed file(s) with 812 addition(s) and 220 deletion(s). Raw diff Collapse all Expand all
00 language: go
11
22 go:
3 - "1.10.x"
4 - "1.11.x"
5 - master
3 - "1.13.x"
4 - "1.14.x"
5
6 os:
7 - linux
8 - osx
9 - windows
1515 * Crashing during initialisation is OK
1616 * Only a single upgrade is ever run in parallel
1717
18 `tableflip` does not work on Windows.
18 **`tableflip` works on Linux and macOS.**
1919
20 It's easy to get started:
20 ## Using the library
2121
2222 ```Go
23 upg, err := tableflip.New(tableflip.Options{})
24 if err != nil {
25 panic(err)
26 }
23 upg, _ := tableflip.New(tableflip.Options{})
2724 defer upg.Stop()
2825
2926 go func() {
3027 sig := make(chan os.Signal, 1)
3128 signal.Notify(sig, syscall.SIGHUP)
3229 for range sig {
33 err := upg.Upgrade()
34 if err != nil {
35 log.Println("Upgrade failed:", err)
36 continue
37 }
38
39 log.Println("Upgrade succeeded")
30 upg.Upgrade()
4031 }
4132 }()
4233
43 ln, err := upg.Fds.Listen("tcp", "localhost:8080")
44 if err != nil {
45 log.Fatalln("Can't listen:", err)
46 }
34 // Listen must be called before Ready
35 ln, _ := upg.Listen("tcp", "localhost:8080")
36 defer ln.Close()
4737
48 var server http.Server
49 go server.Serve(ln)
38 go http.Serve(ln, nil)
5039
5140 if err := upg.Ready(); err != nil {
5241 panic(err)
5342 }
43
5444 <-upg.Exit()
45 ```
5546
56 time.AfterFunc(30*time.Second, func() {
57 os.Exit(1)
58 })
47 Please see the more elaborate [graceful shutdown with net/http](http_example_test.go) example.
5948
60 _ = server.Shutdown(context.Background())
49 ## Integration with `systemd`
50
6151 ```
52 [Unit]
53 Description=Service using tableflip
54
55 [Service]
56 ExecStart=/path/to/binary -some-flag /path/to/pid-file
57 ExecReload=/bin/kill -HUP $MAINPID
58 PIDFile=/path/to/pid-file
59 ```
60
61 See the [documentation](https://godoc.org/github.com/cloudflare/tableflip) as well.
62
63 The logs of a process using `tableflip` may go missing due to a [bug in journald](https://github.com/systemd/systemd/issues/13708). You can work around this by logging directly to journald, for example by using [go-systemd/journal](https://godoc.org/github.com/coreos/go-systemd/journal) and looking for the [$JOURNAL_STREAM](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#$JOURNAL_STREAM) environment variable.
33 "encoding/gob"
44 "fmt"
55 "os"
6
7 "github.com/pkg/errors"
86 )
97
108 type child struct {
2119 // readyW is passed to the child, readyR stays with the parent
2220 readyR, readyW, err := os.Pipe()
2321 if err != nil {
24 return nil, errors.Wrap(err, "pipe failed")
22 return nil, fmt.Errorf("pipe failed: %s", err)
2523 }
2624
2725 namesR, namesW, err := os.Pipe()
2826 if err != nil {
2927 readyR.Close()
3028 readyW.Close()
31 return nil, errors.Wrap(err, "pipe failed")
29 return nil, fmt.Errorf("pipe failed: %s", err)
3230 }
3331
3432 // Copy passed fds and append the notification pipe
5250 readyW.Close()
5351 namesR.Close()
5452 namesW.Close()
55 return nil, errors.Wrapf(err, "can't start process %s", os.Args[0])
53 return nil, fmt.Errorf("can't start process %s: %s", os.Args[0], err)
5654 }
5755
5856 exited := make(chan struct{})
3737 // you're probably using "go run main.go", for graceful reloads to work,
3838 // you'll need use "go build main.go".
3939 //
40 // Tableflip does not work on Windows, because Windows does not have
41 // the mechanisms required to support this method of graceful restarting.
42 // It is still possible to include this package in code that runs on Windows,
43 // which may be necessary in certain development circumstances, but it will not
44 // provide zero downtime upgrades when running on Windows. See the `testing`
45 // package for an example of how to use it.
46 //
4047 package tableflip
0 // +build !windows
1
2 package tableflip
3
4 import (
5 "fmt"
6 "syscall"
7 )
8
9 func dupFd(fd uintptr, name fileName) (*file, error) {
10 dupfd, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_DUPFD_CLOEXEC, 0)
11 if errno != 0 {
12 return nil, fmt.Errorf("can't dup fd using fcntl: %s", errno)
13 }
14
15 return newFile(dupfd, name), nil
16 }
0 package tableflip
1
2 import "errors"
3
4 func dupFd(fd uintptr, name fileName) (*file, error) {
5 return nil, errors.New("tableflip: duplicating file descriptors is not supported on this platform")
6 }
11
22 import (
33 "os"
4 "syscall"
54 )
6
7 var stdEnv = &env{
8 newProc: newOSProcess,
9 newFile: os.NewFile,
10 environ: os.Environ,
11 getenv: os.Getenv,
12 closeOnExec: syscall.CloseOnExec,
13 }
145
156 type env struct {
167 newProc func(string, []string, []*os.File, []string) (process, error)
0 // +build !windows
1
2 package tableflip
3
4 import (
5 "os"
6 "syscall"
7 )
8
9 var stdEnv = &env{
10 newProc: newOSProcess,
11 newFile: os.NewFile,
12 environ: os.Environ,
13 getenv: os.Getenv,
14 closeOnExec: syscall.CloseOnExec,
15 }
0 package tableflip
1
2 // replace Unix-specific syscall with a no-op so it will build
3 // without errors.
4
5 var stdEnv *env = nil
+122
-23
fds.go less more
00 package tableflip
11
22 import (
3 "fmt"
34 "net"
45 "os"
6 "runtime"
57 "strings"
68 "sync"
79 "syscall"
8
9 "github.com/pkg/errors"
1010 )
1111
1212 // Listener can be shared between processes.
1515 syscall.Conn
1616 }
1717
18 // PacketConn can be shared between processes.
19 type PacketConn interface {
20 net.PacketConn
21 syscall.Conn
22 }
23
1824 // Conn can be shared between processes.
1925 type Conn interface {
2026 net.Conn
2329
2430 const (
2531 listenKind = "listener"
32 packetKind = "packet"
2633 connKind = "conn"
2734 fdKind = "fd"
2835 )
3138
3239 func (name fileName) String() string {
3340 return strings.Join(name[:], ":")
41 }
42
43 func (name fileName) isUnix() bool {
44 if name[0] == listenKind && (name[1] == "unix" || name[1] == "unixpacket") {
45 return true
46 }
47 if name[0] == packetKind && (name[1] == "unixgram") {
48 return true
49 }
50 return false
3451 }
3552
3653 // file works around the fact that it's not possible
88105
89106 ln, err = net.Listen(network, addr)
90107 if err != nil {
91 return nil, errors.Wrap(err, "can't create new listener")
108 return nil, fmt.Errorf("can't create new listener: %s", err)
92109 }
93110
94111 if _, ok := ln.(Listener); !ok {
95112 ln.Close()
96 return nil, errors.Errorf("%T doesn't implement tableflip.Listener", ln)
113 return nil, fmt.Errorf("%T doesn't implement tableflip.Listener", ln)
97114 }
98115
99116 err = f.addListenerLocked(network, addr, ln.(Listener))
124141
125142 ln, err := net.FileListener(file.File)
126143 if err != nil {
127 return nil, errors.Wrapf(err, "can't inherit listener %s %s", network, addr)
144 return nil, fmt.Errorf("can't inherit listener %s %s: %s", network, addr, err)
128145 }
129146
130147 delete(f.inherited, key)
152169 ifc.SetUnlinkOnClose(false)
153170 }
154171
155 return f.addConnLocked(listenKind, network, addr, ln)
172 return f.addSyscallConnLocked(listenKind, network, addr, ln)
173 }
174
175 // ListenPacket returns a packet conn inherited from the parent process, or creates a new one.
176 func (f *Fds) ListenPacket(network, addr string) (net.PacketConn, error) {
177 f.mu.Lock()
178 defer f.mu.Unlock()
179
180 conn, err := f.packetConnLocked(network, addr)
181 if err != nil {
182 return nil, err
183 }
184
185 if conn != nil {
186 return conn, nil
187 }
188
189 conn, err = net.ListenPacket(network, addr)
190 if err != nil {
191 return nil, fmt.Errorf("can't create new listener: %s", err)
192 }
193
194 if _, ok := conn.(PacketConn); !ok {
195 return nil, fmt.Errorf("%T doesn't implement tableflip.PacketConn", conn)
196 }
197
198 err = f.addSyscallConnLocked(packetKind, network, addr, conn.(PacketConn))
199 if err != nil {
200 conn.Close()
201 return nil, err
202 }
203
204 return conn, nil
205 }
206
207 // PacketConn returns an inherited packet connection or nil.
208 //
209 // It is safe to close the returned packet connection.
210 func (f *Fds) PacketConn(network, addr string) (net.PacketConn, error) {
211 f.mu.Lock()
212 defer f.mu.Unlock()
213
214 return f.packetConnLocked(network, addr)
215 }
216
217 // AddPacketConn adds a PacketConn.
218 //
219 // It is safe to close conn after calling the method.
220 // Any existing packet connection with the same address is overwitten.
221 func (f *Fds) AddPacketConn(network, addr string, conn PacketConn) error {
222 f.mu.Lock()
223 defer f.mu.Unlock()
224
225 return f.addSyscallConnLocked(packetKind, network, addr, conn)
226 }
227
228 func (f *Fds) packetConnLocked(network, addr string) (net.PacketConn, error) {
229 key := fileName{packetKind, network, addr}
230 file := f.inherited[key]
231 if file == nil {
232 return nil, nil
233 }
234
235 conn, err := net.FilePacketConn(file.File)
236 if err != nil {
237 return nil, fmt.Errorf("can't inherit packet conn %s %s: %s", network, addr, err)
238 }
239
240 delete(f.inherited, key)
241 f.used[key] = file
242 return conn, nil
156243 }
157244
158245 // Conn returns an inherited connection or nil.
170257
171258 conn, err := net.FileConn(file.File)
172259 if err != nil {
173 return nil, errors.Wrapf(err, "can't inherit connection %s %s", network, addr)
260 return nil, fmt.Errorf("can't inherit connection %s %s: %s", network, addr, err)
174261 }
175262
176263 delete(f.inherited, key)
185272 f.mu.Lock()
186273 defer f.mu.Unlock()
187274
188 return f.addConnLocked(connKind, network, addr, conn)
189 }
190
191 func (f *Fds) addConnLocked(kind, network, addr string, conn syscall.Conn) error {
275 return f.addSyscallConnLocked(connKind, network, addr, conn)
276 }
277
278 func (f *Fds) addSyscallConnLocked(kind, network, addr string, conn syscall.Conn) error {
192279 key := fileName{kind, network, addr}
193280 file, err := dupConn(conn, key)
194281 if err != nil {
195 return errors.Wrapf(err, "can't dup listener %s %s", network, addr)
282 return fmt.Errorf("can't dup %s (%s %s): %s", kind, network, addr, err)
196283 }
197284
198285 delete(f.inherited, key)
262349 defer f.mu.Unlock()
263350
264351 for key, file := range f.inherited {
265 if key[0] == listenKind && (key[1] == "unix" || key[1] == "unixpacket") {
352 if key.isUnix() {
266353 // Remove inherited but unused Unix sockets from the file system.
267354 // This undoes the effect of SetUnlinkOnClose(false).
268355 _ = unlinkUnixSocket(key[2])
273360 }
274361
275362 func unlinkUnixSocket(path string) error {
363 if runtime.GOOS == "linux" && strings.HasPrefix(path, "@") {
364 // Don't unlink sockets using the abstract namespace.
365 return nil
366 }
367
276368 info, err := os.Stat(path)
277369 if err != nil {
278370 return err
290382 defer f.mu.Unlock()
291383
292384 for _, file := range f.used {
385 _ = file.Close()
386 }
387 f.used = make(map[fileName]*file)
388 }
389
390 func (f *Fds) closeAndRemoveUsed() {
391 f.mu.Lock()
392 defer f.mu.Unlock()
393
394 for key, file := range f.used {
395 if key.isUnix() {
396 // Remove used Unix Domain Sockets if we are shutting
397 // down without having done an upgrade.
398 // This undoes the effect of SetUnlinkOnClose(false).
399 _ = unlinkUnixSocket(key[2])
400 }
293401 _ = file.Close()
294402 }
295403 f.used = make(map[fileName]*file)
309417 dup, duperr = dupFd(fd, name)
310418 })
311419 if err != nil {
312 return nil, errors.Wrap(err, "can't access fd")
420 return nil, fmt.Errorf("can't access fd: %s", err)
313421 }
314422 return dup, duperr
315423 }
316
317 func dupFd(fd uintptr, name fileName) (*file, error) {
318 dupfd, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_DUPFD_CLOEXEC, 0)
319 if errno != 0 {
320 return nil, errors.Wrap(errno, "can't dup fd using fcntl")
321 }
322
323 return newFile(dupfd, name), nil
324 }
00 package tableflip
11
22 import (
3 "io"
34 "io/ioutil"
45 "net"
56 "os"
67 "path/filepath"
8 "runtime"
79 "testing"
810 )
911
12 func TestFdsAddListener(t *testing.T) {
13 socketPath, cleanup := tempSocket(t)
14 defer cleanup()
15
16 addrs := [][2]string{
17 {"unix", socketPath},
18 {"tcp", "localhost:0"},
19 }
20
21 fds := newFds(nil)
22 for _, addr := range addrs {
23 ln, err := net.Listen(addr[0], addr[1])
24 if err != nil {
25 t.Fatal(err)
26 }
27 if err := fds.AddListener(addr[0], addr[1], ln.(Listener)); err != nil {
28 t.Fatalf("Can't add %s listener: %s", addr[0], err)
29 }
30 ln.Close()
31 }
32 }
33
34 func TestFdsAddPacketConn(t *testing.T) {
35 socketPath, cleanup := tempSocket(t)
36 defer cleanup()
37
38 addrs := [][2]string{
39 {"unix", socketPath},
40 {"udp", "localhost:0"},
41 }
42
43 fds := newFds(nil)
44 for _, addr := range addrs {
45 conn, err := net.ListenPacket(addr[0], addr[1])
46 if err != nil {
47 t.Fatal(err)
48 }
49 if err := fds.AddPacketConn(addr[0], addr[1], conn.(PacketConn)); err != nil {
50 t.Fatalf("Can't add %s listener: %s", addr[0], err)
51 }
52 conn.Close()
53 }
54 }
55
56 func tempSocket(t *testing.T) (string, func()) {
57 t.Helper()
58
59 temp, err := ioutil.TempDir("", "tableflip")
60 if err != nil {
61 t.Fatal(err)
62 }
63
64 return filepath.Join(temp, "socket"), func() { os.RemoveAll(temp) }
65 }
66
1067 func TestFdsListen(t *testing.T) {
11 addrs := [][2]string{
12 {"unix", ""},
68 socketPath, cleanup := tempSocket(t)
69 defer cleanup()
70
71 addrs := [][2]string{
1372 {"tcp", "localhost:0"},
14 }
15
16 fds := newFds(nil)
17
18 for _, addr := range addrs {
19 ln, err := fds.Listen(addr[0], addr[1])
20 if err != nil {
21 t.Fatal(err)
73 {"udp", "localhost:0"},
74 {"unix", socketPath},
75 {"unixgram", socketPath + "Unixgram"},
76 }
77
78 // Linux supports the abstract namespace for domain sockets.
79 if runtime.GOOS == "linux" {
80 addrs = append(addrs,
81 [2]string{"unixpacket", socketPath + "Unixpacket"},
82 [2]string{"unix", ""},
83 [2]string{"unixpacket", ""},
84 [2]string{"unixgram", ""},
85 )
86 }
87
88 var (
89 ln io.Closer
90 err error
91 )
92
93 parent := newFds(nil)
94 for _, addr := range addrs {
95 switch addr[0] {
96 case "udp", "unixgram":
97 ln, err = parent.ListenPacket(addr[0], addr[1])
98 default:
99 ln, err = parent.Listen(addr[0], addr[1])
100 }
101 if err != nil {
102 t.Fatalf("Can't create %s listener: %s", addr[0], err)
22103 }
23104 if ln == nil {
24 t.Fatal("Missing listener", addr)
105 t.Fatalf("Got a nil %s listener", addr[0])
25106 }
26107 ln.Close()
27108 }
28 }
29
30 func TestFdsListener(t *testing.T) {
31 addr := &net.TCPAddr{
32 IP: net.ParseIP("127.0.0.1"),
33 Port: 0,
34 }
35
36 tcp, err := net.ListenTCP("tcp", addr)
37 if err != nil {
38 t.Fatal(err)
39 }
40 defer tcp.Close()
41
42 temp, err := ioutil.TempDir("", "tableflip")
43 if err != nil {
44 t.Fatal(err)
45 }
46 defer os.RemoveAll(temp)
47
48 socketPath := filepath.Join(temp, "socket")
49 unix, err := net.Listen("unix", socketPath)
50 if err != nil {
51 t.Fatal(err)
52 }
53 defer unix.Close()
54
55 parent := newFds(nil)
56 if err := parent.AddListener(addr.Network(), addr.String(), tcp); err != nil {
57 t.Fatal("Can't add listener:", err)
58 }
59 tcp.Close()
60
61 if err := parent.AddListener("unix", socketPath, unix.(Listener)); err != nil {
62 t.Fatal("Can't add listener:", err)
63 }
64 unix.Close()
65
66 if _, err := os.Stat(socketPath); err != nil {
67 t.Error("Unix.Close() unlinked socketPath:", err)
68 }
69109
70110 child := newFds(parent.copy())
71 ln, err := child.Listener(addr.Network(), addr.String())
72 if err != nil {
73 t.Fatal("Can't get listener:", err)
74 }
75 if ln == nil {
76 t.Fatal("Missing listener")
77 }
78 ln.Close()
79
80 child.closeInherited()
81 if _, err := os.Stat(socketPath); err == nil {
82 t.Error("closeInherited() did not unlink socketPath")
83 }
111 for _, addr := range addrs {
112 switch addr[0] {
113 case "udp", "unixgram":
114 ln, err = child.PacketConn(addr[0], addr[1])
115 default:
116 ln, err = child.Listener(addr[0], addr[1])
117 }
118 if err != nil {
119 t.Fatalf("Can't get retrieve %s from child: %s", addr[0], err)
120 }
121 if ln == nil {
122 t.Fatalf("Missing %s listener", addr[0])
123 }
124 ln.Close()
125 }
126 }
127
128 func TestFdsRemoveUnix(t *testing.T) {
129 socketPath, cleanup := tempSocket(t)
130 defer cleanup()
131
132 addrs := [][2]string{
133 {"unix", socketPath},
134 {"unixgram", socketPath + "Unixgram"},
135 }
136
137 if runtime.GOOS == "linux" {
138 addrs = append(addrs,
139 [2]string{"unixpacket", socketPath + "Unixpacket"},
140 )
141 }
142
143 makeFds := func(t *testing.T) *Fds {
144 fds := newFds(nil)
145 for _, addr := range addrs {
146 var c io.Closer
147 var err error
148 if addr[0] == "unixgram" {
149 c, err = fds.ListenPacket(addr[0], addr[1])
150 } else {
151 c, err = fds.Listen(addr[0], addr[1])
152 }
153 if err != nil {
154 t.Fatalf("Can't listen on socket %v: %v", addr, err)
155 }
156 c.Close()
157 if _, err := os.Stat(addr[1]); err != nil {
158 t.Errorf("%s Close() unlinked socket: %s", addr[0], err)
159 }
160 }
161 return fds
162 }
163
164 t.Run("closeAndRemoveUsed", func(t *testing.T) {
165 parent := makeFds(t)
166 parent.closeAndRemoveUsed()
167 for _, addr := range addrs {
168 if _, err := os.Stat(addr[1]); err == nil {
169 t.Errorf("Used %s listeners are not removed", addr[0])
170 }
171 }
172 })
173
174 t.Run("closeInherited", func(t *testing.T) {
175 parent := makeFds(t)
176 child := newFds(parent.copy())
177 child.closeInherited()
178 for _, addr := range addrs {
179 if _, err := os.Stat(addr[1]); err == nil {
180 t.Errorf("Inherited but unused %s listeners are not removed", addr[0])
181 }
182 }
183 })
184
185 t.Run("closeUsed", func(t *testing.T) {
186 parent := makeFds(t)
187 parent.closeUsed()
188 for _, addr := range addrs {
189 if _, err := os.Stat(addr[1]); err != nil {
190 t.Errorf("Used %s listeners are removed", addr[0])
191 }
192 }
193 })
84194 }
85195
86196 func TestFdsConn(t *testing.T) {
197 socketPath, cleanup := tempSocket(t)
198 defer cleanup()
87199 unix, err := net.ListenUnixgram("unixgram", &net.UnixAddr{
88200 Net: "unixgram",
89 Name: "",
201 Name: socketPath,
90202 })
91203 if err != nil {
92204 t.Fatal(err)
0 module github.com/cloudflare/tableflip
1
2 go 1.13
(New empty file)
4444 }
4545 }()
4646
47 ln, err := upg.Fds.Listen("tcp", *listenAddr)
47 // Listen must be called before Ready
48 ln, err := upg.Listen("tcp", *listenAddr)
4849 if err != nil {
4950 log.Fatalln("Can't listen:", err)
5051 }
11
22 import (
33 "encoding/gob"
4 "errors"
5 "fmt"
46 "io"
57 "io/ioutil"
68 "os"
7
8 "github.com/pkg/errors"
99 )
1010
1111 const (
3030 var names [][]string
3131 dec := gob.NewDecoder(rd)
3232 if err := dec.Decode(&names); err != nil {
33 return nil, nil, errors.Wrap(err, "can't decode names from parent process")
33 return nil, nil, fmt.Errorf("can't decode names from parent process: %s", err)
3434 }
3535
3636 files := make(map[fileName]*file)
5757 if n != 0 {
5858 err = errors.New("unexpected data from parent process")
5959 } else if err != nil {
60 err = errors.Wrap(err, "unexpected error while waiting for parent to exit")
60 err = fmt.Errorf("unexpected error while waiting for parent to exit: %s", err)
6161 }
6262 result <- err
6363 close(exited)
7373 func (ps *parent) sendReady() error {
7474 defer ps.wr.Close()
7575 if _, err := ps.wr.Write([]byte{notifyReady}); err != nil {
76 return errors.Wrap(err, "can't notify parent process")
76 return fmt.Errorf("can't notify parent process: %s", err)
7777 }
7878 return nil
7979 }
0 package testing
1
2 import (
3 "net"
4 "os"
5 )
6
7 type Fds struct{}
8
9 // Listen returns a listener by calling net.Listen directly
10 //
11 // Note: In the stub implementation, this is the only function that
12 // actually does anything
13 func (f *Fds) Listen(network, addr string) (net.Listener, error) {
14 return net.Listen(network, addr)
15 }
16
17 // Listener always returns nil, since it is impossible to inherit with
18 // the stub implementation
19 func (f *Fds) Listener(network, addr string) (net.Listener, error) {
20 return nil, nil
21 }
22
23 // AddListener does nothing, since there is no reason to track connections
24 // in the stub implementation
25 func (f *Fds) AddListener(network, addr string, ln net.Listener) error {
26 return nil
27 }
28
29 // Conn always returns nil, since it is impossible to inherit with
30 // the stub implementation
31 func (f *Fds) Conn(network, addr string) (net.Conn, error) {
32 return nil, nil
33 }
34
35 // AddConn does nothing, since there is no reason to track connections
36 // in the stub implementation
37 func (f *Fds) AddConn(network, addr string, conn net.Conn) error {
38 return nil
39 }
40
41 // File always returns nil, since it is impossible to inherit with
42 // the stub implementation
43 func (f *Fds) File(name string) (*os.File, error) {
44 return nil, nil
45 }
46
47 // AddFile does nothing, since there is no reason to track connections
48 // in the stub implementation
49 func (f *Fds) AddFile(name string, file *os.File) error {
50 return nil
51 }
0 package testing
1
2 import (
3 "testing"
4 )
5
6 func TestFdsListen(t *testing.T) {
7 addrs := [][2]string{
8 {"tcp", "localhost:0"},
9 }
10
11 fds := &Fds{}
12
13 for _, addr := range addrs {
14 ln, err := fds.Listen(addr[0], addr[1])
15 if err != nil {
16 t.Fatal(err)
17 }
18 if ln == nil {
19 t.Fatal("Missing listener", addr)
20 }
21 ln.Close()
22 }
23 }
0 package testing_test
1
2 import (
3 "context"
4 "errors"
5 "flag"
6 "fmt"
7 "log"
8 "net"
9 "net/http"
10 "os"
11 "os/signal"
12 "syscall"
13 "time"
14
15 "github.com/cloudflare/tableflip"
16 "github.com/cloudflare/tableflip/testing"
17 )
18
19 type upgrader interface {
20 Listen(network, addr string) (net.Listener, error)
21 Stop()
22 Upgrade() error
23 Ready() error
24 Exit() <-chan struct{}
25 }
26
27 // This shows how to use the upgrader
28 // with the graceful shutdown facilities of net/http
29 // and using the stub implementation if on an unsupported platform.
30 func Example_httpShutdown() {
31 var (
32 listenAddr = flag.String("listen", "localhost:8080", "`Address` to listen on")
33 pidFile = flag.String("pid-file", "", "`Path` to pid file")
34 )
35
36 flag.Parse()
37 log.SetPrefix(fmt.Sprintf("%d ", os.Getpid()))
38
39 var upg upgrader
40 upg, err := tableflip.New(tableflip.Options{
41 PIDFile: *pidFile,
42 })
43 if errors.Is(err, tableflip.ErrNotSupported) {
44 upg, _ = testing.New()
45 } else if err != nil {
46 panic(err)
47 }
48 defer upg.Stop()
49
50 // Do an upgrade on SIGHUP
51 // NOTE: With `testing.Upgrader` this goroutine is useless
52 // You may choose to enclose it inside an `if` statement block.
53 go func() {
54 sig := make(chan os.Signal, 1)
55 signal.Notify(sig, syscall.SIGHUP)
56 for range sig {
57 err := upg.Upgrade()
58 if err != nil {
59 log.Println("Upgrade failed:", err)
60 }
61 }
62 }()
63
64 // Listen must be called before Ready
65 ln, err := upg.Listen("tcp", *listenAddr)
66 if err != nil {
67 log.Fatalln("Can't listen:", err)
68 }
69
70 server := http.Server{
71 // Set timeouts, etc.
72 }
73
74 go func() {
75 err := server.Serve(ln)
76 if err != http.ErrServerClosed {
77 log.Println("HTTP server:", err)
78 }
79 }()
80
81 log.Printf("ready")
82 if err := upg.Ready(); err != nil {
83 panic(err)
84 }
85 <-upg.Exit()
86
87 // Make sure to set a deadline on exiting the process
88 // after upg.Exit() is closed. No new upgrades can be
89 // performed if the parent doesn't exit.
90 time.AfterFunc(30*time.Second, func() {
91 log.Println("Graceful shutdown timed out")
92 os.Exit(1)
93 })
94
95 // Wait for connections to drain.
96 server.Shutdown(context.Background())
97 }
0 // Package testing provides a stub implementation that can be used for
1 // simplified testing of applications that normally use tableflip.
2 // It is also helpful for allowing projects that use tableflip
3 // able to run on Windows, which does not support tableflip.
4 package testing
5
6 import (
7 "context"
8
9 "github.com/cloudflare/tableflip"
10 )
11
12 // Upgrader has all the methods of tableflip.Upgrader, but they don't
13 // actually do anything special.
14 type Upgrader struct {
15 *Fds
16 }
17
18 // New creates a new stub Upgrader.
19 //
20 // Unlike the real version, this can be called many times.
21 func New() (*Upgrader, error) {
22 upg := newStubUpgrader()
23
24 return upg, nil
25 }
26
27 func newStubUpgrader() *Upgrader {
28 return &Upgrader{
29 &Fds{},
30 }
31 }
32
33 // Ready does nothing, since it is impossible to inherit with
34 // the stub implementation.
35 // However, the function still needs to be callable without errors
36 // in order to be useful.
37 func (u *Upgrader) Ready() error {
38 return nil
39 }
40
41 // Exit returns a channel which is closed when the process should
42 // exit.
43 // We can return nil here because reading from a nil channel blocks
44 func (u *Upgrader) Exit() <-chan struct{} {
45 return nil
46 }
47
48 // Stop does nothing, since there will never be anything to stop
49 // in the stub implementation
50 func (u *Upgrader) Stop() {
51 }
52
53 // WaitForParent returns immediately, since the stub implementation
54 // can never be a parent
55 func (u *Upgrader) WaitForParent(ctx context.Context) error {
56 return nil
57 }
58
59 // HasParent is always false, since the stub implementation can never
60 // have a parent
61 func (u *Upgrader) HasParent() bool {
62 return false
63 }
64
65 // Upgrade always returns an error in the stub implementation,
66 // since nothing can be done.
67 func (u *Upgrader) Upgrade() error {
68 return tableflip.ErrNotSupported
69 }
11
22 import (
33 "context"
4 "errors"
5 "fmt"
46 "io/ioutil"
57 "os"
68 "path/filepath"
9 "runtime"
710 "strconv"
811 "sync"
912 "time"
10
11 "github.com/pkg/errors"
1213 )
1314
1415 // DefaultUpgradeTimeout is the duration before the Upgrader kills the new process if no
2627
2728 // Upgrader handles zero downtime upgrades and passing files between processes.
2829 type Upgrader struct {
30 *Fds
31
2932 *env
3033 opts Options
3134 parent *parent
3740 upgradeC chan chan<- error
3841 exitC chan struct{}
3942 exitFd chan neverCloseThisFile
40
41 Fds *Fds
4243 }
4344
4445 var (
4647 stdEnvUpgrader *Upgrader
4748 )
4849
50 var ErrNotSupported = errors.New("tableflip: platform does not support graceful restart")
51
4952 // New creates a new Upgrader. Files are passed from the parent and may be empty.
5053 //
51 // Only the first call to this function will succeed.
54 // Only the first call to this function will succeed. May return ErrNotSupported.
5255 func New(opts Options) (upg *Upgrader, err error) {
5356 stdEnvMu.Lock()
5457 defer stdEnvMu.Unlock()
58
59 if !isSupportedOS() {
60 return nil, fmt.Errorf("%w", ErrNotSupported)
61 }
5562
5663 if stdEnvUpgrader != nil {
5764 return nil, errors.New("tableflip: only a single Upgrader allowed")
6572 }
6673
6774 func newUpgrader(env *env, opts Options) (*Upgrader, error) {
75 if initialWD == "" {
76 return nil, errors.New("couldn't determine initial working directory")
77 }
78
6879 parent, files, err := newParent(env)
6980 if err != nil {
7081 return nil, err
104115
105116 if u.opts.PIDFile != "" {
106117 if err := writePIDFile(u.opts.PIDFile); err != nil {
107 return errors.Wrap(err, "tableflip: can't write PID file")
118 return fmt.Errorf("tableflip: can't write PID file: %s", err)
108119 }
109120 }
110121
122133
123134 // Stop prevents any more upgrades from happening, and closes
124135 // the exit channel.
136 //
137 // If this function is called before a call to Upgrade() has
138 // succeeded, it is assumed that the process is being shut down
139 // completely. All Unix sockets known to Upgrader.Fds are then
140 // unlinked from the filesystem.
125141 func (u *Upgrader) Stop() {
126142 u.stopOnce.Do(func() {
127143 // Interrupt any running Upgrade(), and
176192
177193 func (u *Upgrader) run() {
178194 defer close(u.exitC)
179 defer u.Fds.closeUsed()
180195
181196 var (
182197 parentExited <-chan struct{}
196211 processReady = nil
197212
198213 case <-u.stopC:
214 u.Fds.closeAndRemoveUsed()
199215 return
200216
201217 case request := <-u.upgradeC:
217233 // exits. This signals to the new process that the old process
218234 // has exited.
219235 u.exitFd <- neverCloseThisFile{file}
236 u.Fds.closeUsed()
220237 return
221238 }
222239 }
226243 func (u *Upgrader) doUpgrade() (*os.File, error) {
227244 child, err := startChild(u.env, u.Fds.copy())
228245 if err != nil {
229 return nil, errors.Wrap(err, "can't start child")
246 return nil, fmt.Errorf("can't start child: %s", err)
230247 }
231248
232249 readyTimeout := time.After(u.opts.UpgradeTimeout)
237254
238255 case err := <-child.result:
239256 if err == nil {
240 return nil, errors.Errorf("child %s exited", child)
257 return nil, fmt.Errorf("child %s exited", child)
241258 }
242 return nil, errors.Wrapf(err, "child %s exited", child)
259 return nil, fmt.Errorf("child %s exited: %s", child, err)
243260
244261 case <-u.stopC:
245262 child.Kill()
247264
248265 case <-readyTimeout:
249266 child.Kill()
250 return nil, errors.Errorf("new child %s timed out", child)
267 return nil, fmt.Errorf("new child %s timed out", child)
251268
252269 case file := <-child.ready:
253270 return file, nil
266283
267284 func writePIDFile(path string) error {
268285 dir, file := filepath.Split(path)
286
287 // if dir is empty, the user probably specified just the name
288 // of the pid file expecting it to be created in the current work directory
289 if dir == "" {
290 dir = initialWD
291 }
292
293 if dir == "" {
294 return errors.New("empty initial working directory")
295 }
296
269297 fh, err := ioutil.TempFile(dir, file)
270298 if err != nil {
271299 return err
281309
282310 return os.Rename(fh.Name(), path)
283311 }
312
313 // Check if this is a supported OS.
314 // That is currently all Unix-like OS's.
315 // At the moment, we assume that is everything except Windows.
316 func isSupportedOS() bool {
317 return runtime.GOOS != "windows"
318 }
22 import (
33 "bytes"
44 "context"
5 "encoding/binary"
5 "encoding/gob"
66 "errors"
77 "fmt"
88 "io"
6363
6464 func TestMain(m *testing.M) {
6565 upg, err := New(Options{})
66 if errors.Is(err, ErrNotSupported) {
67 fmt.Fprintln(os.Stderr, "Skipping tests, OS is not supported")
68 os.Exit(0)
69 }
6670 if err != nil {
6771 panic(err)
6872 }
7276 os.Exit(m.Run())
7377 }
7478
75 pid, err := upg.Fds.File("pid")
76 if err != nil {
77 panic(err)
78 }
79
80 if pid != nil {
81 buf := make([]byte, 8)
82 binary.LittleEndian.PutUint64(buf, uint64(os.Getpid()))
83 pid.Write(buf)
84 pid.Close()
85 }
86
87 parent, err := upg.Fds.File("hasParent")
88 if err != nil {
89 panic(err)
90 }
91
92 if parent != nil {
93 if _, err := io.WriteString(parent, fmt.Sprint(upg.HasParent())); err != nil {
94 panic(err)
95 }
96 parent.Close()
79 if err := childProcess(upg); err != nil {
80 fmt.Fprintf(os.Stderr, "Error: %s\n", err)
81 os.Exit(1)
82 }
83 }
84
85 type childState struct {
86 PID int
87 }
88
89 // Used by Benchmark and TestUpgraderOnOS
90 func childProcess(upg *Upgrader) error {
91 if !upg.HasParent() {
92 return errors.New("Upgrader doesn't recognize parent")
93 }
94
95 wState, err := upg.Fds.File("wState")
96 if err != nil {
97 return err
98 }
99 if wState != nil {
100 state := &childState{
101 PID: os.Getpid(),
102 }
103 if err := gob.NewEncoder(wState).Encode(state); err != nil {
104 return err
105 }
106 wState.Close()
97107 }
98108
99109 for _, name := range names {
100110 file, err := upg.Fds.File(name)
101111 if err != nil {
102 panic(err)
112 return fmt.Errorf("can't get file %s: %s", name, err)
103113 }
104114 if file == nil {
105115 continue
106116 }
107117 if _, err := io.WriteString(file, name); err != nil {
108 panic(err)
109 }
110 }
111
118 return fmt.Errorf("can't write to %s: %s", name, err)
119 }
120 file.Close()
121 }
122
123 rExit, err := upg.Fds.File("rExit")
124 if err != nil {
125 return err
126 }
127
128 // Ready closes all inherited but unused files.
112129 if err := upg.Ready(); err != nil {
113 panic(err)
114 }
130 return fmt.Errorf("can't signal ready: %s", err)
131 }
132
133 // Block until the parent is done with us. Returning an
134 // error here won't make the parent fail, so don't bother.
135 if rExit != nil {
136 var b [1]byte
137 rExit.Read(b[:])
138 }
139
140 return nil
115141 }
116142
117143 func TestUpgraderOnOS(t *testing.T) {
121147 }
122148 defer u.Stop()
123149
124 rPid, wPid, err := os.Pipe()
125 if err != nil {
126 t.Fatal(err)
127 }
128 defer rPid.Close()
129
130 if err := u.Fds.AddFile("pid", wPid); err != nil {
131 t.Fatal(err)
132 }
133 wPid.Close()
134
135 rHasParent, wHasParent, err := os.Pipe()
136 if err != nil {
137 t.Fatal(err)
138 }
139 defer rHasParent.Close()
140
141 if err := u.Fds.AddFile("hasParent", wHasParent); err != nil {
142 t.Fatal(err)
143 }
144 wHasParent.Close()
150 pipe := func() (r, w *os.File) {
151 t.Helper()
152
153 r, w, err := os.Pipe()
154 if err != nil {
155 t.Fatal(err)
156 }
157 return r, w
158 }
159
160 addPipe := func(name string, file *os.File) {
161 t.Helper()
162
163 if err := u.Fds.AddFile(name, file); err != nil {
164 t.Fatal(err)
165 }
166 file.Close()
167 }
168
169 rState, wState := pipe()
170 defer rState.Close()
171
172 addPipe("wState", wState)
173
174 rExit, wExit := pipe()
175 defer wExit.Close()
176
177 addPipe("rExit", rExit)
145178
146179 var readers []*os.File
147180 defer func() {
151184 }()
152185
153186 for _, name := range names {
154 r, w, err := os.Pipe()
155 if err != nil {
156 t.Fatal(err)
157 }
187 r, w := pipe()
188 addPipe(name, w)
158189 readers = append(readers, r)
159
160 if err := u.Fds.AddFile(name, w); err != nil {
161 t.Fatal(err)
162 }
163 w.Close()
164190 }
165191
166192 if err := u.Upgrade(); err == nil {
179205 }
180206 }
181207
208 // Tell child it's OK to exit now.
209 wExit.Close()
210
182211 // Close copies of write pipes, so that
183212 // reads below return EOF.
184213 u.Stop()
185214
186 buf := make([]byte, 8)
187 if _, err := rPid.Read(buf); err != nil {
188 t.Fatal(err)
189 }
190
191 if int(binary.LittleEndian.Uint64(buf)) == os.Getpid() {
215 var state childState
216 if err := gob.NewDecoder(rState).Decode(&state); err != nil {
217 t.Fatal("Can't decode state from child:", err)
218 }
219
220 if state.PID == os.Getpid() {
192221 t.Error("Child did not execute in new process")
193 }
194
195 hasParentBytes, err := ioutil.ReadAll(rHasParent)
196 if err != nil {
197 t.Fatal(err)
198 }
199 if !bytes.Equal(hasParentBytes, []byte("true")) {
200 t.Fatal("Child did not recognize parent")
201222 }
202223
203224 for i, name := range names {
409430 }
410431 defer fh.Close()
411432
433 var pid int
434 if _, err := fmt.Fscan(fh, &pid); err != nil {
435 t.Fatal("Can't read PID:", err)
436 }
437
438 if pid != os.Getpid() {
439 t.Error("PID doesn't match")
440 }
441 }
442
443 func TestWritePidFileWithoutPath(t *testing.T) {
444 pidFile := "tableflip-test.pid"
445
446 err := writePIDFile(pidFile)
447 if err != nil {
448 t.Fatal("Could not write pidfile:", err)
449 }
450 defer os.Remove(pidFile)
451
452 // lets see if we are able to read the file back
453 fh, err := os.Open(pidFile)
454 if err != nil {
455 t.Fatal("PID file doesn't exist:", err)
456 }
457 defer fh.Close()
458
459 // just to be sure: check the pid for correctness
460 // if something failed at a previous run we could be reading
461 // a bogus pidfile
412462 var pid int
413463 if _, err := fmt.Fscan(fh, &pid); err != nil {
414464 t.Fatal("Can't read PID:", err)