{"record":{"id":"1c642ab9fc30f0f7","repo":"siyuan-note/siyuan","slug":"related-operations-are-being-processed-please-try","errorCode":null,"errorMessage":"Related operations are being processed, please try again later","messagePattern":"Related operations are being processed, please try again later","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/crypto.go","lineNumber":1372,"sourceCode":"\tnotebookCryptoMu.Lock()\n\tdefer notebookCryptoMu.Unlock()\n\treleaseTransition := holdEncryptedBoxTransition(boxID)\n\tdefer releaseTransition()\n\n\twasUnlocked := IsBoxUnlocked(boxID)\n\tif err = unlockBoxHeld(boxID, password, boxEnc); err != nil {\n\t\treturn false, err\n\t}\n\talreadyMount, err = mountBox(boxID)\n\tif err != nil && !wasUnlocked {\n\t\tlockBoxWithPreparationHeld(boxID, nil)\n\t}\n\treturn alreadyMount, err\n}\n\nfunc unlockBoxHeld(boxID string, password string, boxEnc *conf.BoxEncryption) (err error) {\n\tif _, busy := boxLock.Load(boxID); busy {\n\t\treturn errors.New(Conf.language(239))\n\t}\n\tif boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {\n\t\tsetEncryptedBoxState(boxID, EncryptedBoxStateError)\n\t\treturn errors.New(\"no encrypted key material for box\")\n\t}\n\tif IsBoxUnlocked(boxID) {\n\t\tif GetEncryptedBoxState(boxID) == EncryptedBoxStateError {\n\t\t\treturn errors.New(Conf.Language(316))\n\t\t}\n\t\tsetEncryptedBoxState(boxID, EncryptedBoxStateUnlocked)\n\t\treturn nil\n\t}\n\n\tsetEncryptedBoxState(boxID, EncryptedBoxStateUnlocking)\n\t// 获取 box 写锁，与 LockBox/unmount0 串行化，防止并发锁/解锁导致 db/DEK 状态不一致\n\tacquireBoxWriteLock(boxID)\n\tfinalState := EncryptedBoxStateLocked\n\tdefer func() {","sourceCodeStart":1354,"sourceCodeEnd":1390,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/crypto.go#L1354-L1390","documentation":"Returned by unlockBoxHeld (kernel/model/crypto.go:1371-1372, localized message 239 'Related operations are being processed, please try again later') when the per-notebook lock registry boxLock already holds an entry for the boxID, meaning another lock/unlock/unmount lifecycle operation for the same notebook is in flight. It is a transient busy signal guarding the DEK/db state machine, not a crypto failure; the request made no state changes.","triggerScenarios":"Two concurrent UnlockBox/LockBox/UnmountBox/UnlockAndMountBox calls for the same notebook: user double-clicks the unlock button, an API client retries while the first request is still inside acquireBoxWriteLock, or the auto-lock timer fires while the user is unlocking. Each call runs a ~1s Argon2id derivation, so the busy window is wide enough to hit.","commonSituations":"Frontend not debouncing the unlock dialog submit; batch scripts hammering /api/notebox unlock endpoints; auto-lock (AutoLockMinutes) racing a manual re-unlock after a failed attempt.","solutions":["Retry the same unlock call after a short delay (500ms-2s) - the in-flight operation will have finished","Debounce/disable the unlock button in the client until the first call returns","Serialize all lock/unlock/unmount calls per notebook in your client code instead of firing them in parallel"],"exampleFix":"// before: fire-and-forget concurrent unlock\ngo api.unlockNotebook(boxID, password)\ngo api.unlockNotebook(boxID, password) // may hit busy error 239\n\n// after: serialize + bounded retry on the busy message\nfor attempt := 0; attempt < 3; attempt++ {\n    err := model.UnlockBox(boxID, password, boxCrypt)\n    if err == nil {\n        break\n    }\n    if strings.Contains(err.Error(), Conf.Language(239)) {\n        time.Sleep(time.Duration(500*(attempt+1)) * time.Millisecond)\n        continue\n    }\n    return err // real failure, do not retry\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var err error\nfor attempt := 0; attempt < 3; attempt++ {\n    err = model.UnlockBox(boxID, password, boxCrypt)\n    if err == nil || !strings.Contains(err.Error(), Conf.Language(239)) {\n        break // not the busy error: stop retrying\n    }\n    time.Sleep(time.Duration(400*(attempt+1)) * time.Millisecond)\n}","preventionTips":["Debounce the unlock/lock UI actions so a second submit cannot race the first","Serialize all notebook lifecycle calls (unlock/lock/unmount) per boxID in client code","Remember each unlock runs ~1s of Argon2id - design timeouts and retries around that window"],"tags":["encryption","notebook","concurrency","busy","retry"],"backgroundTag":"concurrent-operation-busy","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}