Skip to content

Fall back to the NULL platform when no display is available - #256

Open
NittanyLion wants to merge 1 commit into
JuliaGL:masterfrom
NittanyLion:headless-null-fallback
Open

NittanyLion wants to merge 1 commit into
JuliaGL:masterfrom
NittanyLion:headless-null-fallback

Conversation

@NittanyLion

Copy link
Copy Markdown

Problem

__init__ calls Init() 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:

ERROR: InitError: Exception[GLFW.GLFWError(65550, "X11: The DISPLAY environment variable is missing"), ErrorException("glfwInit failed")]

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 with PLATFORM_NULL and warn:

┌ Warning: GLFW could not connect to a display and fell back to its NULL platform: the package is usable, but windows cannot be created or shown. Detect this with `GLFW.GetPlatform() == GLFW.PLATFORM_NULL`. To make a missing display fatal again, set the JULIA_GLFW_PLATFORM environment variable to the required platform (e.g. "x11").

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:

  • An explicit JULIA_GLFW_PLATFORM is 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).
  • Behavior with a working display is byte-for-byte unchanged — the fallback branch is only reached when Init() already failed.
  • The fallback is guarded by PlatformSupported(PLATFORM_NULL) (callable before init per GLFW docs), so builds without the NULL platform keep the old behavior.
  • Downstream code can detect the headless state with 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 osmesa empty): 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):

  1. loading with DISPLAY/WAYLAND_DISPLAY stripped succeeds, with is_initialized() and GetPlatform() == PLATFORM_NULL;
  2. loading with JULIA_GLFW_PLATFORM=x11 and no display still fails.

Verified locally (Linux, Julia 1.12.6, GLFW 3.4.0):

  • full test suite passes with a display, including the window-open test;
  • headless: loads with warning, PLATFORM_NULL;
  • headless + JULIA_GLFW_PLATFORM=x11: throws as before;
  • end-to-end: a dependent GUI package that previously could not be precompiled or loaded headless now precompiles, loads, detects the missing display itself, and runs its own file-export path to completion.

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

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-commenter

codecov-commenter commented Aug 12, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 83.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 13.88%. Comparing base (9bb194d) to head (0b4d11b).
⚠️ Report is 17 commits behind head on master.

Files with missing lines Patch % Lines
src/GLFW.jl 83.33% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@NittanyLion

Copy link
Copy Markdown
Author

@SimonDanisch — would you have a moment to look at this one when you get a chance?

Short version: on a headless machine using GLFW dies at init with X11: The DISPLAY environment variable is missing. Because that is a load-time failure, it takes down precompilation of anything that depends on GLFW.jl, so downstream packages never get the chance to run their own headless fallbacks. This bites on CI runners, HPC batch nodes, and containers.

The patch retries Init() with GLFW 3.4's NULL platform when — and only when — the first attempt fails and no explicit platform was requested. Packages can then check GLFW.GetPlatform() == GLFW.PLATFORM_NULL and take their own path, and anyone who wants the old fatal behaviour sets JULIA_GLFW_PLATFORM. Nothing changes when a display is present.

It is 43 lines across 4 files, with subprocess tests covering both directions (headless succeeds with NULL; JULIA_GLFW_PLATFORM=x11 still fails without a display). CI is green on all six jobs and the branch is current with master. Happy to change the approach if you would rather gate it differently.

@SimonDanisch

Copy link
Copy Markdown
Member

Hey, I was on holiday ;)
Currently trying to figure out how to release it, since not erroring breaks the contract for GLMakie's error messages.
I guess we could tag it at GLFW 4.0, but that means we'll stop tracking the version.
Although I guess this issue shows, that it's kind of unrealistic to track the glfw C version....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GLFW error messages on headless system

3 participants