What goes wrong
Exporting a mixdown of a long track as WAV fails with HTTP 500, after the server
has already spent the full render doing the work.
The threshold is about 49.5 minutes at 44.1 kHz, or 45.5 minutes at
48 kHz. StemDeck accepts tracks up to 60 minutes (_DURATION_MAX = 3600), so
this is a supported track failing, not an unsupported one being refused.
FLAC can reach it too on dense material. MP3 and OGG are far below it, and OGG
does not take this code path at all.
Why
_render_to_file finishes a render by moving it into the mixdown cache, pruning
the cache, and then returning the path the response will be built from:
if cache_path is not None:
os.replace(tmp_path, cache_path)
_prune_mixdown_cache(cache_path.parent)
return cache_path
_prune_mixdown_cache evicts oldest-first while the directory is over either
budget:
while entries and (len(entries) > _MIXDOWN_CACHE_MAX_FILES or total > _MIXDOWN_CACHE_MAX_BYTES):
_MIXDOWN_CACHE_MAX_BYTES is 500 MB. A single render larger than that puts the
directory over budget on its own, so the loop deletes it, even though it is the
newest entry and the only one. The caller then hands a path that no longer exists
to FileResponse.
Reproduced by shrinking the budget below one render, which is the same shape as a
60-minute WAV against the real 500 MB budget:
render returned : deadbeef.wav
file exists : False
FileResponse is handed -> A PATH THAT NO LONGER EXISTS
The streaming path (_stream_ffmpeg, used for OGG) prunes too, but the client has
already received the bytes by then, so there the same eviction only loses a cache
entry rather than the response.
Constraints for anyone fixing this
- A render bigger than the whole cache budget is legitimate. It should still be
delivered to the user; the open question is only whether it is worth caching.
- The response must not be built from a path that another request's prune could
remove between the render finishing and the file being sent. Whatever the fix,
the served file has to be one nothing else is entitled to delete.
_prune_mixdown_cache is documented as best-effort and must stay that way: a
failure to prune should never break an export.
- Both budgets matter.
_MIXDOWN_CACHE_MAX_FILES (20) can evict a fresh entry too
once the directory is full, though only when older entries exist to go first.
What goes wrong
Exporting a mixdown of a long track as WAV fails with HTTP 500, after the server
has already spent the full render doing the work.
The threshold is about 49.5 minutes at 44.1 kHz, or 45.5 minutes at
48 kHz. StemDeck accepts tracks up to 60 minutes (
_DURATION_MAX = 3600), sothis is a supported track failing, not an unsupported one being refused.
FLAC can reach it too on dense material. MP3 and OGG are far below it, and OGG
does not take this code path at all.
Why
_render_to_filefinishes a render by moving it into the mixdown cache, pruningthe cache, and then returning the path the response will be built from:
_prune_mixdown_cacheevicts oldest-first while the directory is over eitherbudget:
_MIXDOWN_CACHE_MAX_BYTESis 500 MB. A single render larger than that puts thedirectory over budget on its own, so the loop deletes it, even though it is the
newest entry and the only one. The caller then hands a path that no longer exists
to
FileResponse.Reproduced by shrinking the budget below one render, which is the same shape as a
60-minute WAV against the real 500 MB budget:
The streaming path (
_stream_ffmpeg, used for OGG) prunes too, but the client hasalready received the bytes by then, so there the same eviction only loses a cache
entry rather than the response.
Constraints for anyone fixing this
delivered to the user; the open question is only whether it is worth caching.
remove between the render finishing and the file being sent. Whatever the fix,
the served file has to be one nothing else is entitled to delete.
_prune_mixdown_cacheis documented as best-effort and must stay that way: afailure to prune should never break an export.
_MIXDOWN_CACHE_MAX_FILES(20) can evict a fresh entry tooonce the directory is full, though only when older entries exist to go first.