{"record":{"id":"0e84149848db0490","repo":"unslothai/unsloth","slug":"diffusion-generation-was-cancelled","errorCode":null,"errorMessage":"Diffusion generation was cancelled.","messagePattern":"Diffusion generation was cancelled\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"info","filePath":"studio/backend/core/inference/diffusion.py","lineNumber":4830,"sourceCode":"\n    def _controlnet_pipe(self, state: _LoadState, resolved_cn: Any, cancel: threading.Event) -> Any:\n        \"\"\"Build (once, cached) the family's diffusers ControlNet pipeline around the requested\n        ControlNet model. The ControlNet model is a small extra module loaded via from_pretrained\n        and cached by id; the pipeline is assembled with ``Pipeline.from_pipe(base,\n        controlnet=model)`` -- reusing the resident base modules at their loaded dtype (no reload,\n        no recast; torch_dtype=None for the same reason as _workflow_pipe). Raises a clear\n        ValueError when the family declares no ControlNet classes.\"\"\"\n        fam = state.family\n        pipe_cls_name = getattr(fam, \"controlnet_pipeline_class\", None)\n        model_cls_name = getattr(fam, \"controlnet_model_class\", None)\n        if not pipe_cls_name or not model_cls_name:\n            raise ValueError(f\"ControlNet is not supported for the '{fam.name}' model family.\")\n        import diffusers\n\n        cn_model = self._cn_models.get(resolved_cn.id)\n        if cn_model is None:\n            if cancel.is_set():\n                raise RuntimeError(DIFFUSION_CANCELLED_MSG)\n            # resolve_controlnet accepts a bare owner/name without the trust gate and from_pretrained would execute a malicious\n            # pickle, so run the same Hub malware preflight. It fails OPEN, so a remote repo also forces safetensors below.\n            remote_cn = not getattr(resolved_cn, \"is_local\", False)\n            if remote_cn:\n                from utils.security import evaluate_file_security\n                _cn_fs = evaluate_file_security(resolved_cn.path, hf_token = state.hf_token or None)\n                if _cn_fs.blocked:\n                    raise ValueError(_cn_fs.reason)\n            # Keep at most one ControlNet resident, else swapping ControlNets accumulates until OOM.\n            if self._cn_models or self._cn_pipes:\n                self._cn_models.clear()\n                self._cn_pipes.clear()\n                clear_gpu_cache()\n            import torch\n\n            # state.dtype is the display string (\"bfloat16\"), so pass the real dtype and avoid a float32 load.\n            cn_dtype = getattr(torch, str(state.dtype).replace(\"torch.\", \"\"), None)\n            # Force safetensors for an untrusted remote repo: if the Hub scan failed open, an embedded pickle would still deserialize.","sourceCodeStart":4812,"sourceCodeEnd":4848,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/diffusion.py#L4812-L4848","documentation":"A benign cancellation sentinel: while building the ControlNet pipeline, the per-generation `cancel` Event was already set before the ControlNet model download/load began, so `_controlnet_pipe` raises RuntimeError(DIFFUSION_CANCELLED_MSG) instead of doing wasted work. The cancel Event is set under `_lock` by unload() or a superseding load, so this indicates a model swap raced an in-flight generation request.","triggerScenarios":"A generate() call with ControlNet whose resolved ControlNet model is not cached; before `from_pretrained` starts, `cancel.is_set()` is True because unload() or a new load signaled this generation. The first check in the `cn_model is None` branch fires.","commonSituations":"User cancels or switches models right as a ControlNet job starts; a superseding load invalidates in-flight jobs; rapid UI interactions triggering unload during queued generations. Expected under normal concurrent use, not a bug.","solutions":["Catch RuntimeError with the cancellation message and treat it as a no-op (the job was intentionally aborted).","Re-issue the generate call after the new model finishes loading if the ControlNet result is still wanted.","In orchestrating code, serialize load/unload and generate so cancels are observed before dispatching work."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_cancel(exc: RuntimeError) -> bool:\n    return \"cancelled\" in str(exc).lower()","tryCatchPattern":"try:\n    result = diffusion.generate(**params)\nexcept RuntimeError as e:\n    if str(e) == DIFFUSION_CANCELLED_MSG:  # or compare against the exported constant\n        return {\"status\": \"cancelled\"}  # benign abort, not an error\n    raise","preventionTips":["Treat the cancellation message as control flow, not failure; never retry it automatically.","Serialize model load/unload against generation dispatch in your orchestrator.","Distinguish cancel sentinels from real failures by exact message or exception type."],"tags":["diffusion","cancellation","concurrency","controlnet"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}