BUG: make log rotation safe across Windows processes - #5303
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces cross-platform, cross-process log rotation safety by implementing a shared _SafeFileRotationMixin that uses platform-native file locking (msvcrt on Windows and fcntl on Unix) and tracks rotation state via a shared lock file. It updates the rotating file handlers to inherit from this mixin and adds comprehensive unit tests to verify the Windows fallback and rotation coordination. The feedback highlights a potential file descriptor leak in _acquire_rotation_lock if the locking mechanism raises an exception, suggesting wrapping the lock acquisition in a try...except block to ensure the file descriptor is closed.
| def _acquire_rotation_lock(self): | ||
| lock_fd = open(self._lock_path, "r+b") | ||
| if msvcrt is not None: | ||
| lock_fd.seek(0) | ||
| msvcrt.locking(lock_fd.fileno(), msvcrt.LK_LOCK, 1) | ||
| else: | ||
| fcntl.flock(lock_fd, fcntl.LOCK_EX) | ||
| return lock_fd |
There was a problem hiding this comment.
If msvcrt.locking or fcntl.flock raises an exception (for example, due to a timeout or locking conflict), the newly opened lock_fd will be leaked because the caller's try...finally block in doRollover is not entered when _acquire_rotation_lock raises an exception. To prevent file descriptor leaks, wrap the locking logic in a try...except block and close lock_fd before re-raising the exception.
| def _acquire_rotation_lock(self): | |
| lock_fd = open(self._lock_path, "r+b") | |
| if msvcrt is not None: | |
| lock_fd.seek(0) | |
| msvcrt.locking(lock_fd.fileno(), msvcrt.LK_LOCK, 1) | |
| else: | |
| fcntl.flock(lock_fd, fcntl.LOCK_EX) | |
| return lock_fd | |
| def _acquire_rotation_lock(self): | |
| lock_fd = open(self._lock_path, "r+b") | |
| try: | |
| if msvcrt is not None: | |
| lock_fd.seek(0) | |
| msvcrt.locking(lock_fd.fileno(), msvcrt.LK_LOCK, 1) | |
| else: | |
| fcntl.flock(lock_fd, fcntl.LOCK_EX) | |
| return lock_fd | |
| except Exception: | |
| lock_fd.close() | |
| raise |
|
Friendly ping - this PR makes log rotation safe across Windows processes. Would appreciate a review. Thanks! |
qinxuye
left a comment
There was a problem hiding this comment.
Please also address the unresolved Gemini review comment: _acquire_rotation_lock() must close lock_fd if msvcrt.locking() or fcntl.flock() raises.
| # process keeps writing through its existing handle. | ||
| import shutil | ||
|
|
||
| shutil.copy2(source, dest) |
There was a problem hiding this comment.
The Windows fallback can still lose log records. The rotation lock only serializes doRollover(); sibling processes continue writing normally. If another process writes after copy2() finishes but before truncate(0), that record is not in the archive and is then erased from the active file. I reproduced this deterministically with two handlers: the pre-copy record was archived, while the record written in this window disappeared from both files. Please coordinate normal writes with this copy/truncate critical section, or use a rotation strategy that does not truncate a file still shared by writers, and add a Windows concurrent-write regression test.
Summary
msvcrt.lockingon Windows while retainingfcntl.flockon POSIXFixes #5284.
Validation
xinference/deploy/test/test_log_rotation.py: 35 passedThe regression tests simulate Windows lock and rename behavior on macOS, including two handlers racing on size and midnight rotation. A Windows host end-to-end run was not available.