{"record":{"id":"2ace28d8e2d17ab9","repo":"unslothai/unsloth","slug":"an-import-into-folder-name-is-already-running-2ace28","errorCode":null,"errorMessage":"An import into '{folder.name}' is already running. Wait for it to finish, then reload the dataset list.","messagePattern":"An import into '(.+?)' is already running\\. Wait for it to finish, then reload the dataset list\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"studio/backend/routes/training.py","lineNumber":4222,"sourceCode":"    \"\"\"Materialize a curated example dataset into a Studio dataset folder (images + .txt\n    captions), ready to train. Idempotent: a folder that already holds images is returned\n    as-is rather than re-downloaded.\"\"\"\n    _require_diffusion_dataset_mutable()\n    entry = _example_by_id(body.id)\n    folder = _resolve_dataset_folder(body.name or entry[\"id\"], must_exist = False)\n\n    def do_import() -> DiffusionDatasetImportResponse:\n        folder.mkdir(parents = True, exist_ok = True)\n        # Any trainable item already in the folder makes this a no-op: dropping example images\n        # into a folder the user filled with clips would silently mix two dataset kinds.\n        summary = _diffusion_dataset_summary(folder)\n        if summary.image_count > 0 or summary.clip_count > 0:\n            return _import_response(entry, folder, imported = 0)\n        # One import at a time per dataset folder: the training interlock COUNTS mutations rather than excluding them, so two\n        # imports into the same empty name both passed the emptiness check and merged. Refusing the second is honest.\n        lock = _dataset_import_lock(folder)\n        if not lock.acquire(blocking = False):\n            raise HTTPException(\n                status_code = 409,\n                detail = (\n                    f\"An import into '{folder.name}' is already running. Wait for it to finish, \"\n                    \"then reload the dataset list.\"\n                ),\n            )\n        try:\n            return _do_import_locked(entry, folder)\n        finally:\n            lock.release()\n\n    def _do_import_locked(entry: dict, folder: Path) -> DiffusionDatasetImportResponse:\n        import os\n        import shutil\n        import tempfile\n\n        imported = 0\n        # Re-read under the lock: a winner may have promoted its staging dir while this request was checking, so returning the folder as-is matches the idempotent path.","sourceCodeStart":4204,"sourceCodeEnd":4240,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/training.py#L4204-L4240","documentation":"HTTP 409 from the example-dataset import route: a per-folder import lock (_dataset_import_lock(folder), acquired non-blocking) is already held, meaning another import into the same dataset name is in flight. Exists because the training interlock counts mutations rather than excluding concurrent ones — two imports into the same empty name would otherwise both pass the emptiness check and merge.","triggerScenarios":"Two concurrent POSTs to import an example into the same dataset name (double-click of an import button, script retry while the first request is still running). The second request 409s immediately at lock.acquire(blocking=False).","commonSituations":"UI double-submit without disabling the button; impatient retries of a slow import (HF downloads take minutes); parallel automation scripts importing the same example.","solutions":["Wait for the in-flight import to finish, then reload the dataset list — the running import's result will be there.","Prevent double-submits: disable the import button while a request is in flight.","If the first import crashed and the lock is stale, verify no import thread is alive, then restart the backend process to clear in-memory locks."],"exampleFix":"// before\nbutton.onclick = () => api.importExample(ex.id);\n// after\nbutton.disabled = true;\ntry { await api.importExample(ex.id); } finally { button.disabled = false; }","handlingStrategy":"retry","validationCode":"async function importOnce(exId) {\n  if (inFlight[exId]) return inFlight[exId];\n  inFlight[exId] = api.importExample(exId).finally(() => delete inFlight[exId]);\n  return inFlight[exId];\n}","typeGuard":"def is_import_in_flight(exc: HTTPException) -> bool:\n    return exc.status_code == 409 and 'already running' in str(exc.detail)","tryCatchPattern":"try:\n    resp = await api.importExample(ex.id)\nexcept HTTPStatusError as e:\n    if e.response.status_code == 409 and 'already running' in e.response.text:\n        await wait_for_import_completion(ex)  # poll dataset list, then proceed\n        return\n    raise","preventionTips":["Debounce/disable import buttons client-side.","On 409 'already running', wait-and-reload instead of immediate retry — retrying instantly will 409 again.","Key in-flight guards per dataset name, not globally."],"tags":["concurrency","http-409","import","locking"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}