bevyengine / bevyengine/bevy

Asset loading intermittently exceeds RLIMIT_NOFILE on 0.19 despite the #22560 semaphore (500ms timeout falls through to unbounded opens)

Open
#24,898 2 comments 0 reactions 0 assignees View on GitHub
A-Assets C-Bug D-Modest O-iOS O-MacOS S-Needs-Design
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## Bevy version and features

- Bevy `0.19.0`
- Non-default features: `["jpeg", "ktx2", "basis-universal", "wav"]`

## Relevant system information

- Rust `1.95.0` (stable)
- macOS `26.2` (Apple Silicon), default GUI-launch soft `RLIMIT_NOFILE` = 256
- Backend: Metal

## What you did

I preload ~650 assets in a single startup system: a list of paths looped through `asset_server.load()`, handles stashed in a resource so nothing unloads mid-run. It's ~30 GLBs (scenes + animations + embedded textures), a few hundred textures (KTX2/PNG), ~80 audio files, and 100+ WGSL shaders, so effectively everything is requested in the same frame.

## What went wrong

**Expected:** the concurrent-open-files cap added in #22560 keeps me under the OS `RLIMIT_NOFILE`, so a burst of loads never hits `EMFILE`.

**Actual:** the loads still intermittently fail with `Too many open files (EMFILE)` on 0.19.

Reading #22560, I think the cause is that the semaphore acquire has a **500ms timeout, after which the loader "continues like before"** (commit `32124bb`), i.e. it opens the file anyway. Under a burst like mine, the large GLB/KTX2 reads hold permits for a while, so many waiting loaders hit the timeout and then open their files past the cap, unbounded again. That also explains the intermittency: it only breaks when enough long reads pile up for the timeouts to cascade.

Most resulting failures are visible (missing textures), but the worst one is silent: a **compute shader** asset fails to load, `PipelineCache` reports "Pipeline could not be compiled because the following shader could not be loaded", and then the pipeline **stays in `Err` forever**. My compute pass never dispatches, and a material reading that storage buffer renders uninitialized GPU memory (garbage triangles). for me It took days to trace that back to `EMFILE`, because nothing surfaces the permanent failure at runtime.

> Note: I first raised this in the Discord platform channel but wanted to create on hre also since it may be a silent failure affecting other OSes too.
fyi:
https://discord.com/channels/691052431525675048/749690399135891486/1522946680410996757

Contributor guide

Open the contributing guide

Research direction

Start by tracing the asset_server.load() path through the semaphore acquire and its 500ms timeout, then follow how failures reach PipelineCache. Reproduce a burst of large GLB/KTX2 and shader loads under RLIMIT_NOFILE; done means the cap is not bypassed after timeout and compute-shader load failures are surfaced rather than leaving the pipeline permanently Err.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
game-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.