Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion images/chromium-headful/wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ cleanup () {
echo "[wrapper] Cleaning up..."
# Re-enable scale-to-zero if the script terminates early
enable_scale_to_zero
supervisorctl -c /etc/supervisor/supervisord.conf stop kernel-images-api || true
supervisorctl -c /etc/supervisor/supervisord.conf stop chromedriver || true
supervisorctl -c /etc/supervisor/supervisord.conf stop chromium || true
supervisorctl -c /etc/supervisor/supervisord.conf stop kernel-images-api || true
supervisorctl -c /etc/supervisor/supervisord.conf stop dbus || true
# Stop log tailers
if [[ -n "${tail_pids[*]:-}" ]]; then
Expand Down
2 changes: 1 addition & 1 deletion images/chromium-headless/image/wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,11 @@ cleanup () {
echo "[wrapper] Cleaning up..."
# Re-enable scale-to-zero if the script terminates early
enable_scale_to_zero
supervisorctl -c /etc/supervisor/supervisord.conf stop kernel-images-api || true
supervisorctl -c /etc/supervisor/supervisord.conf stop chromedriver || true
supervisorctl -c /etc/supervisor/supervisord.conf stop chromium || true
supervisorctl -c /etc/supervisor/supervisord.conf stop xvfb || true
supervisorctl -c /etc/supervisor/supervisord.conf stop dbus || true
supervisorctl -c /etc/supervisor/supervisord.conf stop kernel-images-api || true
# Stop log tailers
if [[ -n "${tail_pids[*]:-}" ]]; then
for tp in "${tail_pids[@]}"; do
Expand Down
81 changes: 81 additions & 0 deletions server/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"encoding/json"
"fmt"
"log/slog"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/signal"
"strings"
"sync"
"syscall"
"time"

Expand Down Expand Up @@ -149,9 +151,14 @@ func main() {
fs.ServeHTTP(w, r)
})

r.Get("/health", healthHandler(upstreamMgr))

srv := &http.Server{
Addr: fmt.Sprintf(":%d", config.Port),
Handler: r,
BaseContext: func(_ net.Listener) context.Context {
return ctx
},
}

// wait up to 10 seconds for initial upstream; exit nonzero if not found
Expand Down Expand Up @@ -191,6 +198,9 @@ func main() {
srvDevtools := &http.Server{
Addr: fmt.Sprintf("0.0.0.0:%d", config.DevToolsProxyPort),
Handler: rDevtools,
BaseContext: func(_ net.Listener) context.Context {
return ctx
},
}

// ChromeDriver proxy: intercepts POST /session to inject the DevTools proxy
Expand All @@ -216,6 +226,9 @@ func main() {
srvChromeDriver := &http.Server{
Addr: fmt.Sprintf("0.0.0.0:%d", config.ChromeDriverProxyPort),
Handler: rChromeDriver,
BaseContext: func(_ net.Listener) context.Context {
return ctx
},
}

go func() {
Expand Down Expand Up @@ -269,6 +282,74 @@ func main() {
}
}

func healthHandler(mgr *devtoolsproxy.UpstreamManager) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cdpStatus := "ok"
if mgr.Current() == "" {
cdpStatus = "not_ready"
}

chromeStatus := "ok"
chromeProbeCtx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(chromeProbeCtx, http.MethodGet, "http://127.0.0.1:9223/json/version", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
chromeStatus = "error"
}
if resp != nil {
resp.Body.Close()
}

status := http.StatusOK
if cdpStatus != "ok" || chromeStatus != "ok" {
status = http.StatusServiceUnavailable
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]string{
"cdp_status": cdpStatus,
"chrome_status": chromeStatus,
})
}
}

// wsTracker tracks active WebSocket connections so they can be
// closed with a proper close frame during shutdown.
type wsTracker struct {
mu sync.Mutex
conns map[*wsTrackerEntry]struct{}
}

type wsTrackerEntry struct {
closeFunc func()
}

func newWSTracker() *wsTracker {
return &wsTracker{conns: make(map[*wsTrackerEntry]struct{})}
}

func (t *wsTracker) Add(closeFunc func()) func() {
entry := &wsTrackerEntry{closeFunc: closeFunc}
t.mu.Lock()
t.conns[entry] = struct{}{}
t.mu.Unlock()
return func() {
t.mu.Lock()
delete(t.conns, entry)
t.mu.Unlock()
}
}

func (t *wsTracker) CloseAll() {
t.mu.Lock()
defer t.mu.Unlock()
for entry := range t.conns {
entry.closeFunc()
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsTracker entirely unused dead code

Low Severity

The wsTracker type, wsTrackerEntry, newWSTracker, Add, and CloseAll are defined but never instantiated or called anywhere in the codebase. The PR description mentions this is "for future explicit close-on-shutdown," but shipping dead code adds maintenance burden without providing value. This scaffolding can be added when the feature is actually wired up.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 35ee1e8. Configure here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CloseAll holds lock while invoking arbitrary callbacks

Medium Severity

CloseAll calls each closeFunc() while holding mu. The removal function returned by Add also acquires mu. In the expected usage pattern, closing a WebSocket connection causes the handler goroutine to exit and invoke the removal function, which will block on mu — and if closeFunc waits for that goroutine to finish, it deadlocks. The entries need to be copied and the lock released before invoking callbacks.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 35ee1e8. Configure here.


func mustFFmpeg() {
cmd := exec.Command("ffmpeg", "-version")
if err := cmd.Run(); err != nil {
Expand Down
Loading