Skip to content

Commit 40f565c

Browse files
committed
wrapper: parallel phase A boot, per-probe durations, split into feature files
Start chromium alongside xorg/xvfb so chromium-launcher's preamble overlaps display startup; chromium-launcher waits internally for the X server. Final ready log reports per-probe (cdp/chromedriver/forward-proxy/...) durations so the gating probe is visible. main.go split into feature files (supervisord, probes, display, chromium, envoy, system).
1 parent 60ae285 commit 40f565c

8 files changed

Lines changed: 467 additions & 361 deletions

File tree

server/cmd/chromium-launcher/main.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ func main() {
4242
// Wait for devtools port to be available (handles SIGKILL socket cleanup delay)
4343
waitForPort(internalPort, 5*time.Second)
4444

45+
// Wait for the X server. The wrapper starts chromium in parallel with
46+
// xorg/xvfb, so the display socket may not be ready yet — without this
47+
// gate chromium would fail on connect and supervisord would restart us.
48+
waitForX(":1", 20*time.Second)
49+
4550
baseFlags := os.Getenv("CHROMIUM_FLAGS")
4651
runtimeTokens, err := chromiumflags.ReadOptionalFlagFile(*runtimeFlagsPath)
4752
if err != nil {
@@ -125,6 +130,30 @@ func execLookPath(file string) (string, error) {
125130
return exec.LookPath(file)
126131
}
127132

133+
// waitForX blocks until the X server is reachable on display :N. We try
134+
// both the named unix socket (Xorg, headful) and the abstract namespace
135+
// socket (Xvfb runs with -nolisten unix, which disables the named socket
136+
// but leaves the abstract one). Mirrors the wrapper's check so chromium
137+
// can be started in parallel with the X server without failing on connect.
138+
func waitForX(display string, timeout time.Duration) {
139+
num := strings.TrimPrefix(display, ":")
140+
named := "/tmp/.X11-unix/X" + num
141+
abstract := "@/tmp/.X11-unix/X" + num
142+
deadline := time.Now().Add(timeout)
143+
for time.Now().Before(deadline) {
144+
if c, err := net.DialTimeout("unix", named, 200*time.Millisecond); err == nil {
145+
_ = c.Close()
146+
return
147+
}
148+
if c, err := net.DialTimeout("unix", abstract, 200*time.Millisecond); err == nil {
149+
_ = c.Close()
150+
return
151+
}
152+
time.Sleep(20 * time.Millisecond)
153+
}
154+
fmt.Fprintf(os.Stderr, "warning: X display %s not responsive after %s\n", display, timeout)
155+
}
156+
128157
// waitForPort waits until the given port is available for binding on IPv4.
129158
// This handles the delay after SIGKILL before the kernel releases the socket.
130159
// We disable SO_REUSEADDR to get an accurate check matching chromium's bind behavior.

server/cmd/wrapper/chromium.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"strings"
8+
"time"
9+
)
10+
11+
// applyHeadlessDefaultFlags mirrors the legacy headless wrapper.sh: when
12+
// CHROMIUM_FLAGS is unset, fill in a curated headless+stealth flag list.
13+
// --disable-background-networking is intentionally omitted: it prevents
14+
// Chrome from fetching ExtensionInstallForcelist managed extensions.
15+
func applyHeadlessDefaultFlags() {
16+
if strings.TrimSpace(os.Getenv("CHROMIUM_FLAGS")) != "" {
17+
return
18+
}
19+
flags := strings.Join([]string{
20+
"--accept-lang=en-US,en",
21+
"--allow-pre-commit-input",
22+
"--blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4",
23+
"--crash-dumps-dir=/tmp/chromium-dumps",
24+
"--disable-back-forward-cache",
25+
"--disable-background-timer-throttling",
26+
"--disable-backgrounding-occluded-windows",
27+
"--disable-blink-features=AutomationControlled",
28+
"--disable-breakpad",
29+
"--disable-client-side-phishing-detection",
30+
"--disable-component-extensions-with-background-pages",
31+
"--disable-crash-reporter",
32+
"--disable-crashpad",
33+
"--disable-dev-shm-usage",
34+
"--disable-features=AcceptCHFrame,AutoExpandDetailsElement,AvoidUnnecessaryBeforeUnloadCheckSync,CertificateTransparencyComponentUpdater,DeferRendererTasksAfterInput,DestroyProfileOnBrowserClose,DialMediaRouteProvider,ExtensionManifestV2Disabled,GlobalMediaControls,HttpsUpgrades,ImprovedCookieControls,LazyFrameLoading,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate",
35+
"--disable-field-trial-config",
36+
"--disable-gcm-registration",
37+
"--disable-gpu",
38+
"--disable-gpu-compositing",
39+
"--disable-hang-monitor",
40+
"--disable-ipc-flooding-protection",
41+
"--disable-notifications",
42+
"--disable-popup-blocking",
43+
"--disable-prompt-on-repost",
44+
"--disable-renderer-backgrounding",
45+
"--disable-search-engine-choice-screen",
46+
"--disable-software-rasterizer",
47+
"--enable-use-zoom-for-dsf=false",
48+
"--export-tagged-pdf",
49+
"--force-color-profile=srgb",
50+
"--hide-crash-restore-bubble",
51+
"--hide-scrollbars",
52+
"--metrics-recording-only",
53+
"--mute-audio",
54+
"--no-default-browser-check",
55+
"--no-first-run",
56+
"--no-sandbox",
57+
"--no-service-autorun",
58+
"--ozone-platform=headless",
59+
"--password-store=basic",
60+
"--unsafely-disable-devtools-self-xss-warnings",
61+
"--use-angle=swiftshader",
62+
"--use-gl=angle",
63+
"--use-mock-keychain",
64+
}, " ")
65+
_ = os.Setenv("CHROMIUM_FLAGS", flags)
66+
}
67+
68+
// dismissNoSandboxWarning replicates the wrapper.sh behaviour of clicking the
69+
// "X" on the --no-sandbox infobar. Cosmetic; runs off the hot path.
70+
func dismissNoSandboxWarning() {
71+
out, err := exec.Command("xdotool", "getdisplaygeometry").Output()
72+
if err != nil {
73+
return
74+
}
75+
parts := strings.Fields(strings.TrimSpace(string(out)))
76+
if len(parts) != 2 {
77+
return
78+
}
79+
width := parts[0]
80+
x := width
81+
if w := atoi(width); w > 30 {
82+
x = fmt.Sprintf("%d", w-30)
83+
}
84+
target := "New Tab - Chromium"
85+
deadline := time.Now().Add(30 * time.Second)
86+
for time.Now().Before(deadline) {
87+
out, err := exec.Command("xdotool", "search", "--name", target).Output()
88+
if err == nil && len(strings.TrimSpace(string(out))) > 0 {
89+
id := strings.Fields(string(out))[0]
90+
if exec.Command("xdotool", "windowactivate", "--sync", id).Run() == nil {
91+
break
92+
}
93+
}
94+
time.Sleep(100 * time.Millisecond)
95+
}
96+
// Without a settle delay the click can land before the --no-sandbox infobar
97+
// has finished painting, leaving the warning on screen. The legacy
98+
// wrapper.sh slept 5s here for the same reason. Runs off the hot path
99+
// (goroutine fired post-readiness) so this doesn't extend time-to-CDP.
100+
time.Sleep(5 * time.Second)
101+
port := os.Getenv("KERNEL_IMAGES_API_PORT")
102+
if port == "" {
103+
port = defaultAPIPort
104+
}
105+
body := fmt.Sprintf(`{"x":%s,"y":115}`, x)
106+
_ = exec.Command("curl", "-s", "-o", "/dev/null", "-X", "POST",
107+
"http://localhost:"+port+"/computer/click_mouse",
108+
"-H", "Content-Type: application/json",
109+
"-d", body).Run()
110+
}
111+
112+
func atoi(s string) int {
113+
n := 0
114+
for _, c := range s {
115+
if c < '0' || c > '9' {
116+
return 0
117+
}
118+
n = n*10 + int(c-'0')
119+
}
120+
return n
121+
}

server/cmd/wrapper/display.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"net"
5+
"strings"
6+
"time"
7+
)
8+
9+
// waitForX waits until the X server is reachable on display :N. We try both
10+
// the named unix socket (Xorg, headful) and the abstract namespace socket
11+
// (Xvfb runs with -nolisten unix, which disables the named socket but leaves
12+
// the abstract one). Cheaper than spawning xdpyinfo in a loop.
13+
func waitForX(display string, timeout time.Duration) {
14+
num := strings.TrimPrefix(display, ":")
15+
named := "/tmp/.X11-unix/X" + num
16+
abstract := "@/tmp/.X11-unix/X" + num // Linux abstract namespace
17+
deadline := time.Now().Add(timeout)
18+
for time.Now().Before(deadline) {
19+
if c, err := net.DialTimeout("unix", named, 200*time.Millisecond); err == nil {
20+
_ = c.Close()
21+
return
22+
}
23+
if c, err := net.DialTimeout("unix", abstract, 200*time.Millisecond); err == nil {
24+
_ = c.Close()
25+
return
26+
}
27+
time.Sleep(20 * time.Millisecond)
28+
}
29+
logf("WARNING: X display %s not responsive after %s", display, timeout)
30+
}

server/cmd/wrapper/envoy.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import "os"
4+
5+
// envoyEnabled mirrors init-envoy.sh's gate: when any of these are unset
6+
// the script exits early without starting envoy, so we should skip the
7+
// readiness probe too (otherwise it would just time out at 60s).
8+
func envoyEnabled() bool {
9+
return os.Getenv("INST_NAME") != "" &&
10+
os.Getenv("METRO_NAME") != "" &&
11+
os.Getenv("XDS_SERVER") != "" &&
12+
os.Getenv("KERNEL_INSTANCE_JWT") != ""
13+
}

0 commit comments

Comments
 (0)