websocket 优雅地关闭
websocket gracefully shutdown
我有一个 websocket 服务器。我为他写了一个测试,测试他正常关机的能力。创建了 5 个连接,每个连接发送 5 个请求。一段时间后,关机开始。必须满足所有 25 个请求。如果我关闭 exit
频道,那么测试将无法正常工作。
time.AfterFunc(50*time.Millisecond, func() {
close(exit)
close(done)
})
如果我只调用 s.shutdown
函数,那么一切正常。
time.AfterFunc(50*time.Millisecond, func() {
require.Nil(t, s.Shutdown())
close(done)
})
我的测试
func TestServer_GracefulShutdown(t *testing.T) {
done := make(chan struct{})
exit := make(chan struct{})
ctx := context.Background()
finishedRequestCount := atomic.NewInt32(0)
ln, err := net.Listen("tcp", "localhost:")
require.Nil(t, err)
handler := HandlerFunc(func(conn *websocket.Conn) {
for {
_, _, err := conn.ReadMessage()
if err != nil {
return
}
time.Sleep(100 * time.Millisecond)
finishedRequestCount.Inc()
}
})
s, err := makeServer(ctx, handler) // server create
require.Nil(t, err)
time.AfterFunc(50*time.Millisecond, func() {
close(exit)
close(done)
})
go func() {
fmt.Printf("Running...")
require.Nil(t, s.Run(ctx, exit, ln))
}()
for i := 0; i < connCount; i++ {
go func() {
err := clientRun(ln)
require.Nil(t, err)
}()
}
<-done
assert.Equal(t, int32(totalCount), finishedRequestCount.Load())
}
我的运行函数
func (s *Server) Run(ctx context.Context, exit <-chan struct{}, ln net.Listener) error {
errs := make(chan error, 1)
go func() {
err := s.httpServer.Run(ctx, exit, ln)
if err != nil {
errs <- err
}
}()
select {
case <-ctx.Done():
return s.Close()
case <-exit:
return s.Shutdown()
case err := <-errs:
return err
}
}
我的关机
func (s *Server) Shutdown() error {
err := s.httpServer.Shutdown() // we close the possibility to connect to any conn
s.wg.Wait()
return err
}
如果执行以下代码会发生什么?
close(exit)
close(done)
两个频道几乎同时关闭。第一个触发等待正常关闭的 Shutdown
函数。但是第二个触发了
的评估
assert.Equal(t, int32(totalCount), finishedRequestCount.Load())
在正常关机仍在 运行 或尚未开始时触发。
如果直接执行 Shutdown 函数,它将阻塞直到完成,然后 close(done)
才会开始断言。这就是为什么它有效:
require.Nil(t, s.Shutdown())
close(done)
您可以将 close(done)
移动到以下位置以使测试工作,同时使用 exit
通道关闭:
go func() {
fmt.Printf("Running...")
require.Nil(t, s.Run(ctx, exit, ln))
close(done)
}()
这样done
会在Shutdown
函数执行后关闭
正如评论中所讨论的,我强烈建议使用上下文而不是通道来关闭。他们隐藏了封闭渠道的复杂性。
我有一个 websocket 服务器。我为他写了一个测试,测试他正常关机的能力。创建了 5 个连接,每个连接发送 5 个请求。一段时间后,关机开始。必须满足所有 25 个请求。如果我关闭 exit
频道,那么测试将无法正常工作。
time.AfterFunc(50*time.Millisecond, func() {
close(exit)
close(done)
})
如果我只调用 s.shutdown
函数,那么一切正常。
time.AfterFunc(50*time.Millisecond, func() {
require.Nil(t, s.Shutdown())
close(done)
})
我的测试
func TestServer_GracefulShutdown(t *testing.T) {
done := make(chan struct{})
exit := make(chan struct{})
ctx := context.Background()
finishedRequestCount := atomic.NewInt32(0)
ln, err := net.Listen("tcp", "localhost:")
require.Nil(t, err)
handler := HandlerFunc(func(conn *websocket.Conn) {
for {
_, _, err := conn.ReadMessage()
if err != nil {
return
}
time.Sleep(100 * time.Millisecond)
finishedRequestCount.Inc()
}
})
s, err := makeServer(ctx, handler) // server create
require.Nil(t, err)
time.AfterFunc(50*time.Millisecond, func() {
close(exit)
close(done)
})
go func() {
fmt.Printf("Running...")
require.Nil(t, s.Run(ctx, exit, ln))
}()
for i := 0; i < connCount; i++ {
go func() {
err := clientRun(ln)
require.Nil(t, err)
}()
}
<-done
assert.Equal(t, int32(totalCount), finishedRequestCount.Load())
}
我的运行函数
func (s *Server) Run(ctx context.Context, exit <-chan struct{}, ln net.Listener) error {
errs := make(chan error, 1)
go func() {
err := s.httpServer.Run(ctx, exit, ln)
if err != nil {
errs <- err
}
}()
select {
case <-ctx.Done():
return s.Close()
case <-exit:
return s.Shutdown()
case err := <-errs:
return err
}
}
我的关机
func (s *Server) Shutdown() error {
err := s.httpServer.Shutdown() // we close the possibility to connect to any conn
s.wg.Wait()
return err
}
如果执行以下代码会发生什么?
close(exit)
close(done)
两个频道几乎同时关闭。第一个触发等待正常关闭的 Shutdown
函数。但是第二个触发了
assert.Equal(t, int32(totalCount), finishedRequestCount.Load())
在正常关机仍在 运行 或尚未开始时触发。
如果直接执行 Shutdown 函数,它将阻塞直到完成,然后 close(done)
才会开始断言。这就是为什么它有效:
require.Nil(t, s.Shutdown())
close(done)
您可以将 close(done)
移动到以下位置以使测试工作,同时使用 exit
通道关闭:
go func() {
fmt.Printf("Running...")
require.Nil(t, s.Run(ctx, exit, ln))
close(done)
}()
这样done
会在Shutdown
函数执行后关闭
正如评论中所讨论的,我强烈建议使用上下文而不是通道来关闭。他们隐藏了封闭渠道的复杂性。