Skip to content

Commit 2eefc89

Browse files
committed
refactor: address PR review feedback for healthcheck and proxy
Critical fixes: - Add detailed comment explaining hasConnectedOnce guard prevents goroutine leaks on reconnects - Verify port detection logic correctly handles URLs with explicit ports Documentation improvements: - Document 3-second timeout assumptions in listen.go for local development context - Document timeout parameter guidance in healthcheck.CheckServerHealth function - Fix re-export comment from 'backward compatibility' to 'convenience' Test coverage: - Add TestCheckServerHealth_PortInURL to verify explicit ports are not overwritten - Confirms fix for edge case where localhost:8080 would not become localhost:8080:80 All tests passing: go test ./pkg/listen/...
1 parent c8c33df commit 2eefc89

File tree

5 files changed

+39
-4
lines changed

5 files changed

+39
-4
lines changed

pkg/listen/healthcheck.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/hookdeck/hookdeck-cli/pkg/listen/healthcheck"
88
)
99

10-
// Re-export types and constants from healthcheck subpackage for backward compatibility
10+
// Re-export types and constants from healthcheck subpackage for convenience
1111
type ServerHealthStatus = healthcheck.ServerHealthStatus
1212
type HealthCheckResult = healthcheck.HealthCheckResult
1313

pkg/listen/healthcheck/healthcheck.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ type HealthCheckResult struct {
2727
Duration time.Duration
2828
}
2929

30-
// CheckServerHealth performs a TCP connection check to the target URL
30+
// CheckServerHealth performs a TCP connection check to verify a server is listening.
31+
// The timeout parameter should be appropriate for the deployment context:
32+
// - Local development: 3s is typically sufficient
33+
// - Production/edge: May require longer timeouts due to network conditions
3134
func CheckServerHealth(targetURL *url.URL, timeout time.Duration) HealthCheckResult {
3235
start := time.Now()
3336

pkg/listen/healthcheck/healthcheck_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package healthcheck
22

33
import (
4+
"fmt"
45
"net"
56
"net/http"
67
"net/http/httptest"
@@ -170,3 +171,29 @@ func TestFormatHealthMessage_NilError(t *testing.T) {
170171
t.Errorf("Expected message to contain 'unknown error' when error is nil")
171172
}
172173
}
174+
175+
func TestCheckServerHealth_PortInURL(t *testing.T) {
176+
// Create a server on a non-standard port
177+
listener, err := net.Listen("tcp", "localhost:0")
178+
if err != nil {
179+
t.Fatalf("Failed to create listener: %v", err)
180+
}
181+
defer listener.Close()
182+
183+
// Get the actual port assigned by the OS
184+
addr := listener.Addr().(*net.TCPAddr)
185+
targetURL, _ := url.Parse(fmt.Sprintf("http://localhost:%d/path", addr.Port))
186+
187+
// Perform health check
188+
result := CheckServerHealth(targetURL, 3*time.Second)
189+
190+
// Verify that the health check succeeded
191+
// This confirms that when a port is already in the URL, we don't append
192+
// a default port (which would cause localhost:8080 to become localhost:8080:80)
193+
if !result.Healthy {
194+
t.Errorf("Expected healthy=true for server with port in URL, got false: %v", result.Error)
195+
}
196+
if result.Error != nil {
197+
t.Errorf("Expected no error for server with port in URL, got: %v", result.Error)
198+
}
199+
}

pkg/listen/listen.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ Specify a single destination to update the path. For example, pass a connection
124124
}
125125

126126
// Perform initial health check on target server
127+
// Using 3-second timeout optimized for local development scenarios.
128+
// This assumes low latency to localhost. For production/edge deployments,
129+
// this timeout may need to be configurable in future iterations.
127130
healthCheckTimeout := 3 * time.Second
128131
healthResult := CheckServerHealth(URL, healthCheckTimeout)
129132

pkg/listen/proxy/proxy.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,10 @@ func (p *Proxy) Run(parentCtx context.Context) error {
158158
<-p.webSocketClient.Connected()
159159
p.renderer.OnConnected()
160160

161-
// Only start health monitoring on first successful connection
162-
// to prevent goroutine leaks on reconnects
161+
// Only start health monitoring on first successful connection to prevent
162+
// goroutine leaks on reconnects. The hasConnectedOnce guard ensures that
163+
// even if the websocket reconnects multiple times (which happens in the
164+
// Run() loop), we only spawn the health monitor goroutine once.
163165
if !hasConnectedOnce {
164166
hasConnectedOnce = true
165167

0 commit comments

Comments
 (0)