siyuan-note/siyuan · error

truncate file [%s] failed: %s

Error message

truncate file [%s] failed: %s

What it means

WriteFileByMmap writes a file by truncating it to the incoming data length, then memory-mapping and copying. This error fires when the f.Truncate call fails, aborting the write before any data is copied, and is raised for attribute-view saves and tree writes.

Source

Thrown at kernel/util/mmap.go:47

// WriteFileByMmap 使用内存映射将 data 原地覆写到 filePath。
//
// 流程:OpenFile(O_RDWR|O_CREATE) → Truncate 到精确长度 → mmap.Map(RDWR) →
// copy 写入 → Flush → Unmap,全程持有 filelock 的进程内互斥锁,避免并发写冲突。
//
// 相比 filelock.WriteFile(临时文件 + rename + fsync),此路径在进程级 I/O
// 计数(IO Write Bytes)上几乎不计——copy 是纯内存写,不经过 I/O 子系统,
// 只有 Flush 会产生极少量计入。出错时由调用方回退到 filelock.WriteFile。
func WriteFileByMmap(filePath string, data []byte) (err error) {
	f, err := filelock.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return
	}
	defer filelock.CloseFile(f)

	if err = f.Truncate(int64(len(data))); err != nil {
		msg := fmt.Sprintf("truncate file [%s] failed: %s", filePath, err)
		logging.LogError(msg)
		err = errors.New(msg)
		return
	}

	m, err := mmap.Map(f, mmap.RDWR, 0)
	if err != nil {
		msg := fmt.Sprintf("map file [%s] failed: %s", filePath, err)
		logging.LogError(msg)
		err = errors.New(msg)
		return
	}
	defer m.Unmap()

	copy(m, data)
	if err = m.Flush(); err != nil {
		msg := fmt.Sprintf("flush data [%s] failed: %s", filePath, err)
		logging.LogError(msg)
		err = errors.New(msg)
		return

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Read the embedded OS error (%s) to identify the cause (sharing violation, read-only fs, disk full)
  2. Close other processes locking the file (sync clients, editors, antivirus scans) and retry the save
  3. Check disk free space and that the workspace volume is writable
  4. Retry the operation — SiYuan will fall back to normal write paths in some callers

Example fix

// before
err = WriteFileByMmap(absPath, data) // fails: file locked
// after
filelock.Release(absPath) // or close the external lock holder
err = WriteFileByMmap(absPath, data)
Defensive patterns

Strategy: retry

Validate before calling

// before writing, ensure path is writable and not locked
if _, err := os.Stat(filePath); err != nil { return err }
if f, err := os.OpenFile(filePath, os.O_RDWR, 0644); err != nil { return err } else { f.Close() }

Try / catch

if err := util.WriteFileByMmap(path, data); err != nil {
    log.Printf("mmap write failed: %v", err)
    time.Sleep(100*time.Millisecond)
    err = util.WriteFileByMmap(path, data) // retry once after transient lock
}

Prevention

When it happens

Trigger: Calling WriteFileByMmap (via SaveAttributeView or WriteTree) on a path where the OS refuses truncate: file locked by another process, read-only filesystem, disk full semantics on some platforms, or an invalid/disappearing file handle.

Common situations: Sync or indexing processes holding the .sy/.av file open on Windows; workspace on a read-only or full disk; file removed between open and truncate; antivirus locking data files.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/b17997e11a480548. Report an issue: GitHub.