gorilla/websocket

[ISSUE] Handling SIGINT and SIGTERM for Graceful Shutdown in Go Websocket App

matterai opened this issue · 2 comments

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Hey!

I've got a main.go file where I'm playing around with this code snippet:

ctx, cancelCtx := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancelCtx()

service := NewService(ctx)
service.Connect()

Inside the Run() method, I'm setting up a connection with DialContext(s.context, url, nil) and diving into an endless loop that listens for signals. My goal here is to catch SIGINT and SIGTERM so I can neatly wrap up the connection and close the app on a good note. Here's what the service code looks like:

func NewService(context context.Context) *GatewayService {
	return &GatewayService{
		context: context,
	}
}

func (s *Service) Connect() error {
	url := "wss://dial.ws"
	conn, _, err := websocket.DefaultDialer.DialContext(s.context, url, nil)
	if err != nil {
		log.Printf("Error dialing: %v", err)
		return err
	}

	for {
		select {
		case <-s.context.Done():
			log.Println("Context done")
			if err := s.conn.Close(); err != nil {
				log.Printf("Error closing connection: %v", err)
				return err
			}

			return s.context.Err()

		default:
			_, message, err := conn.ReadMessage()
			if err != nil {
				log.Printf("Error reading message: %v", err)
				return err
			}

			log.Println(string(message))
		}
	}
}

When I give it a go and trigger a SIGTERM, the app just keeps on running. It looks like conn.ReadMessage() is holding up the main go-routine while it waits for new bytes from the websocket channel. I might be missing something here, but this seems a bit off to me. Is this how it's supposed to work? What's the best way to deal with termination signals with this library?

Thanks a bunch for your insights!

Expected Behavior

No response

Steps To Reproduce

No response

Anything else?

No response

Use two goroutines, one to read the connection and the other to handle signal. Here's an example.

Yep, I fixed it yesterday already. I made a goroutine for reading messages. The main goroutine is awaiting signals in for-cycle in the end of my Connect func:

	done := make(chan struct{})
	go func() {
		defer close(done)
		for {
			_, message, err := conn.ReadMessage()
			if err != nil {
				s.logger.Printf("Error reading message: %v", err)
				return
			}

			_, err = s.handler.HandleMessage(message)
			if err != nil {
				s.logger.Printf("Error handling message: %v", err)
				return
			}
		}
	}()

	for {
		select {
		case <-s.context.Done():
			s.logger.Println("Conext done, closing connection")
			return s.context.Err()
		case <-done:
			s.logger.Println("Message handler done, closing connection")
			return s.context.Err()
		}
	}

Thank you. Sorry for bothering.