{"record":{"id":"f2f56be850784329","repo":"siyuan-note/siyuan","slug":"239","errorCode":"239","errorMessage":"Related operations are being processed, please try again later","messagePattern":"Related operations are being processed, please try again later","errorType":"error_code","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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/mount.go#L173-L209","documentation":"RemoveBox uses a per-notebook sync.Map (boxLock) via LoadOrStore to serialise destructive operations on the same notebook; if an entry already exists for boxID it returns errors.New(Conf.language(239)) = 'Related operations are being processed, please try again later'. The entry is deleted on return (defer boxLock.Delete), so the lock is held only for the duration of one RemoveBox call.","triggerScenarios":"Two concurrent RemoveBox calls (or another operation that acquires boxLock for the same boxID) overlap: the second sees the stored entry and fails immediately. Because the lock is per-boxID, only same-notebook concurrency is affected; different notebooks proceed in parallel.","commonSituations":"Double-click on a remove button triggering two API calls; a retry storm from a flaky client; an automation that issues duplicate removal requests; a UI that does not disable the button while a request is in flight.","solutions":["Retry after a short delay (the lock is released when the in-flight RemoveBox returns), or better, deduplicate in the caller.","Disable the remove affordance in the UI while a request is pending to prevent duplicate submissions.","For automations, guard with an in-app mutex keyed by boxID so only one removal is issued.","Treat code 239 as transient: surface 'please try again' rather than a hard failure."],"exampleFix":"// before: concurrent duplicate removal\nmodel.RemoveBox(boxID) // goroutine A\nmodel.RemoveBox(boxID) // goroutine B -> Language(239)\n\n// after: serialise per-boxID in the caller\nboxMu.Lock(boxID); defer boxMu.Unlock(boxID)\nmodel.RemoveBox(boxID)","handlingStrategy":"retry","validationCode":"// Deduplicate removal requests per boxID in the caller (in-process mutex).\nvar removeMu sync.Map\nfunc removeOnce(boxID string) error {\n    if _, loaded := removeMu.LoadOrStore(boxID, true); loaded {\n        return errors.New(\"already in progress\")\n    }\n    defer removeMu.Delete(boxID)\n    return model.RemoveBox(boxID)\n}","typeGuard":"null","tryCatchPattern":"// Treat 239 as transient: back off and retry a few times.\nfor i := 0; i < 3; i++ {\n    err := model.RemoveBox(boxID)\n    if err == nil || !isBusyErr(err) { return err }\n    time.Sleep(time.Duration(100<<i) * time.Millisecond)\n}","preventionTips":["Disable the remove button while a request is pending.","Deduplicate duplicate API calls client-side.","Serialise destructive ops per notebook with a caller-side mutex."],"tags":["notebook","mount","remove","concurrency","lock"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}