{"id":15372926,"url":"https://github.com/rs/seamless","last_synced_at":"2025-05-07T09:25:02.684Z","repository":{"id":57498702,"uuid":"93494989","full_name":"rs/seamless","owner":"rs","description":"Seamless restart / zero-downtime deploy for Go servers","archived":false,"fork":false,"pushed_at":"2024-09-10T08:16:54.000Z","size":16,"stargazers_count":118,"open_issues_count":1,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-31T08:38:45.691Z","etag":null,"topics":["daemon","golang"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-06T08:32:05.000Z","updated_at":"2025-03-23T19:57:21.000Z","dependencies_parsed_at":"2024-06-19T00:28:19.239Z","dependency_job_id":"ed657e65-f61f-47d0-9858-c690260e5ca7","html_url":"https://github.com/rs/seamless","commit_stats":{"total_commits":10,"total_committers":3,"mean_commits":"3.3333333333333335","dds":0.4,"last_synced_commit":"5150154666598f5080d8524304b053d645a3205e"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rs%2Fseamless","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rs%2Fseamless/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rs%2Fseamless/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rs%2Fseamless/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rs","download_url":"https://codeload.github.com/rs/seamless/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252849126,"owners_count":21813778,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["daemon","golang"],"created_at":"2024-10-01T13:53:42.396Z","updated_at":"2025-05-07T09:25:02.657Z","avatar_url":"https://github.com/rs.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go Seamless Restart\n[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/seamless) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/seamless/master/LICENSE)\n\nPackage seamless implements a seamless restart strategy for daemons monitored by a service supervisor expecting non-forking daemons like daemontools, runit, systemd etc.\n\nThe seamless strategy is to fully rely on the service supervisor to restart the daemon, while providing to the daemon the full control of the restart process. To achieve this, seamless duplicates the daemon at startup in order to establish a supervisor -\u003e launcher -\u003e daemon relationship. The launcher is the first generation of the daemon hijacked by seamless to act as a circuit breaker between the supervisor and the supervised process.\n\nThis way, when the supervisor sends a `TERM` signal to stop the daemon, the launcher intercepts the signal and send an USR2 signal to its child (the actual daemon). In the daemon, seamless intercepts the USR2 signals to initiate the first stage of the seamless restart.\n\nDuring the first stage, the daemon prepare itself to welcome a new version of itself by creating a PID file (see below) and by for instance closing file descriptors. At this point, the daemon is still supposed to accept requests. Once read, seamless make it send a `CHLD` signal back to the launcher (its parent). Upon reception, the launcher, immediately die, cutting to link between the supervisor and the daemon, making the supervisor attempting a restart of the daemon while current daemon is still running, detached and unsupervised.\n\nOnce the supervisor restarted the daemon, the daemon can start serving traffic in place of the old (still running) daemon by rebinding sockets using `SO_REUSEPORT` for instance (see different strategies in examples/). This is the second stage of the seamless restart. When ready, the new daemon calls seamless.Started which will look for a PID file, and if found, will send a `TERM` signal to the old daemon using the PID found in this file.\n\nWhen the old daemon receives this `TERM` signal, the third and last stage of the seamless restart is engaged. The OnShutdown function is called so the daemon can gracefully shutdown using Go 1.8 http graceful Shutdown method for instance. This stage can last as long as you decide. When done, the old process can exit in order to conclude the seamless restart.\n\nSeamless does not try to implement the actual graceful shutdown or to manage sockets migration. This task is left to the caller. See the examples directory for different implementations.\n\n## Usage\n\nHere is an example of seamless restart of an HTTP server using Go 1.8 provided graceful shutdown feature + the `SO_REUSEPORT` sockopt.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\treuseport \"github.com/kavu/go_reuseport\"\n\t\"github.com/rs/seamless\"\n)\n\nvar (\n\tlisten          = flag.String(\"listen\", \"localhost:8080\", \"Listen address\")\n\tpidFile         = flag.String(\"pid-file\", \"/tmp/reuseport.pid\", \"Seemless restart PID file\")\n\tgracefulTimeout = flag.Duration(\"graceful-timeout\", 60*time.Second, \"Maximum duration to wait for in-flight requests\")\n)\n\nfunc init() {\n\tflag.Parse()\n\tseamless.Init(*pidFile)\n}\n\nfunc main() {\n\t// Use github.com/kavu/go_reuseport waiting for\n\t// https://github.com/golang/go/issues/9661 to be fixed.\n\t//\n\t// The idea of SO_REUSEPORT flag is that two processes can listen on the\n\t// same host:port. Using the capability, the new daemon can listen while\n\t// the old daemon is still bound, allowing seemless transition from one\n\t// process to the other.\n\tl, err := reuseport.Listen(\"tcp\", *listen)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\ts := \u0026http.Server{\n\t\tAddr: *listen,\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif d := r.URL.Query().Get(\"delay\"); d != \"\" {\n\t\t\t\tif delay, err := time.ParseDuration(d); err == nil {\n\t\t\t\t\ttime.Sleep(delay)\n\t\t\t\t}\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\tfmt.Fprintf(w, \"Server pid: %d\\n\", os.Getpid())\n\t\t}),\n\t}\n\n\t// Implement the graceful shutdown that will be triggered once the new process\n\t// successfully rebound the socket.\n\tseamless.OnShutdown(func() {\n\t\tctx, cancel := context.WithTimeout(context.Background(), *gracefulTimeout)\n\t\tdefer cancel()\n\t\tif err := s.Shutdown(ctx); err != nil {\n\t\t\tlog.Print(\"Graceful shutdown timeout, force closing\")\n\t\t\ts.Close()\n\t\t}\n\t})\n\n\tgo func() {\n\t\t// Give the server a second to start\n\t\ttime.Sleep(time.Second)\n\t\tif err == nil {\n\t\t\t// Signal seamless that the daemon is started and the socket is\n\t\t\t// bound successfully. If a pid file is found, seamless will send\n\t\t\t// a signal to the old process to start its graceful shutdown\n\t\t\t// sequence.\n\t\t\tseamless.Started()\n\t\t}\n\t}()\n\terr = s.Serve(l)\n\tif err != nil \u0026\u0026 err != http.ErrServerClosed {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Once graceful shutdown is initiated, the Serve method is return with a\n\t// http.ErrServerClosed error. We must not exit until the graceful shutdown\n\t// is completed. The seamless.Wait method blocks until the OnShutdown callback\n\t// has returned.\n\tseamless.Wait()\n}\n```\n\nLets test this using daemontools. We first create the service directory:\n\n    mkdir -p service\n    cat \u003c\u003cEOF \u003e service/run\n    #!/bin/sh\n    exec ./reuseport\n    EOF\n    chmod 755 service/run\n    go build -o service/reuseport examples/reuseport\n\nThen in a separate terminal, run supervise on this service:\n\n    supervise ./service/\n\nThen in two terminals, run two loops, one with fast quick request and another with artificially slow requests:\n\n    # term 1\n    while true; do curl http://localhost:8080 || break; done\n\n    # term 2\n    while true; do curl 'http://localhost:8080?delay=10s' || break; done\n\nThen in yet another terminal, try to restart the service:\n\n    svc -t ./service/\n\nYou should see no refused connection on the first terminal and the ongoing slow request should not be interrupted on the other one.\n\n# License\n\nAll source code is licensed under the [MIT License](https://raw.githubusercontent.com/rs/seamless/master/LICENSE).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frs%2Fseamless","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frs%2Fseamless","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frs%2Fseamless/lists"}