{"record":{"id":"f893d25ad7999321","repo":"unslothai/unsloth","slug":"the-previous-dictation-worker-did-not-exit-and-sti","errorCode":null,"errorMessage":"The previous dictation worker did not exit and still holds its memory. Try again shortly.","messagePattern":"The previous dictation worker did not exit and still holds its memory\\. Try again shortly\\.","errorType":"http","errorClass":"SttModelBusyError","httpStatus":409,"severity":"error","filePath":"studio/backend/core/inference/stt_sidecar.py","lineNumber":1485,"sourceCode":"            candidate = None\n            device: Optional[str] = None\n            resident_released = False\n            self._start_survivor = None\n            try:\n                self._raise_if_load_cancelled(cancel_event)\n                cached = self._ensure_model_downloaded(model_id)\n                snapshot_path = cached.path\n                if snapshot_path is None:\n                    raise SttModelNotDownloadedError(\n                        f\"STT model '{model_id}' is not downloaded. \"\n                        \"Download it in Settings, then Voice, before loading it.\"\n                    )\n                self._raise_if_load_cancelled(cancel_event)\n                device, dtype = _pick_device()\n                if not self._release_engine_locked():\n                    # Starting a second child over one that never exited doubles\n                    # the memory this release was meant to give back.\n                    raise SttModelBusyError(\n                        \"The previous dictation worker did not exit and still holds its \"\n                        \"memory. Try again shortly.\"\n                    )\n                resident_released = True\n                logger.info(\"Loading STT model %s (%s) on %s\", model_id, snapshot_path, device)\n\n                def not_downloaded(cause: BaseException) -> SttModelNotDownloadedError:\n                    return SttModelNotDownloadedError(\n                        f\"STT model '{model_id}' is not downloaded. \"\n                        \"Download it in Settings, then Voice, before loading it.\"\n                    )\n\n                retry_on_cpu = False\n                try:\n                    candidate = self._build_model(str(snapshot_path), device, dtype, cancel_event)\n                    self._raise_if_load_cancelled(cancel_event)\n                except SttLoadCancelledError:\n                    raise","sourceCodeStart":1467,"sourceCodeEnd":1503,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/stt_sidecar.py#L1467-L1503","documentation":"Raised in load() when _release_engine_locked() fails to retire the previous dictation worker child process before starting a new one. The comment explains why: starting a second child over one that never exited would double GPU/CPU memory, so the load refuses and a timer retries the release.","triggerScenarios":"Switching models (or reloading after eviction) while the previous worker subprocess has not terminated — it survived its kill signal, is hung in a CUDA call, or is a zombie the reaper has not reaped.","commonSituations":"Worker stuck in a long GPU kernel or driver teardown; SIGKILL not yet delivered/processed under load; PID namespace or zombie-reaping issues in containers; rapid model switching thrash.","solutions":["Simply retry after a short wait — the release timer keeps trying and the next load usually succeeds.","If persistent, inspect for orphaned worker processes (ps | grep the sidecar worker) and kill them manually.","In containers, ensure the init/reaper (e.g. tini) is set so killed children are reaped; check GPU memory with nvidia-smi for a leaked worker."],"exampleFix":"# before\nengine = sidecar.load(new_model)  # previous worker alive -> SttModelBusyError\n# after\nfor _ in range(5):\n    try:\n        engine = sidecar.load(new_model); break\n    except SttModelBusyError:\n        time.sleep(2)","handlingStrategy":"retry","validationCode":"import subprocess\ndef leaked_workers() -> list[str]:\n    out = subprocess.run([\"ps\", \"-eo\", \"pid,cmd\"], capture_output=True, text=True).stdout\n    return [l for l in out.splitlines() if \"stt\" in l and \"worker\" in l]","typeGuard":null,"tryCatchPattern":"for _ in range(5):\n    try:\n        engine = sidecar.load(model); break\n    except SttModelBusyError:\n        time.sleep(2)  # release timer retries meanwhile\nelse:\n    kill_leaked_workers(); engine = sidecar.load(model)","preventionTips":["Run containers with an init/reaper so killed workers are reaped","Avoid rapid model switching; let one switch settle first","Monitor GPU memory for leaked worker processes after failures"],"tags":["stt","worker","process","memory","busy","gpu"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}