Skip to content

Commit 8fd842f

Browse files
committed
fix(client): make Windows fsyncDir0 best-effort for directory handles
FlushFileBuffers is documented for file/volume handles; on NTFS it refuses a directory handle, commonly returning ERROR_ACCESS_DENIED (or ERROR_INVALID_FUNCTION on filesystems that do not implement it), and a GENERIC_WRITE open of a directory can itself be refused by ACLs. Because fsyncDir0 failures are fatal to their callers -- SfManifest.create, SlotLock.acquire (reached whenever sf_sync_interval_millis > 0), and the SegmentManager hot-spare/trim barriers -- SF disk mode with sf_durability=periodic could hard-fail to start on Windows, a Windows-only regression on the durability path. Treat the documented "directory cannot be flushed" signatures (ERROR_ACCESS_DENIED, ERROR_INVALID_FUNCTION), and an ACL-refused GENERIC_WRITE open, as best-effort success: create/rename/unlink of directory entries are made crash consistent by NTFS metadata journaling ($LogFile). This mirrors libgit2/PostgreSQL/SQLite, which do not rely on directory fsync on Windows. Genuine I/O errors, and a missing directory, still propagate as fatal. File-data fsync (Files_fsync) is unchanged.
1 parent 0c81ad3 commit 8fd842f

1 file changed

Lines changed: 26 additions & 1 deletion

File tree

core/src/main/c/windows/files.c

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,36 @@ JNIEXPORT jint JNICALL Java_io_questdb_client_std_Files_fsyncDir0
232232
OPEN_EXISTING,
233233
FILE_FLAG_BACKUP_SEMANTICS);
234234
if (fd < 0) {
235+
// A directory can be crash-consistent yet not openable for write:
236+
// some ACL/filesystem configurations refuse a GENERIC_WRITE open of a
237+
// directory handle with ERROR_ACCESS_DENIED. Directory-entry durability
238+
// on NTFS is provided by metadata journaling ($LogFile), so degrade to
239+
// best-effort success here rather than hard-failing the SF durability
240+
// path. open_file() already recorded the error via SaveLastError(); a
241+
// genuine failure such as a missing directory (ERROR_PATH_NOT_FOUND)
242+
// still propagates as fatal.
243+
if (GetLastError() == ERROR_ACCESS_DENIED) {
244+
return 0;
245+
}
235246
return -1;
236247
}
237248
if (!FlushFileBuffers(FD_TO_HANDLE(fd))) {
238-
SaveLastError();
249+
DWORD err = GetLastError();
239250
CloseHandle(FD_TO_HANDLE(fd));
251+
// FlushFileBuffers is documented for file/volume handles; NTFS refuses
252+
// it on a directory handle (typically ERROR_ACCESS_DENIED, and
253+
// ERROR_INVALID_FUNCTION on filesystems that do not implement it).
254+
// create/rename/unlink of directory entries are made crash consistent
255+
// by NTFS metadata journaling, so treat those "not supported"
256+
// signatures as best-effort success. This mirrors libgit2/PostgreSQL/
257+
// SQLite, which do not rely on directory fsync on Windows, and keeps
258+
// the SF manifest-create, slot-lock, and trim/unlink barriers from
259+
// hard-failing. Any other error is a real I/O failure and stays fatal.
260+
if (err == ERROR_ACCESS_DENIED || err == ERROR_INVALID_FUNCTION) {
261+
return 0;
262+
}
263+
SetLastError(err);
264+
SaveLastError();
240265
return -1;
241266
}
242267
CloseHandle(FD_TO_HANDLE(fd));

0 commit comments

Comments
 (0)