├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── endless.go ├── examples ├── README.md ├── hook.go ├── multi_port.go ├── simple.go ├── testserver.go └── tls.go └── test ├── restart_server.sh ├── stop_server.sh └── test_restarting.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | *~ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Florian von Bock 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # endless 2 | 3 | Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3+) 4 | 5 | [![GoDoc](https://godoc.org/github.com/fvbock/endless?status.svg)](https://godoc.org/github.com/fvbock/endless) 6 | 7 | ## Inspiration & Credits 8 | 9 | Well... it's what you want right - no need to hook in and out on a loadbalancer or something - just compile, SIGHUP, start new one, finish old requests etc. 10 | 11 | There is https://github.com/rcrowley/goagain and i looked at https://fitstar.github.io/falcore/hot_restart.html which looked easier to do, but still some assembly required. I wanted something that's ideally as simple as 12 | 13 | err := endless.ListenAndServe("localhost:4242", mux) 14 | 15 | I found the excellent post [Graceful Restart in Golang](http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/) by [Grisha Trubetskoy](https://github.com/grisha) and took his code as a start. So a lot of credit to Grisha! 16 | 17 | 18 | ## Features 19 | 20 | - Drop-in replacement for `http.ListenAndServe` and `http.ListenAndServeTLS` 21 | - Signal hooks to execute your own code before or after the listened to signals (SIGHUP, SIGUSR1, SIGUSR2, SIGINT, SIGTERM, SIGTSTP) 22 | - You can start multiple servers from one binary and endless will take care of the different sockets/ports assignments when restarting 23 | 24 | 25 | ## Default Timeouts & MaxHeaderBytes 26 | 27 | There are three variables exported by the package that control the values set for `DefaultReadTimeOut`, `DefaultWriteTimeOut`, and `MaxHeaderBytes` on the inner [`http.Server`](https://golang.org/pkg/net/http/#Server): 28 | 29 | DefaultReadTimeOut time.Duration 30 | DefaultWriteTimeOut time.Duration 31 | DefaultMaxHeaderBytes int 32 | 33 | The endless default behaviour is to use the same defaults defined in `net/http`. 34 | 35 | These have impact on endless by potentially not letting the parent process die until all connections are handled/finished. 36 | 37 | 38 | ### Hammer Time 39 | 40 | To deal with hanging requests on the parent after restarting endless will *hammer* the parent 60 seconds after receiving the shutdown signal from the forked child process. When hammered still running requests get terminated. This behaviour can be controlled by another exported variable: 41 | 42 | DefaultHammerTime time.Duration 43 | 44 | The default is 60 seconds. When set to `-1` `hammerTime()` is not invoked automatically. You can then hammer the parent manually by sending `SIGUSR2`. This will only hammer the parent if it is already in shutdown mode. So unless the process had received a `SIGTERM`, `SIGSTOP`, or `SIGINT` (manually or by forking) before `SIGUSR2` will be ignored. 45 | 46 | If you had hanging requests and the server got hammered you will see a log message like this: 47 | 48 | 2015/04/04 13:04:10 [STOP - Hammer Time] Forcefully shutting down parent 49 | 50 | 51 | ## Examples & Documentation 52 | 53 | import "github.com/fvbock/endless" 54 | 55 | and then replacing `http.ListenAndServe` with `endless.ListenAndServe` or `http.ListenAndServeTLS` with `endless.ListenAndServeTLS` 56 | 57 | err := endless.ListenAndServe("localhost:4242", handler) 58 | 59 | After starting your server you can make some changes, build, and send `SIGHUP` to the running process and it will finish handling any outstanding requests and serve all new incoming ones with the new binary. 60 | 61 | More examples are in [here](https://github.com/fvbock/endless/tree/master/examples) 62 | 63 | There is also [GoDoc Documentation](https://godoc.org/github.com/fvbock/endless) 64 | 65 | 66 | ## Signals 67 | 68 | The endless server will listen for the following signals: `syscall.SIGHUP`, `syscall.SIGUSR1`, `syscall.SIGUSR2`, `syscall.SIGINT`, `syscall.SIGTERM`, and `syscall.SIGTSTP`: 69 | 70 | `SIGHUP` will trigger a fork/restart 71 | 72 | `syscall.SIGINT` and `syscall.SIGTERM` will trigger a shutdown of the server (it will finish running requests) 73 | 74 | `SIGUSR2` will trigger [hammerTime](https://github.com/fvbock/endless#hammer-time) 75 | 76 | `SIGUSR1` and `SIGTSTP` are listened for but do not trigger anything in the endless server itself. (probably useless - might get rid of those two) 77 | 78 | You can hook your own functions to be called *pre* or *post* signal handling - eg. pre fork or pre shutdown. More about that in the [hook example](https://github.com/fvbock/endless/tree/master/examples#hooking-into-the-signal-handling). 79 | 80 | 81 | ## Limitation: No changing of ports 82 | 83 | Currently you cannot restart a server on a different port than the previous version was running on. 84 | 85 | ## PID file 86 | 87 | If you want to save actual pid file, you can change the `BeforeBegin` hook like this: 88 | 89 | server := endless.NewServer("localhost:4242", handler) 90 | server.BeforeBegin = func(add string) { 91 | log.Printf("Actual pid is %d", syscall.Getpid()) 92 | // save it somehow 93 | } 94 | err := server.ListenAndServe() 95 | 96 | 97 | ## TODOs 98 | 99 | - tests 100 | - documentation 101 | - less ugly wrapping of the tls.listener 102 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | endless provides a drop in replacement for the `net/http` stl functions `ListenAndServe` and `ListenAndServeTLS` to achieve zero downtime restarts and code upgrades. 3 | 4 | The code is based on the excellent post [Graceful Restart in Golang](http://grisha.org/blog/2014/06/03/graceful-restart-in-golang/) by [Grisha Trubetskoy](https://github.com/grisha). I took his code as a start. So a lot of credit to Grisha! 5 | */ 6 | package endless 7 | -------------------------------------------------------------------------------- /endless.go: -------------------------------------------------------------------------------- 1 | package endless 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os" 11 | "os/exec" 12 | "os/signal" 13 | "runtime" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | "time" 18 | 19 | // "github.com/fvbock/uds-go/introspect" 20 | ) 21 | 22 | const ( 23 | PRE_SIGNAL = iota 24 | POST_SIGNAL 25 | 26 | STATE_INIT 27 | STATE_RUNNING 28 | STATE_SHUTTING_DOWN 29 | STATE_TERMINATE 30 | ) 31 | 32 | var ( 33 | runningServerReg sync.RWMutex 34 | runningServers map[string]*endlessServer 35 | runningServersOrder []string 36 | socketPtrOffsetMap map[string]uint 37 | runningServersForked bool 38 | 39 | DefaultReadTimeOut time.Duration 40 | DefaultWriteTimeOut time.Duration 41 | DefaultMaxHeaderBytes int 42 | DefaultHammerTime time.Duration 43 | 44 | isChild bool 45 | socketOrder string 46 | 47 | hookableSignals []os.Signal 48 | ) 49 | 50 | func init() { 51 | runningServerReg = sync.RWMutex{} 52 | runningServers = make(map[string]*endlessServer) 53 | runningServersOrder = []string{} 54 | socketPtrOffsetMap = make(map[string]uint) 55 | 56 | DefaultMaxHeaderBytes = 0 // use http.DefaultMaxHeaderBytes - which currently is 1 << 20 (1MB) 57 | 58 | // after a restart the parent will finish ongoing requests before 59 | // shutting down. set to a negative value to disable 60 | DefaultHammerTime = 60 * time.Second 61 | 62 | hookableSignals = []os.Signal{ 63 | syscall.SIGHUP, 64 | syscall.SIGUSR1, 65 | syscall.SIGUSR2, 66 | syscall.SIGINT, 67 | syscall.SIGTERM, 68 | syscall.SIGTSTP, 69 | } 70 | } 71 | 72 | type endlessServer struct { 73 | http.Server 74 | EndlessListener net.Listener 75 | SignalHooks map[int]map[os.Signal][]func() 76 | tlsInnerListener *endlessListener 77 | wg sync.WaitGroup 78 | sigChan chan os.Signal 79 | isChild bool 80 | state uint8 81 | lock *sync.RWMutex 82 | BeforeBegin func(add string) 83 | } 84 | 85 | /* 86 | NewServer returns an intialized endlessServer Object. Calling Serve on it will 87 | actually "start" the server. 88 | */ 89 | func NewServer(addr string, handler http.Handler) (srv *endlessServer) { 90 | runningServerReg.Lock() 91 | defer runningServerReg.Unlock() 92 | 93 | socketOrder = os.Getenv("ENDLESS_SOCKET_ORDER") 94 | isChild = os.Getenv("ENDLESS_CONTINUE") != "" 95 | 96 | if len(socketOrder) > 0 { 97 | for i, addr := range strings.Split(socketOrder, ",") { 98 | socketPtrOffsetMap[addr] = uint(i) 99 | } 100 | } else { 101 | socketPtrOffsetMap[addr] = uint(len(runningServersOrder)) 102 | } 103 | 104 | srv = &endlessServer{ 105 | wg: sync.WaitGroup{}, 106 | sigChan: make(chan os.Signal), 107 | isChild: isChild, 108 | SignalHooks: map[int]map[os.Signal][]func(){ 109 | PRE_SIGNAL: map[os.Signal][]func(){ 110 | syscall.SIGHUP: []func(){}, 111 | syscall.SIGUSR1: []func(){}, 112 | syscall.SIGUSR2: []func(){}, 113 | syscall.SIGINT: []func(){}, 114 | syscall.SIGTERM: []func(){}, 115 | syscall.SIGTSTP: []func(){}, 116 | }, 117 | POST_SIGNAL: map[os.Signal][]func(){ 118 | syscall.SIGHUP: []func(){}, 119 | syscall.SIGUSR1: []func(){}, 120 | syscall.SIGUSR2: []func(){}, 121 | syscall.SIGINT: []func(){}, 122 | syscall.SIGTERM: []func(){}, 123 | syscall.SIGTSTP: []func(){}, 124 | }, 125 | }, 126 | state: STATE_INIT, 127 | lock: &sync.RWMutex{}, 128 | } 129 | 130 | srv.Server.Addr = addr 131 | srv.Server.ReadTimeout = DefaultReadTimeOut 132 | srv.Server.WriteTimeout = DefaultWriteTimeOut 133 | srv.Server.MaxHeaderBytes = DefaultMaxHeaderBytes 134 | srv.Server.Handler = handler 135 | 136 | srv.BeforeBegin = func(addr string) { 137 | log.Println(syscall.Getpid(), addr) 138 | } 139 | 140 | runningServersOrder = append(runningServersOrder, addr) 141 | runningServers[addr] = srv 142 | 143 | return 144 | } 145 | 146 | /* 147 | ListenAndServe listens on the TCP network address addr and then calls Serve 148 | with handler to handle requests on incoming connections. Handler is typically 149 | nil, in which case the DefaultServeMux is used. 150 | */ 151 | func ListenAndServe(addr string, handler http.Handler) error { 152 | server := NewServer(addr, handler) 153 | return server.ListenAndServe() 154 | } 155 | 156 | /* 157 | ListenAndServeTLS acts identically to ListenAndServe, except that it expects 158 | HTTPS connections. Additionally, files containing a certificate and matching 159 | private key for the server must be provided. If the certificate is signed by a 160 | certificate authority, the certFile should be the concatenation of the server's 161 | certificate followed by the CA's certificate. 162 | */ 163 | func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error { 164 | server := NewServer(addr, handler) 165 | return server.ListenAndServeTLS(certFile, keyFile) 166 | } 167 | 168 | func (srv *endlessServer) getState() uint8 { 169 | srv.lock.RLock() 170 | defer srv.lock.RUnlock() 171 | 172 | return srv.state 173 | } 174 | 175 | func (srv *endlessServer) setState(st uint8) { 176 | srv.lock.Lock() 177 | defer srv.lock.Unlock() 178 | 179 | srv.state = st 180 | } 181 | 182 | /* 183 | Serve accepts incoming HTTP connections on the listener l, creating a new 184 | service goroutine for each. The service goroutines read requests and then call 185 | handler to reply to them. Handler is typically nil, in which case the 186 | DefaultServeMux is used. 187 | 188 | In addition to the stl Serve behaviour each connection is added to a 189 | sync.Waitgroup so that all outstanding connections can be served before shutting 190 | down the server. 191 | */ 192 | func (srv *endlessServer) Serve() (err error) { 193 | defer log.Println(syscall.Getpid(), "Serve() returning...") 194 | srv.setState(STATE_RUNNING) 195 | err = srv.Server.Serve(srv.EndlessListener) 196 | log.Println(syscall.Getpid(), "Waiting for connections to finish...") 197 | srv.wg.Wait() 198 | srv.setState(STATE_TERMINATE) 199 | return 200 | } 201 | 202 | /* 203 | ListenAndServe listens on the TCP network address srv.Addr and then calls Serve 204 | to handle requests on incoming connections. If srv.Addr is blank, ":http" is 205 | used. 206 | */ 207 | func (srv *endlessServer) ListenAndServe() (err error) { 208 | addr := srv.Addr 209 | if addr == "" { 210 | addr = ":http" 211 | } 212 | 213 | go srv.handleSignals() 214 | 215 | l, err := srv.getListener(addr) 216 | if err != nil { 217 | log.Println(err) 218 | return 219 | } 220 | 221 | srv.EndlessListener = newEndlessListener(l, srv) 222 | 223 | if srv.isChild { 224 | syscall.Kill(syscall.Getppid(), syscall.SIGTERM) 225 | } 226 | 227 | srv.BeforeBegin(srv.Addr) 228 | 229 | return srv.Serve() 230 | } 231 | 232 | /* 233 | ListenAndServeTLS listens on the TCP network address srv.Addr and then calls 234 | Serve to handle requests on incoming TLS connections. 235 | 236 | Filenames containing a certificate and matching private key for the server must 237 | be provided. If the certificate is signed by a certificate authority, the 238 | certFile should be the concatenation of the server's certificate followed by the 239 | CA's certificate. 240 | 241 | If srv.Addr is blank, ":https" is used. 242 | */ 243 | func (srv *endlessServer) ListenAndServeTLS(certFile, keyFile string) (err error) { 244 | addr := srv.Addr 245 | if addr == "" { 246 | addr = ":https" 247 | } 248 | 249 | config := &tls.Config{} 250 | if srv.TLSConfig != nil { 251 | *config = *srv.TLSConfig 252 | } 253 | if config.NextProtos == nil { 254 | config.NextProtos = []string{"http/1.1"} 255 | } 256 | 257 | config.Certificates = make([]tls.Certificate, 1) 258 | config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 259 | if err != nil { 260 | return 261 | } 262 | 263 | go srv.handleSignals() 264 | 265 | l, err := srv.getListener(addr) 266 | if err != nil { 267 | log.Println(err) 268 | return 269 | } 270 | 271 | srv.tlsInnerListener = newEndlessListener(l, srv) 272 | srv.EndlessListener = tls.NewListener(srv.tlsInnerListener, config) 273 | 274 | if srv.isChild { 275 | syscall.Kill(syscall.Getppid(), syscall.SIGTERM) 276 | } 277 | 278 | log.Println(syscall.Getpid(), srv.Addr) 279 | return srv.Serve() 280 | } 281 | 282 | /* 283 | getListener either opens a new socket to listen on, or takes the acceptor socket 284 | it got passed when restarted. 285 | */ 286 | func (srv *endlessServer) getListener(laddr string) (l net.Listener, err error) { 287 | if srv.isChild { 288 | var ptrOffset uint = 0 289 | runningServerReg.RLock() 290 | defer runningServerReg.RUnlock() 291 | if len(socketPtrOffsetMap) > 0 { 292 | ptrOffset = socketPtrOffsetMap[laddr] 293 | // log.Println("laddr", laddr, "ptr offset", socketPtrOffsetMap[laddr]) 294 | } 295 | 296 | f := os.NewFile(uintptr(3+ptrOffset), "") 297 | l, err = net.FileListener(f) 298 | if err != nil { 299 | err = fmt.Errorf("net.FileListener error: %v", err) 300 | return 301 | } 302 | } else { 303 | l, err = net.Listen("tcp", laddr) 304 | if err != nil { 305 | err = fmt.Errorf("net.Listen error: %v", err) 306 | return 307 | } 308 | } 309 | return 310 | } 311 | 312 | /* 313 | handleSignals listens for os Signals and calls any hooked in function that the 314 | user had registered with the signal. 315 | */ 316 | func (srv *endlessServer) handleSignals() { 317 | var sig os.Signal 318 | 319 | signal.Notify( 320 | srv.sigChan, 321 | hookableSignals..., 322 | ) 323 | 324 | pid := syscall.Getpid() 325 | for { 326 | sig = <-srv.sigChan 327 | srv.signalHooks(PRE_SIGNAL, sig) 328 | switch sig { 329 | case syscall.SIGHUP: 330 | log.Println(pid, "Received SIGHUP. forking.") 331 | err := srv.fork() 332 | if err != nil { 333 | log.Println("Fork err:", err) 334 | } 335 | case syscall.SIGUSR1: 336 | log.Println(pid, "Received SIGUSR1.") 337 | case syscall.SIGUSR2: 338 | log.Println(pid, "Received SIGUSR2.") 339 | srv.hammerTime(0 * time.Second) 340 | case syscall.SIGINT: 341 | log.Println(pid, "Received SIGINT.") 342 | srv.shutdown() 343 | case syscall.SIGTERM: 344 | log.Println(pid, "Received SIGTERM.") 345 | srv.shutdown() 346 | case syscall.SIGTSTP: 347 | log.Println(pid, "Received SIGTSTP.") 348 | default: 349 | log.Printf("Received %v: nothing i care about...\n", sig) 350 | } 351 | srv.signalHooks(POST_SIGNAL, sig) 352 | } 353 | } 354 | 355 | func (srv *endlessServer) signalHooks(ppFlag int, sig os.Signal) { 356 | if _, notSet := srv.SignalHooks[ppFlag][sig]; !notSet { 357 | return 358 | } 359 | for _, f := range srv.SignalHooks[ppFlag][sig] { 360 | f() 361 | } 362 | return 363 | } 364 | 365 | /* 366 | shutdown closes the listener so that no new connections are accepted. it also 367 | starts a goroutine that will hammer (stop all running requests) the server 368 | after DefaultHammerTime. 369 | */ 370 | func (srv *endlessServer) shutdown() { 371 | if srv.getState() != STATE_RUNNING { 372 | return 373 | } 374 | 375 | srv.setState(STATE_SHUTTING_DOWN) 376 | if DefaultHammerTime >= 0 { 377 | go srv.hammerTime(DefaultHammerTime) 378 | } 379 | // disable keep-alives on existing connections 380 | srv.SetKeepAlivesEnabled(false) 381 | err := srv.EndlessListener.Close() 382 | if err != nil { 383 | log.Println(syscall.Getpid(), "Listener.Close() error:", err) 384 | } else { 385 | log.Println(syscall.Getpid(), srv.EndlessListener.Addr(), "Listener closed.") 386 | } 387 | } 388 | 389 | /* 390 | hammerTime forces the server to shutdown in a given timeout - whether it 391 | finished outstanding requests or not. if Read/WriteTimeout are not set or the 392 | max header size is very big a connection could hang... 393 | 394 | srv.Serve() will not return until all connections are served. this will 395 | unblock the srv.wg.Wait() in Serve() thus causing ListenAndServe(TLS) to 396 | return. 397 | */ 398 | func (srv *endlessServer) hammerTime(d time.Duration) { 399 | defer func() { 400 | // we are calling srv.wg.Done() until it panics which means we called 401 | // Done() when the counter was already at 0 and we're done. 402 | // (and thus Serve() will return and the parent will exit) 403 | if r := recover(); r != nil { 404 | log.Println("WaitGroup at 0", r) 405 | } 406 | }() 407 | if srv.getState() != STATE_SHUTTING_DOWN { 408 | return 409 | } 410 | time.Sleep(d) 411 | log.Println("[STOP - Hammer Time] Forcefully shutting down parent") 412 | for { 413 | if srv.getState() == STATE_TERMINATE { 414 | break 415 | } 416 | srv.wg.Done() 417 | runtime.Gosched() 418 | } 419 | } 420 | 421 | func (srv *endlessServer) fork() (err error) { 422 | runningServerReg.Lock() 423 | defer runningServerReg.Unlock() 424 | 425 | // only one server instance should fork! 426 | if runningServersForked { 427 | return errors.New("Another process already forked. Ignoring this one.") 428 | } 429 | 430 | runningServersForked = true 431 | 432 | var files = make([]*os.File, len(runningServers)) 433 | var orderArgs = make([]string, len(runningServers)) 434 | // get the accessor socket fds for _all_ server instances 435 | for _, srvPtr := range runningServers { 436 | // introspect.PrintTypeDump(srvPtr.EndlessListener) 437 | switch srvPtr.EndlessListener.(type) { 438 | case *endlessListener: 439 | // normal listener 440 | files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.EndlessListener.(*endlessListener).File() 441 | default: 442 | // tls listener 443 | files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File() 444 | } 445 | orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr 446 | } 447 | 448 | env := append( 449 | os.Environ(), 450 | "ENDLESS_CONTINUE=1", 451 | ) 452 | if len(runningServers) > 1 { 453 | env = append(env, fmt.Sprintf(`ENDLESS_SOCKET_ORDER=%s`, strings.Join(orderArgs, ","))) 454 | } 455 | 456 | // log.Println(files) 457 | path := os.Args[0] 458 | var args []string 459 | if len(os.Args) > 1 { 460 | args = os.Args[1:] 461 | } 462 | 463 | cmd := exec.Command(path, args...) 464 | cmd.Stdout = os.Stdout 465 | cmd.Stderr = os.Stderr 466 | cmd.ExtraFiles = files 467 | cmd.Env = env 468 | 469 | // cmd.SysProcAttr = &syscall.SysProcAttr{ 470 | // Setsid: true, 471 | // Setctty: true, 472 | // Ctty: , 473 | // } 474 | 475 | err = cmd.Start() 476 | if err != nil { 477 | log.Fatalf("Restart: Failed to launch, error: %v", err) 478 | } 479 | 480 | return 481 | } 482 | 483 | type endlessListener struct { 484 | net.Listener 485 | stopped bool 486 | server *endlessServer 487 | } 488 | 489 | func (el *endlessListener) Accept() (c net.Conn, err error) { 490 | tc, err := el.Listener.(*net.TCPListener).AcceptTCP() 491 | if err != nil { 492 | return 493 | } 494 | 495 | tc.SetKeepAlive(true) // see http.tcpKeepAliveListener 496 | tc.SetKeepAlivePeriod(3 * time.Minute) // see http.tcpKeepAliveListener 497 | 498 | c = endlessConn{ 499 | Conn: tc, 500 | server: el.server, 501 | } 502 | 503 | el.server.wg.Add(1) 504 | return 505 | } 506 | 507 | func newEndlessListener(l net.Listener, srv *endlessServer) (el *endlessListener) { 508 | el = &endlessListener{ 509 | Listener: l, 510 | server: srv, 511 | } 512 | 513 | return 514 | } 515 | 516 | func (el *endlessListener) Close() error { 517 | if el.stopped { 518 | return syscall.EINVAL 519 | } 520 | 521 | el.stopped = true 522 | return el.Listener.Close() 523 | } 524 | 525 | func (el *endlessListener) File() *os.File { 526 | // returns a dup(2) - FD_CLOEXEC flag *not* set 527 | tl := el.Listener.(*net.TCPListener) 528 | fl, _ := tl.File() 529 | return fl 530 | } 531 | 532 | type endlessConn struct { 533 | net.Conn 534 | server *endlessServer 535 | } 536 | 537 | func (w endlessConn) Close() error { 538 | err := w.Conn.Close() 539 | if err == nil { 540 | w.server.wg.Done() 541 | } 542 | return err 543 | } 544 | 545 | /* 546 | RegisterSignalHook registers a function to be run PRE_SIGNAL or POST_SIGNAL for 547 | a given signal. PRE or POST in this case means before or after the signal 548 | related code endless itself runs 549 | */ 550 | func (srv *endlessServer) RegisterSignalHook(prePost int, sig os.Signal, f func()) (err error) { 551 | if prePost != PRE_SIGNAL && prePost != POST_SIGNAL { 552 | err = fmt.Errorf("Cannot use %v for prePost arg. Must be endless.PRE_SIGNAL or endless.POST_SIGNAL.", sig) 553 | return 554 | } 555 | for _, s := range hookableSignals { 556 | if s == sig { 557 | srv.SignalHooks[prePost][sig] = append(srv.SignalHooks[prePost][sig], f) 558 | return 559 | } 560 | } 561 | err = fmt.Errorf("Signal %v is not supported.", sig) 562 | return 563 | } 564 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Simple server 4 | 5 | Compile the example 6 | 7 | $ go build -o simple_server examples/simple.go 8 | 9 | Run it 10 | 11 | $ ./simple_server 12 | 2015/03/22 20:03:29 PID: 2710 localhost:4242 13 | 14 | Make a request 15 | 16 | $ curl http://localhost:4242/hello 17 | WORLD! 18 | 19 | Change the handler - eg. replace the `!` with a `?` 20 | 21 | $ go build -o simple_server examples/simple.go 22 | $ kill -1 2710 23 | 24 | The server log says something like: 25 | 26 | 2015/03/22 20:04:10 2710 Received SIGHUP. forking. 27 | 2015/03/22 20:04:10 2710 Received SIGTERM. 28 | 2015/03/22 20:04:10 2710 Waiting for connections to finish... 29 | 2015/03/22 20:04:10 PID: 2726 localhost:4242 30 | 2015/03/22 20:04:10 accept tcp 127.0.0.1:4242: use of closed network connection 31 | 2015/03/22 20:04:10 Server on 4242 stopped 32 | 33 | Make another request 34 | 35 | $ curl http://localhost:4242/hello 36 | WORLD? 37 | 38 | 39 | ## TLS 40 | 41 | Create local cert and key file: 42 | 43 | go run $GOROOT/src/crypto/tls/generate_cert.go --host=localhost 44 | 45 | Compile the example 46 | 47 | $ go build -o tls_server examples/tls.go 48 | 49 | Run it 50 | 51 | $ ./tls_server 52 | 2015/03/23 19:43:29 PID: 21710 localhost:4242 53 | 54 | Make a request (`-k` to disable certificate checks in curl) 55 | 56 | $ curl -k https://localhost:4242/hello 57 | WORLD! 58 | 59 | The rest is like the simple server example: modify the tls.go code, build, send SIGHUP, and make another request. 60 | 61 | 62 | ## Hooking into the signal handling 63 | 64 | If you want to time certain actions before or after the server does something based on a signal it received you can hook your own functions into the signal handling of the endless server. 65 | 66 | There is a `PRE_SIGNAL` and a `POST_SIGNAL` hook dor each signal. These are exposed as lists of parameterless functions: 67 | 68 | func preSigUsr1() { 69 | log.Println("pre SIGUSR1") 70 | } 71 | 72 | If you want to have this function executed before `SIGUSR1` you would add it to the hooks like this: 73 | 74 | srv := endless.NewServer("localhost:4244", mux) 75 | srv.SignalHooks[endless.PRE_SIGNAL][syscall.SIGUSR1] = append( 76 | srv.SignalHooks[endless.PRE_SIGNAL][syscall.SIGUSR1], 77 | preSigUsr1) 78 | 79 | then build, and run it 80 | 81 | $ go build -o hook_server examples/hook.go 82 | $ ./hook_server 83 | 2015/04/06 20:32:13 1489 localhost:4244 84 | 85 | now send `SIGUSR1` 86 | 87 | kill -SIGUSR1 1489 88 | 89 | and you should see something like this 90 | 91 | 2015/04/06 20:33:07 pre SIGUSR1 92 | 2015/04/06 20:33:07 1489 Received SIGUSR1. 93 | 2015/04/06 20:33:07 post SIGUSR1 94 | 95 | 96 | ## Running several servers (eg on several ports) 97 | 98 | This is probably less useful as you could always run separate servers - but in case you need to start more than one listener from one binary it will also work with endless - pretty much the same way it works in the simple and TLS examples. 99 | -------------------------------------------------------------------------------- /examples/hook.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "syscall" 8 | 9 | "github.com/fvbock/endless" 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func handler(w http.ResponseWriter, r *http.Request) { 14 | w.Write([]byte("WORLD!")) 15 | } 16 | 17 | func preSigUsr1() { 18 | log.Println("pre SIGUSR1") 19 | } 20 | 21 | func postSigUsr1() { 22 | log.Println("post SIGUSR1") 23 | } 24 | 25 | func main() { 26 | mux1 := mux.NewRouter() 27 | mux1.HandleFunc("/hello", handler). 28 | Methods("GET") 29 | 30 | srv := endless.NewServer("localhost:4244", mux1) 31 | srv.SignalHooks[endless.PRE_SIGNAL][syscall.SIGUSR1] = append( 32 | srv.SignalHooks[endless.PRE_SIGNAL][syscall.SIGUSR1], 33 | preSigUsr1) 34 | srv.SignalHooks[endless.POST_SIGNAL][syscall.SIGUSR1] = append( 35 | srv.SignalHooks[endless.POST_SIGNAL][syscall.SIGUSR1], 36 | postSigUsr1) 37 | 38 | err := srv.ListenAndServe() 39 | if err != nil { 40 | log.Println(err) 41 | } 42 | log.Println("Server on 4244 stopped") 43 | 44 | os.Exit(0) 45 | } 46 | -------------------------------------------------------------------------------- /examples/multi_port.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "sync" 8 | "time" 9 | 10 | "github.com/fvbock/endless" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | func handler(w http.ResponseWriter, r *http.Request) { 15 | w.Write([]byte("WORLD!")) 16 | } 17 | 18 | func handlerFoo(w http.ResponseWriter, r *http.Request) { 19 | time.Sleep(time.Second) 20 | w.Write([]byte("BAR")) 21 | } 22 | 23 | func handlerBar(w http.ResponseWriter, r *http.Request) { 24 | w.Write([]byte("FOO")) 25 | } 26 | 27 | func main() { 28 | mux1 := mux.NewRouter() 29 | mux1.HandleFunc("/hello", handler). 30 | Methods("GET") 31 | mux1.HandleFunc("/foo", handlerFoo). 32 | Methods("GET") 33 | 34 | mux2 := mux.NewRouter() 35 | mux2.HandleFunc("/bar", handlerBar). 36 | Methods("GET") 37 | 38 | log.Println("Starting servers...") 39 | 40 | w := sync.WaitGroup{} 41 | w.Add(2) 42 | go func() { 43 | time.Sleep(time.Second) 44 | err := endless.ListenAndServe("localhost:4242", mux1) 45 | if err != nil { 46 | log.Println(err) 47 | } 48 | log.Println("Server on 4242 stopped") 49 | w.Done() 50 | }() 51 | go func() { 52 | err := endless.ListenAndServe("localhost:4243", mux2) 53 | if err != nil { 54 | log.Println(err) 55 | } 56 | log.Println("Server on 4243 stopped") 57 | w.Done() 58 | }() 59 | w.Wait() 60 | log.Println("All servers stopped. Exiting.") 61 | 62 | os.Exit(0) 63 | } 64 | -------------------------------------------------------------------------------- /examples/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/fvbock/endless" 9 | "github.com/gorilla/mux" 10 | ) 11 | 12 | func handler(w http.ResponseWriter, r *http.Request) { 13 | w.Write([]byte("WORLD!")) 14 | } 15 | 16 | func main() { 17 | mux1 := mux.NewRouter() 18 | mux1.HandleFunc("/hello", handler). 19 | Methods("GET") 20 | 21 | err := endless.ListenAndServe("localhost:4242", mux1) 22 | if err != nil { 23 | log.Println(err) 24 | } 25 | log.Println("Server on 4242 stopped") 26 | 27 | os.Exit(0) 28 | } 29 | -------------------------------------------------------------------------------- /examples/testserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "net/http" 7 | "os" 8 | "time" 9 | 10 | "github.com/fvbock/endless" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | func handler(w http.ResponseWriter, r *http.Request) { 15 | time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond) 16 | w.Write([]byte("bar\n")) 17 | } 18 | 19 | func main() { 20 | // endless.DefaultHammerTime = 10*time.Second 21 | mux := mux.NewRouter() 22 | mux.HandleFunc("/foo", handler). 23 | Methods("GET") 24 | 25 | err := endless.ListenAndServe("localhost:4242", mux) 26 | if err != nil { 27 | log.Println(err) 28 | } 29 | log.Println("Server on 4242 stopped") 30 | 31 | os.Exit(0) 32 | } 33 | -------------------------------------------------------------------------------- /examples/tls.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/fvbock/endless" 9 | "github.com/gorilla/mux" 10 | ) 11 | 12 | func handler(w http.ResponseWriter, r *http.Request) { 13 | w.Write([]byte("WORLD!")) 14 | } 15 | 16 | func main() { 17 | mux1 := mux.NewRouter() 18 | mux1.Schemes("https") 19 | mux1.HandleFunc("/hello", handler). 20 | Methods("GET") 21 | 22 | err := endless.ListenAndServeTLS("localhost:4242", "cert.pem", "key.pem", mux1) 23 | if err != nil { 24 | log.Println(err) 25 | } 26 | log.Println("Server on 4242 stopped") 27 | 28 | os.Exit(0) 29 | } 30 | -------------------------------------------------------------------------------- /test/restart_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ps aux | grep "test_server" | grep -v grep | awk '{print $2}' | xargs -i kill -1 {} 3 | -------------------------------------------------------------------------------- /test/stop_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ps aux | grep "test_server" | grep -v grep | awk '{print $2}' | xargs -i kill {} 3 | -------------------------------------------------------------------------------- /test/test_restarting.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "os" 8 | "os/exec" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | var ( 14 | keepRestarting bool 15 | ) 16 | 17 | func compileAndStartTestServer(compileDone chan struct{}) { 18 | cmd := exec.Command("go", []string{"build", "-a", "-v", "-o", "test_server", "examples/testserver.go"}...) 19 | cmd.Stdout = os.Stdout 20 | cmd.Stderr = os.Stderr 21 | 22 | err := cmd.Start() 23 | if err != nil { 24 | log.Println("test server compile error:", err) 25 | } 26 | err = cmd.Wait() 27 | if err != nil { 28 | log.Println("test server compile error:", err) 29 | } 30 | 31 | cmd = exec.Command("./test_server", []string{}...) 32 | cmd.Stdout = os.Stdout 33 | cmd.Stderr = os.Stderr 34 | err = cmd.Start() 35 | if err != nil { 36 | log.Println("test server error:", err) 37 | } 38 | compileDone <- struct{}{} 39 | err = cmd.Wait() 40 | if err != nil { 41 | log.Println("test server error:", err) 42 | } 43 | return 44 | } 45 | 46 | func runAB() (err error) { 47 | time.Sleep(time.Second * 1) 48 | cmd := exec.Command("ab", []string{"-c 1000", "-n 100000", "http://localhost:4242/foo"}...) 49 | cmd.Stdout = os.Stdout 50 | cmd.Stderr = os.Stderr 51 | 52 | err = cmd.Start() 53 | if err != nil { 54 | log.Println("AB error:", err) 55 | } 56 | err = cmd.Wait() 57 | if err != nil { 58 | log.Println("AB error:", err) 59 | } 60 | return 61 | } 62 | 63 | func stopTestServer() (err error) { 64 | log.Println("* Wait 5 seconds and then send kill -15 to server") 65 | time.Sleep(time.Second * 5) 66 | log.Println("* kill -15") 67 | cmd := exec.Command("./test/stop_server.sh", []string{}...) 68 | cmd.Stdout = os.Stdout 69 | cmd.Stderr = os.Stderr 70 | 71 | err = cmd.Start() 72 | if err != nil { 73 | log.Println("kill error:", err) 74 | return 75 | } 76 | 77 | err = cmd.Wait() 78 | if err != nil { 79 | log.Println("kill error:", err) 80 | } 81 | return 82 | } 83 | 84 | func keepRestartingServer() { 85 | time.Sleep(time.Second * 1) 86 | for keepRestarting { 87 | time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) 88 | // time.Sleep(time.Second * 60) 89 | log.Println("sending kill -1") 90 | cmd := exec.Command("./test/restart_server.sh", []string{}...) 91 | cmd.Stdout = os.Stdout 92 | cmd.Stderr = os.Stderr 93 | 94 | err := cmd.Start() 95 | if err != nil { 96 | log.Println("restart error:", err) 97 | } 98 | err = cmd.Wait() 99 | if err != nil { 100 | log.Println("restart error:", err) 101 | } 102 | } 103 | } 104 | 105 | func main() { 106 | // check for ab - pretty hacky... 107 | out, _ := exec.Command("which", []string{"ab"}...).Output() 108 | log.Println("WHICH ab:", string(out)) 109 | if len(out) == 0 { 110 | log.Println("cant find ab (apache bench). not running test.") 111 | return 112 | } 113 | 114 | wg := sync.WaitGroup{} 115 | var compileDone = make(chan struct{}, 1) 116 | wg.Add(2) 117 | go func() { 118 | log.Println("compile and start test server") 119 | compileAndStartTestServer(compileDone) 120 | log.Println("test server stopped") 121 | wg.Done() 122 | }() 123 | 124 | time.Sleep(time.Second * 1) 125 | 126 | go func() { 127 | <-compileDone 128 | log.Println("Starting ab") 129 | keepRestarting = true 130 | go keepRestartingServer() 131 | err := runAB() 132 | if err != nil { 133 | panic(fmt.Sprintf("Failed to start ab: %v", err)) 134 | } 135 | log.Println("ab done. stop server.") 136 | keepRestarting = false 137 | stopTestServer() 138 | wg.Done() 139 | }() 140 | 141 | wg.Wait() 142 | log.Println("All done.") 143 | } 144 | --------------------------------------------------------------------------------