mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-21 06:37:21 +00:00
fix(server): gracefully shut down HTTP proxy to drain in-flight requests
On signal, main waited on wg for StartHTTPProxy's server.Listen, which never returns because nothing called server.Shutdown(); it timed out after 10s and process-exited, abruptly killing in-flight GraphQL requests. Expose the proxy Fiber app and register an http-proxy Shutdown component so server.Listen returns after Shutdown, letting connections drain. Guard the shared app handle with a mutex (written by the StartHTTPProxy goroutine, read by the shutdown component). Verified with build, vet, full test and full race suite.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -599,3 +600,63 @@ func TestStartHTTPProxy_StartsAndShutdown(t *testing.T) {
|
||||
t.Errorf("want 200, got %d", httpResp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Test_shutdownHTTPProxy verifies the graceful-shutdown helper: it is a no-op
|
||||
// when no server is running, and it stops a listening proxy server so that
|
||||
// server.Listen returns (enabling a clean in-flight-request drain instead of
|
||||
// process-exit cutoff).
|
||||
func (suite *Tests) Test_shutdownHTTPProxy() {
|
||||
// No server running: must be a safe no-op.
|
||||
httpProxyMu.Lock()
|
||||
original := httpProxyApp
|
||||
httpProxyApp = nil
|
||||
httpProxyMu.Unlock()
|
||||
defer func() {
|
||||
httpProxyMu.Lock()
|
||||
httpProxyApp = original
|
||||
httpProxyMu.Unlock()
|
||||
}()
|
||||
suite.NoError(shutdownHTTPProxy(context.Background()))
|
||||
|
||||
// With a listening server assigned, shutdown must stop it.
|
||||
app := fiber.New()
|
||||
app.Get("/ping", func(c fiber.Ctx) error { return c.SendStatus(200) })
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
listenDone := make(chan struct{})
|
||||
go func() {
|
||||
defer close(listenDone)
|
||||
_ = app.Listener(ln)
|
||||
}()
|
||||
|
||||
// Wait until the listener is actually serving (poll the port) before
|
||||
// shutting down; otherwise Shutdown can race server startup and no-op.
|
||||
addr := ln.Addr().String()
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
suite.Fail("proxy server never started listening on " + addr)
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
httpProxyMu.Lock()
|
||||
httpProxyApp = app
|
||||
httpProxyMu.Unlock()
|
||||
suite.NoError(shutdownHTTPProxy(context.Background()))
|
||||
|
||||
select {
|
||||
case <-listenDone:
|
||||
// Listen returned because we called Shutdown.
|
||||
case <-time.After(3 * time.Second):
|
||||
suite.Fail("proxy server did not shut down; in-flight requests would be cut off")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user