[GH-ISSUE #439] [FEATURE REQUEST] programmatically signal the server to shutdown #201

Closed
opened 2026-03-02 05:19:34 +03:00 by kerem · 4 comments
Owner

Originally created by @ghstahl on GitHub (Apr 22, 2022).
Original GitHub issue: https://github.com/hibiken/asynq/issues/439

Originally assigned to: @hibiken on GitHub.

Is your feature request related to a problem? Please describe.
I am dynamically spinning up and shutting down servers based upon a config change.
i.e. my app can have many so-called asynq server, they are just go routines. In my test I have 2 running in a single app. I just can't get them to gracefully shutdown. I want the go routine they live in to end because srv.Run() returned gracefully.

I thought calling srv.Shutdown() would do it, but that is not how the code works.

asynq run
wait for signal

You can see that signal a shutdown is for everything, which isn't what I want. I just want to signal one of my running asynq servers to shutdown.

Describe the solution you'd like
Move the following from waitForSignals to the Server struct{} as a member variable.

type Server struct {
	sigs chan os.Signal
}
func (srv *Server) waitForSignals() {
	srv.logger.Info("Send signal TERM or INT to terminate the process")
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, windows.SIGTERM, windows.SIGINT)
	<-sigs
}

would now look like this.

func (srv *Server) waitForSignals() {
	srv.logger.Info("Send signal TERM or INT to terminate the process")
	signal.Notify(srv.sigs, windows.SIGTERM, windows.SIGINT)
	<-srv.sigs
}

add a new method to signal a shutdown.

func (s *Runtime) SignalGracefulShutdown() {
	s.sig<-windows.SIGTERM
}

Other Examples

type Runtime struct {
	waitChannel     chan os.Signal
}
func NewRuntime() *Runtime {
	return &Runtime{
		waitChannel: make(chan os.Signal),
	}
}

// Stop ...
func (s *Runtime) Stop() {
	s.waitChannel <- os.Interrupt
}

// Wait for someone to call stop
func (s *Runtime) Wait() {
	signal.Notify(
		s.waitChannel,
		os.Interrupt,
		syscall.SIGINT,
		syscall.SIGQUIT,
		syscall.SIGTERM,
	)
	<-s.waitChannel
}
Originally created by @ghstahl on GitHub (Apr 22, 2022). Original GitHub issue: https://github.com/hibiken/asynq/issues/439 Originally assigned to: @hibiken on GitHub. **Is your feature request related to a problem? Please describe.** I am dynamically spinning up and shutting down servers based upon a config change. i.e. my app can have many so-called asynq server, they are just go routines. In my test I have 2 running in a single app. I just can't get them to gracefully shutdown. I want the go routine they live in to end because ```srv.Run()``` returned gracefully. I thought calling srv.Shutdown() would do it, but that is not how the code works. [asynq run](https://github.com/hibiken/asynq/blob/94719e325cc89f3c1fd56919929212977de97616/server.go#L594) [ wait for signal](https://github.com/hibiken/asynq/blob/94719e325cc89f3c1fd56919929212977de97616/signals_windows.go#L17) You can see that signal a shutdown is for everything, which isn't what I want. I just want to signal one of my running asynq servers to shutdown. **Describe the solution you'd like** Move the following from ```waitForSignals``` to the Server struct{} as a member variable. ```go type Server struct { sigs chan os.Signal } func (srv *Server) waitForSignals() { srv.logger.Info("Send signal TERM or INT to terminate the process") sigs := make(chan os.Signal, 1) signal.Notify(sigs, windows.SIGTERM, windows.SIGINT) <-sigs } ``` would now look like this. ```go func (srv *Server) waitForSignals() { srv.logger.Info("Send signal TERM or INT to terminate the process") signal.Notify(srv.sigs, windows.SIGTERM, windows.SIGINT) <-srv.sigs } ``` add a new method to signal a shutdown. ```go func (s *Runtime) SignalGracefulShutdown() { s.sig<-windows.SIGTERM } ``` ## Other Examples ```go type Runtime struct { waitChannel chan os.Signal } func NewRuntime() *Runtime { return &Runtime{ waitChannel: make(chan os.Signal), } } // Stop ... func (s *Runtime) Stop() { s.waitChannel <- os.Interrupt } // Wait for someone to call stop func (s *Runtime) Wait() { signal.Notify( s.waitChannel, os.Interrupt, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, ) <-s.waitChannel } ```
kerem 2026-03-02 05:19:34 +03:00
Author
Owner

@hibiken commented on GitHub (Apr 22, 2022):

@ghstahl thanks for creating an issue.

If you'd like to programmatically start and shutdown the server, then please use Start instead of Run. (Run takes care of waiting for termination signal and calls Shutdown on your behalf).

You can see an example here: https://pkg.go.dev/github.com/hibiken/asynq#example-Server.Shutdown

<!-- gh-comment-id:1106959468 --> @hibiken commented on GitHub (Apr 22, 2022): @ghstahl thanks for creating an issue. If you'd like to programmatically start and shutdown the server, then please use `Start` instead of `Run`. (Run takes care of waiting for termination signal and calls `Shutdown` on your behalf). You can see an example here: https://pkg.go.dev/github.com/hibiken/asynq#example-Server.Shutdown
Author
Owner

@ghstahl commented on GitHub (Apr 23, 2022):

It's not a question of stopping and starting a server instance. I need to throw the whole thing away because it was configured to pull from a set of queues that are no longer valid.
As new configs come in, I can spin up new servers (hosted in a go routines) that have a set of queues never seen before.

Once a server instance is started, I can't shut it down, meaning I can't get rid of the go routine that hosted it.

As it stands, I would be left with dangling orphaned go routines that are hosting a server instance that is stopped (srv.Stop()) and shutdown (srv.Shutdown()), which I would like to not have.

The ask here is that I would like to surgically have a server instance stop running completely vs all of them being signaled to go away.

BTW: Would you say that the pull request I have in flight stands on its own as a feature that would be nice to have in the framework. i.e., signal a instance of a server to go away vs all of them.

<!-- gh-comment-id:1107054835 --> @ghstahl commented on GitHub (Apr 23, 2022): It's not a question of stopping and starting a server instance. I need to throw the whole thing away because it was configured to pull from a set of queues that are no longer valid. As new configs come in, I can spin up new servers (hosted in a go routines) that have a set of queues never seen before. Once a server instance is started, I can't shut it down, meaning I can't get rid of the go routine that hosted it. As it stands, I would be left with dangling orphaned go routines that are hosting a server instance that is stopped (srv.Stop()) and shutdown (srv.Shutdown()), which I would like to not have. The ask here is that I would like to surgically have a server instance stop running completely vs all of them being signaled to go away. BTW: Would you say that the pull request I have in flight stands on its own as a feature that would be nice to have in the framework. i.e., signal a instance of a server to go away vs all of them.
Author
Owner

@hibiken commented on GitHub (Apr 28, 2022):

@ghstahl Sorry for the late reply!

Are you calling Server.Run to start a server in your goroutine? If so, please use Server.Start instead.
As long as you have reference to that server in your goroutine, you can shutdown the specific server by calling Shutdown on the server.

Example:

package main

import (
        "log"
        "sync"
        "time"

        "github.com/hibiken/asynq"
)

// Example of running multiple servers (each in its own goroutine) and shutting them down individually.
// First server stops after 4s, Second stops after 10s
func main() {
        var wg sync.WaitGroup

        wg.Add(2)
        go func() {
                srv := asynq.NewServer(
                        asynq.RedisClientOpt{Addr: ":6379"},
                        asynq.Config{
                            Concurrency: 10,
                        },
                )
                mux := asynq.NewServeMux()
                srv.Start(mux)
                time.Sleep(4 * time.Second)
                log.Println("Shutting down server1...")
                srv.Shutdown()
                log.Printf("DEBUG: goroutine 1 exiting...")
                wg.Done()
        }()
        go func() {
                srv := asynq.NewServer(
                        asynq.RedisClientOpt{Addr: ":6379"},
                        asynq.Config{
                                Concurrency: 10,
                        },
                )
                mux := asynq.NewServeMux()
                srv.Start(mux)
                time.Sleep(10 * time.Second)
                log.Println("Shutting down server2...")
                srv.Shutdown()
                log.Printf("DEBUG: goroutine 2 exiting...")
                wg.Done()
        }()
        wg.Wait()
        log.Printf("DEBUG: main exiting...")
}
<!-- gh-comment-id:1112193403 --> @hibiken commented on GitHub (Apr 28, 2022): @ghstahl Sorry for the late reply! Are you calling `Server.Run` to start a server in your goroutine? If so, please use `Server.Start` instead. As long as you have reference to that server in your goroutine, you can shutdown the specific server by calling `Shutdown` on the server. Example: ```go package main import ( "log" "sync" "time" "github.com/hibiken/asynq" ) // Example of running multiple servers (each in its own goroutine) and shutting them down individually. // First server stops after 4s, Second stops after 10s func main() { var wg sync.WaitGroup wg.Add(2) go func() { srv := asynq.NewServer( asynq.RedisClientOpt{Addr: ":6379"}, asynq.Config{ Concurrency: 10, }, ) mux := asynq.NewServeMux() srv.Start(mux) time.Sleep(4 * time.Second) log.Println("Shutting down server1...") srv.Shutdown() log.Printf("DEBUG: goroutine 1 exiting...") wg.Done() }() go func() { srv := asynq.NewServer( asynq.RedisClientOpt{Addr: ":6379"}, asynq.Config{ Concurrency: 10, }, ) mux := asynq.NewServeMux() srv.Start(mux) time.Sleep(10 * time.Second) log.Println("Shutting down server2...") srv.Shutdown() log.Printf("DEBUG: goroutine 2 exiting...") wg.Done() }() wg.Wait() log.Printf("DEBUG: main exiting...") }
Author
Owner

@ghstahl commented on GitHub (Apr 28, 2022):

Yes, one could say I am calling Server.Run 🤦‍♂️
Well, this is a relief, please excuse me while I go delete that fork of mine!

<!-- gh-comment-id:1112661136 --> @ghstahl commented on GitHub (Apr 28, 2022): Yes, one could say I am calling Server.Run :man_facepalming: Well, this is a relief, please excuse me while I go delete that fork of mine!
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/asynq#201
No description provided.