Repository navigation
Fall back to the NULL platform when no display is available - #256
NittanyLion wants to merge 1 commit into
Conversation
GLFW.jl calls glfwInit() from __init__, so on a machine with no display server -- headless CI, cluster nodes, batch jobs -- merely loading the package (or any package that depends on it) throws an InitError, and it cannot even be precompiled there. Downstream packages get no chance to handle the situation themselves. GLFW 3.4's NULL platform exists for exactly this case and initializes without a display. When the default platform fails and the user has not requested a platform explicitly, retry with PLATFORM_NULL and warn: the package loads, windows cannot be created or shown, and downstream code can detect the state with GLFW.GetPlatform() == GLFW.PLATFORM_NULL. An explicit JULIA_GLFW_PLATFORM is honored as stated: if the requested platform cannot be initialized, loading fails as before rather than being second-guessed. Behavior with a working display is unchanged. Fixes JuliaGL#169. Addresses the load-time layer of JuliaGL#200 and JuliaGL#253. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #256 +/- ##
==========================================
+ Coverage 10.00% 13.88% +3.88%
==========================================
Files 5 5
Lines 370 389 +19
==========================================
+ Hits 37 54 +17
- Misses 333 335 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@SimonDanisch — would you have a moment to look at this one when you get a chance? Short version: on a headless machine The patch retries It is 43 lines across 4 files, with subprocess tests covering both directions (headless succeeds with NULL; |
|
Hey, I was on holiday ;) |
Problem
__init__callsInit()and treats failure as fatal, so on a machine with no display server — headless CI, HPC/cluster nodes, batch jobs — merely loading GLFW.jl (or any package that depends on it, transitively) throws:Such packages cannot even be precompiled on those machines, since precompiling a dependent package loads GLFW. Downstream packages that have their own perfectly good headless fallbacks (inert windows, file export) never get the chance to use them, because the failure happens below them, at load time.
This is the situation described in #169 and #200, and the default-platform layer of #253.
Fix
GLFW 3.4's NULL platform exists for exactly this case. When
Init()fails and the user has not explicitly requested a platform, retry withPLATFORM_NULLand warn:The originally collected init errors are still surfaced as warnings afterwards (per the preference in #169 not to swallow them), so the user sees both what failed and what happened instead.
Design decisions:
JULIA_GLFW_PLATFORMis honored as stated. If the requested platform cannot be initialized, loading fails exactly as before — a request is not second-guessed. This keeps a way to opt back into fatal behavior (JULIA_GLFW_PLATFORM=x11).Init()already failed.PlatformSupported(PLATFORM_NULL)(callable before init per GLFW docs), so builds without the NULL platform keep the old behavior.GLFW.GetPlatform() == GLFW.PLATFORM_NULL.Re the OSMesa concern raised in #253: OSMesa is only needed for context creation on the NULL platform, not for
glfwInit(). Verified on a machine with no OSMesa installed (ldconfig -p | grep -i osmesaempty): init on the NULL platform succeeds. So loading now works everywhere; window/context creation on a headless node still fails with a normal, catchable GLFW error at the call site — which is where downstream packages can actually handle it.Tests
Added to
test/runtests.jl(Linux, in subprocesses so the test process's initialized GLFW is undisturbed):DISPLAY/WAYLAND_DISPLAYstripped succeeds, withis_initialized()andGetPlatform() == PLATFORM_NULL;JULIA_GLFW_PLATFORM=x11and no display still fails.Verified locally (Linux, Julia 1.12.6, GLFW 3.4.0):
PLATFORM_NULL;JULIA_GLFW_PLATFORM=x11: throws as before;Fixes #169. Addresses the load-time layer of #200 (without the API break of removing
Init()from__init__) and complements the env-var override from #253.🤖 Generated with Claude Code