siyuan-note/siyuan · warning

239

239

Error message

Conf.language(239)

What it means

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.

Solutions

  1. Retry the removeNotebook call after the in-flight operation completes (short delay + bounded retries)
  2. Serialize operations per notebook ID in your client instead of issuing them concurrently
  3. Check for running tasks (e.g. /api/queryProgress or UI spinners) before issuing remove

Example fix

// before
err := model.RemoveBox(boxID) // may hit busy lock 239

// after
var err error
for i := 0; i < 5; i++ {
    err = model.RemoveBox(boxID)
    if err == nil || !strings.Contains(err.Error(), Conf.Language(239)) {
        break
    }
    time.Sleep(500 * time.Millisecond)
}
Defensive patterns

Strategy: retry

Try / catch

var err error
for attempt := 0; attempt < 5; attempt++ {
    err = model.RemoveBox(boxID)
    if err == nil {
        break
    }
    if err.Error() != Conf.Language(239) { // busy lock is the only retryable case
        break
    }
    time.Sleep(time.Duration(attempt+1) * 500 * time.Millisecond)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/f2f56be850784329. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/mount.go:191

func collectBoxDeletedAttributeViewBlocks(boxID string) (ret map[string]map[string]struct{}, err error) {
	rootIDs := treenode.GetRootBlockIDsByBoxID(boxID)
	if 1 > len(rootIDs) {
		return map[string]map[string]struct{}{}, nil
	}
	boundAVIDs, err := sql.QueryBoundBlockAVIDsInBox(nil, rootIDs, boxID)
	if nil != err {
		return nil, err
	}
	return groupDeletedAttributeViewBlocks(boundAVIDs), nil
}

func RemoveBox(boxID string) (err error) {
	if !ast.IsNodeIDPattern(boxID) {
		return errors.New("invalid notebook ID")
	}
	if _, loaded := boxLock.LoadOrStore(boxID, true); loaded {
		err = errors.New(Conf.language(239))
		return
	}
	defer boxLock.Delete(boxID)

	if util.IsReservedFilename(boxID) {
		return fmt.Errorf("can not remove [%s] caused by it is a reserved file", boxID)
	}

	FlushTxQueue()
	sql.FlushQueue()
	// 索引和笔记本目录删除后无法再读取 custom-avs,需提前收集;实际删除成功后再清理绑定行。
	deletedAttrViewBlockIDs, err := collectBoxDeletedAttributeViewBlocks(boxID)
	if nil != err {
		return fmt.Errorf("query database-bound blocks in notebook [%s] failed: %w", boxID, err)
	}
	isUserGuide := IsUserGuide(boxID)
	localPath := filepath.Join(util.DataDir, boxID)
	if !filelock.IsExist(localPath) {

View on GitHub (pinned to afa823b6b4)