Skip to content

Commit fb56b04

Browse files
linesightclaude
andcommitted
Use --disable-setuid-sandbox instead of --no-sandbox on Linux CI
--no-sandbox in CEF 146 no-sandbox mode skips the infrastructure that registers the Mojo IPC bootstrap fd (global descriptor key 7) for child processes, causing all subprocesses to crash with "Failed global descriptor lookup: 7". Previous workarounds (--single-process, --no-zygote, in-process services) eliminated subprocess spawns but --single-process introduced a new deadlock with CEF's external pump mode. Fix: drop --no-sandbox and use --disable-setuid-sandbox instead. This tells Chrome to skip the setuid helper binary (not shipped) and fall back to the user-namespace sandbox, which correctly sets up the Mojo IPC fd for every subprocess. User namespaces are available on GitHub Actions Ubuntu 24.04 runners. Also revert the short-lived async CreateBrowser() pump-loop hack which is no longer needed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1da62d7 commit fb56b04

3 files changed

Lines changed: 16 additions & 55 deletions

File tree

src/cefpython.pyx

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -806,36 +806,11 @@ def CreateBrowserSync(windowInfo=None,
806806
cdef CefRefPtr[CefDictionaryValue] extra_info
807807

808808
# CEF browser creation.
809-
IF UNAME_SYSNAME == "Linux":
810-
# CefBrowserHost::CreateBrowserSync() deadlocks when combined with
811-
# --single-process + external pump mode (CefDoMessageLoopWork). The
812-
# nested RunLoop it creates cannot drive the in-process renderer
813-
# thread initialisation. Use async CreateBrowser() and keep pumping
814-
# the message loop until OnAfterCreated fires and populates
815-
# g_pyBrowsers, then retrieve the CefBrowser ref from there.
816-
cdef set _before_browser_ids = set(g_pyBrowsers.keys())
817-
cef_browser_static.CreateBrowser(
809+
with nogil:
810+
cefBrowser = cef_browser_static.CreateBrowserSync(
818811
cefWindowInfo, <CefRefPtr[CefClient]?>clientHandler,
819812
cefNavigateUrl, cefBrowserSettings, extra_info,
820813
cefRequestContext)
821-
cdef PyBrowser _linux_pyBrowser = None
822-
cdef int _i
823-
for _i in range(6000): # up to 60 s at 0.01 s/iter
824-
with nogil:
825-
CefDoMessageLoopWork()
826-
_new_browser_ids = set(g_pyBrowsers.keys()) - _before_browser_ids
827-
if _new_browser_ids:
828-
_linux_pyBrowser = GetPyBrowserById(min(_new_browser_ids))
829-
if _linux_pyBrowser is not None:
830-
cefBrowser = _linux_pyBrowser.cefBrowser
831-
break
832-
time.sleep(0.01)
833-
ELSE:
834-
with nogil:
835-
cefBrowser = cef_browser_static.CreateBrowserSync(
836-
cefWindowInfo, <CefRefPtr[CefClient]?>clientHandler,
837-
cefNavigateUrl, cefBrowserSettings, extra_info,
838-
cefRequestContext)
839814

840815
if not cefBrowser or not cefBrowser.get():
841816
Debug("CefBrowser::CreateBrowserSync() failed")

unittests/main_test.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -144,27 +144,20 @@ def test_main(self):
144144
# Chrome 130+ blocks window.open() called without a user gesture.
145145
switches = {"disable-popup-blocking": ""}
146146
if LINUX:
147-
# Sandbox setup (user-namespace) fails on CI runners.
148-
switches["no-sandbox"] = ""
147+
# Disable the setuid sandbox helper (not shipped in our package)
148+
# so that Chrome falls back to the user-namespace sandbox, which
149+
# correctly passes the Mojo IPC bootstrap fd to subprocesses.
150+
switches["disable-setuid-sandbox"] = ""
149151
# /dev/shm is too small in CI containers.
150152
switches["disable-dev-shm-usage"] = ""
151153
# GPU acceleration is not available under xvfb.
152154
switches["disable-gpu"] = ""
153155
switches["disable-gpu-compositing"] = ""
154156
# Run GPU process inside the browser process so it is not
155-
# spawned during CefInitialize() where it would fail the
156-
# global descriptor lookup (key 7) and block OnContextInitialized.
157+
# spawned during CefInitialize() where it would fail.
157158
switches["in-process-gpu"] = ""
158-
# Run the renderer inside the browser process. Without this,
159-
# the renderer subprocess fails the global descriptor lookup
160-
# (key 7, the Mojo IPC bootstrap fd) because CEF 146 does not
161-
# pass it in --no-sandbox mode; CEF then waits ~60s before
162-
# CreateBrowserSync() returns None.
163-
switches["single-process"] = ""
164-
switches["no-zygote"] = ""
165-
# Run utility services in-process so they don't need the Mojo
166-
# bootstrap fd (global descriptor 7) that CEF 146 does not pass
167-
# to subprocesses in --no-sandbox mode on Linux CI.
159+
# Run utility services in-process so they don't each need a
160+
# separate subprocess (reduces spawn overhead on CI).
168161
switches["disable-features"] = "StorageServiceOutOfProcess"
169162
switches["enable-features"] = "NetworkServiceInProcess"
170163
cef.Initialize(settings, switches=switches)

unittests/osr_test.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,17 @@ def test_osr(self):
113113
"disable-surfaces": "", # This is required for PDF ext to work
114114
}
115115
if LINUX:
116-
# Sandbox setup fails on CI runners.
117-
switches["no-sandbox"] = ""
116+
# Disable the setuid sandbox helper (not shipped in our package)
117+
# so that Chrome falls back to the user-namespace sandbox, which
118+
# correctly passes the Mojo IPC bootstrap fd to subprocesses.
119+
switches["disable-setuid-sandbox"] = ""
118120
# /dev/shm is too small in CI containers.
119121
switches["disable-dev-shm-usage"] = ""
120122
# Run GPU process inside the browser process so it is not
121-
# spawned during CefInitialize() where it would fail the
122-
# global descriptor lookup (key 7) and block OnContextInitialized.
123+
# spawned during CefInitialize() where it would fail.
123124
switches["in-process-gpu"] = ""
124-
# Run the renderer inside the browser process. Without this,
125-
# the renderer subprocess fails the global descriptor lookup
126-
# (key 7, the Mojo IPC bootstrap fd) because CEF 146 does not
127-
# pass it in --no-sandbox mode; CEF then waits ~60s before
128-
# CreateBrowserSync() returns None.
129-
switches["single-process"] = ""
130-
switches["no-zygote"] = ""
131-
# Run utility services in-process so they don't need the Mojo
132-
# bootstrap fd (global descriptor 7) that CEF 146 does not pass
133-
# to subprocesses in --no-sandbox mode on Linux CI.
125+
# Run utility services in-process so they don't each need a
126+
# separate subprocess (reduces spawn overhead on CI).
134127
switches["disable-features"] = "StorageServiceOutOfProcess"
135128
switches["enable-features"] = "NetworkServiceInProcess"
136129
browser_settings = {

0 commit comments

Comments
 (0)