{"record":{"id":"f2f56be850784329","repo":"siyuan-note/siyuan","slug":"239","errorCode":"239","errorMessage":"Conf.language(239)","messagePattern":"Conf\\.language\\(239\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/mount.go","lineNumber":191,"sourceCode":"\nfunc collectBoxDeletedAttributeViewBlocks(boxID string) (ret map[string]map[string]struct{}, err error) {\n\trootIDs := treenode.GetRootBlockIDsByBoxID(boxID)\n\tif 1 > len(rootIDs) {\n\t\treturn map[string]map[string]struct{}{}, nil\n\t}\n\tboundAVIDs, err := sql.QueryBoundBlockAVIDsInBox(nil, rootIDs, boxID)\n\tif nil != err {\n\t\treturn nil, err\n\t}\n\treturn groupDeletedAttributeViewBlocks(boundAVIDs), nil\n}\n\nfunc RemoveBox(boxID string) (err error) {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn errors.New(\"invalid notebook ID\")\n\t}\n\tif _, loaded := boxLock.LoadOrStore(boxID, true); loaded {\n\t\terr = errors.New(Conf.language(239))\n\t\treturn\n\t}\n\tdefer boxLock.Delete(boxID)\n\n\tif util.IsReservedFilename(boxID) {\n\t\treturn fmt.Errorf(\"can not remove [%s] caused by it is a reserved file\", boxID)\n\t}\n\n\tFlushTxQueue()\n\tsql.FlushQueue()\n\t// 索引和笔记本目录删除后无法再读取 custom-avs，需提前收集；实际删除成功后再清理绑定行。\n\tdeletedAttrViewBlockIDs, err := collectBoxDeletedAttributeViewBlocks(boxID)\n\tif nil != err {\n\t\treturn fmt.Errorf(\"query database-bound blocks in notebook [%s] failed: %w\", boxID, err)\n\t}\n\tisUserGuide := IsUserGuide(boxID)\n\tlocalPath := filepath.Join(util.DataDir, boxID)\n\tif !filelock.IsExist(localPath) {","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/mount.go#L173-L209","documentation":"Returned by model.RemoveBox when the per-notebook boxLock is already held (sync.Map LoadOrStore succeeds with loaded=true), meaning another mount/unmount/remove/reindex operation on the same notebook is in flight. Localized message Conf.language(239): \"Related operations are being processed, please try again later\". This is a transient busy signal, not a permanent failure.","triggerScenarios":"Concurrent API calls targeting the same notebook: removeNotebook while openNotebook/closeNotebook or a reindex is still running; rapid double-clicks in the UI firing two remove requests; automation retrying removal before the first attempt finishes.","commonSituations":"UI race when a user clicks delete immediately after opening/closing a notebook; scripts that fire mount and remove in parallel for the same box; slow disks making the first operation long enough to overlap the second.","solutions":["Retry the removeNotebook call after the in-flight operation completes (short delay + bounded retries)","Serialize operations per notebook ID in your client instead of issuing them concurrently","Check for running tasks (e.g. /api/queryProgress or UI spinners) before issuing remove"],"exampleFix":"// before\nerr := model.RemoveBox(boxID) // may hit busy lock 239\n\n// after\nvar err error\nfor i := 0; i < 5; i++ {\n    err = model.RemoveBox(boxID)\n    if err == nil || !strings.Contains(err.Error(), Conf.Language(239)) {\n        break\n    }\n    time.Sleep(500 * time.Millisecond)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var err error\nfor attempt := 0; attempt < 5; attempt++ {\n    err = model.RemoveBox(boxID)\n    if err == nil {\n        break\n    }\n    if err.Error() != Conf.Language(239) { // busy lock is the only retryable case\n        break\n    }\n    time.Sleep(time.Duration(attempt+1) * 500 * time.Millisecond)\n}","preventionTips":["Serialize notebook operations per boxID in your client with a per-key mutex","Debounce duplicate UI clicks that fire concurrent remove/mount requests","Retry only the busy (239) error; all other failures are permanent for that attempt"],"tags":["notebook","concurrency","lock","transient","siyuan"],"backgroundTag":"operation-in-progress-retry","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}