Bug in NVIDIA-NeMo/RL #4655. All permalinks pin the PR head 495ce13. Review comments are in a pending review; their links work once it is published.
generation_prefix_cleanup.enabled, the background thread stores the first delete error and exits. From then on, every finishing call and every checkpoint on that worker raises.Two pages on this PR โ this one: what one failed delete does with the flag on.
Every finishing call first asks the cleanup thread for a slot with reserve(), even a call that was never cut and has nothing to delete.
When a CLEAR fails, the thread stores the error and returns (L206-L215), and reserve() checks that error first (L146-L152).
That check sits outside the handler that says "completion is already durable" (L1563), and after the try that cleans up a failed call (L2558-L2560). So the client gets HTTP 500, and the call's capture state stays registered.
thread alive after failure: False / pending after failure: 1
reserve raised: RuntimeError('background prefix cleanup failed; no automatic retry') (every call)
fence wait_paused raised: RuntimeError('background prefix cleanup failed; no automatic retry')
Output of a repro with the real module and a clear that fails once. Only an actor restart clears the stored error.
review comment โ background_prefix_cleanup.py:206
Nothing below is implemented, and it has not been run.
Where the fix lands: RL only, one file.
BackgroundPrefixCleanup._drain, plus dropping the error check from reserve() and wait_paused(). No Gym change.
@@ background_prefix_cleanup.py L206-L216 ยท _drain try: self._clear(keys) - except Exception as error: # noqa: BLE001 - surface arbitrary transport failures - with self._condition: - self._error = error - self._failed_keys = keys - self._active = False - self._condition.notify_all() - LOGGER.exception( - "background prefix cleanup failed for %d keys", len(keys) - ) - return + except Exception: # noqa: BLE001 - a lost delete only leaves extra rows + # Never retry: a CLEAR may have partly succeeded. Keep serving. + LOGGER.exception( + "background prefix cleanup failed for %d keys", len(keys) + ) with self._condition: self._active = False self._pending -= len(batch) self._condition.notify_all()
What it does: a failed batch is logged and dropped, the slot count goes back down, and the thread keeps going, exactly like the flag-off path. It never retries, because a CLEAR can partly succeed. A broken TQ still fails loudly at the next final-row write, so nothing is hidden. If the module is rewritten to take a permit on the existing checkpoint gate (previous page), this bug goes away with it.