PartFile — part.met written on low disk space, risking truncation/corruption
Implementation¶
Landed in eMule-main commit 4b4087d (Harden part-file durability and disk-space handling):
- CemuleApp::CanWritePartMetFiles(LPCTSTR pszPath, bool bForceRefresh) added to Emule.cpp/h
- Checks GetDiskFreeSpaceEx per volume root; caches result to avoid per-file syscalls
- PartFile.cpp:1180 guards SavePartFile() with CanWritePartMetFiles before attempting write
- DownloadQueueDiskSpaceSeams.h added for testability
- Disk-full pause logic added to DownloadQueue.cpp
Summary¶
PartFile.cpp and PartFileWriteThread.cpp attempt to write .part.met files with no check for available disk space. On a drive with very little free space, the write fails mid-file, producing a partial (truncated) metadata file. eMuleAI v1.3 adds a CanWritePartMetFiles() guard that checks free space per volume before each write, with a per-volume cache to avoid repeated syscalls.
Problem¶
The existing code attempts the write unconditionally:
// PartFile.cpp — no disk space check before attempting .met write
CStdioFile file;
if (!file.Open(strTmpFile, CFile::modeCreate | CFile::modeWrite | ...)) {
...
}
// write proceeds; if disk fills mid-write: partial .met file
A .part.met that is partially written and then not properly replaced (see BUG-009) can look like a valid file to the parser but contain truncated or zero-filled data — causing incorrect progress display, wrong AICH hashes, or download restart.
During shutdown, the write thread may already be stopped. Attempting to flush buffers after the write thread is gone can cause a hang or crash (see BUG-012).
Fix¶
Part 1: Disk space guard (from eMuleAI)¶
Add to CemuleApp:
// Minimum free space required for a .met write: MAXMETSIZE bytes
bool CanWritePartMetFiles(LPCTSTR pszPath, bool bForceRefresh = false);
Internally:
- Extracts the volume root from pszPath via GetVolumeRootPath()
- Queries GetDiskFreeSpaceEx for the volume
- If free space < MAXMETSIZE: logs a warning, returns false
- Caches result per volume root (avoids syscall per-file); force-refresh after any write failure
Call at the top of SavePartFile() and in PartFileWriteThread before each write:
if (!theApp.CanWritePartMetFiles(GetTmpPath()))
return false; // skip write, log, continue
Part 2: Shutdown flush guard (see also BUG-012)¶
In CPartFile::~CPartFile():
// Only flush if the write thread is still running
const CPartFileWriteThread* pThread = theApp.m_pPartFileWriteThread;
const bool bShutdownWithoutWriteThread =
theApp.IsClosing() && (!pThread || !pThread->IsRunning());
if (!bShutdownWithoutWriteThread)
FlushBuffer(false, true);
Acceptance Criteria¶
- [x]
CanWritePartMetFiles()implemented with volume-root extraction + free-space check - [x]
SavePartFile()returnsfalseearly when disk space check fails - [x]
PartFileWriteThreadskips write when disk space check fails - [x] On failure: log line emitted ("Part.met disk-space guard blocked...")
- [x] Cache invalidated on write failure (force-refresh on next call)
- [x] No spurious write attempts during shutdown when write thread is already stopped
Current Evidence¶
CemuleApp::CanWritePartMetFiles()resolves the volume identity path, checks current free space against the effective minimum, caches per-volume decisions, and logsPart.met disk-space guard blocked metadata writes...when blocked.CPartFile::SavePartFile()returnsfalsebefore opening the temporary.part.met.backupwrite path whenCanWritePartMetFiles()rejects the volume.- The buffered data flush path checks required free space before queuing writes
to
CPartFileWriteThread; on insufficient space it throws before handing buffers to the worker. - Write failures in
SavePartFile()calltheApp.InvalidatePartMetWriteGuardCache(m_fullname)before returning. BUG-012follow-up commit75a92c2now applies the shutdown write-thread guard before the first destructor-time flush attempt.- Native coverage in
part_file_persistence.tests.cppcovers the part.met disk-space floor, cached write permission, force-refresh invalidation, and shutdown flush policy. - Latest validation:
python -m emule_workspace validatepython -m emule_workspace build app --config Debug --platform x64python -m emule_workspace test all --config Debug --platform x64