On Sun, 28 Dec 2025 11:52:36 -0800
Nahor <[email protected]> wrote:
> Attached is a reproducible example.
> The example just calls `fork()` then open/flock/close a directory and
> repeats (fork/open/flock/close). The forks optionally sleep then
> open/flock/close the same directory and exit.
> 
> There is no issue if either the parent or the children don't call `flock()`.
> Without sleeping, the example deadlocks immediately on my system 100%
> of the time. Killing the child allow the parent to proceed, fork the
> next child, which triggers the next deadlock.
> When sleeping, _sometimes_ one child will deadlock with the parent.
> Killing that child allows the parent and remaining children to proceed
> (and potentially trigger another deadlock). Killing the parent also
> unblocks all the children.

Thanks for the report and the test case.
I looked into the issue and found the cause. I also confirmed that
the patch attached solves the issue.

Could anyone please review the patch?

-- 
Takashi Yano <[email protected]>
From 5b0a3fac8c6f4f56626d108a2dfa9738f73ecf6b Mon Sep 17 00:00:00 2001
From: Takashi Yano <[email protected]>
Date: Sat, 3 Jan 2026 21:53:36 +0900
Subject: [PATCH] Cygwin: close: Do not lock fdtab

Otherwise, a deadlock can occur if the child process attempts to
lock a file while the parent process is closing the same file, which
is already locked. The deadlock mechanism is as follows.

When the child process attempts to lock a file, it notifies the parent
process by calling CreateRemoteThread(), which creates a remote thread
in the parent. That thread checks whether the file being locked is
currently opened in the parent. During the operation, cygheap->fdtab
is temporarily locked in order to enumerate the file descriptors.

However, if the parent process is closing the same file at that moment,
it also locks fdtab via cygheap_fdget cfd(fd, true) in __close().
If the parent acquires th fdtab lock first, it proceeds to call
del_my_locks(), which attempts to lock the inode in inode_t:get().

At this point, the inode is already locked in the child,
so the parent waits for the child to release the inode. Meanwhile,
the child is waiting to acquire the fdtab lock, which is still held
by the parent. As a result, the parent and child become deadlocked.

However, since close_all_files() and close_range() do not lock fdtab,
it should not be necessary for __close() to lock fdtab either.

This patch removes fdtab lock in __close() to resolve the issue.

Addresses: https://cygwin.com/pipermail/cygwin/2025-December/259187.html
Fixes: df63bd490a52 ("* cygheap.h (cygheap_fdmanip): New class: simplifies 
locking and retrieval of fds from cygheap->fdtab.")
Reported-by: Nahor <[email protected]>
Reviewed-by:
Signed-off-by: Takashi Yano <[email protected]>
---
 winsup/cygwin/syscalls.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/winsup/cygwin/syscalls.cc b/winsup/cygwin/syscalls.cc
index 1b1ff17b0..75287f72a 100644
--- a/winsup/cygwin/syscalls.cc
+++ b/winsup/cygwin/syscalls.cc
@@ -1702,7 +1702,7 @@ __close (int fd, int flag)
 
   pthread_testcancel ();
 
-  cygheap_fdget cfd (fd, true);
+  cygheap_fdget cfd (fd);
   if (cfd < 0)
     res = -1;
   else
-- 
2.51.0

-- 
Problem reports:      https://cygwin.com/problems.html
FAQ:                  https://cygwin.com/faq/
Documentation:        https://cygwin.com/docs.html
Unsubscribe info:     https://cygwin.com/ml/#unsubscribe-simple

Reply via email to