Comments (18)
For reference, I narrowed this example down from CMake Issue 25562. CMake uses libuv to run child processes, and works on many platforms, but hangs in libuv on this one.
from libuv.
If you run the program with strace -f
. Does the output provide any hints?
from libuv.
I just ran the program under strace -f
. The hang does not occur and all 10000 iterations complete. Then I ran the process by itself first, waited for it to hang, and used strace -f -p $PID 2>log
to attach to it. That causes the process to resume running. If I Ctrl-C the strace
process then the test program eventually hangs again. Attaching strace
wakes it up again. Each time the log
file shows the following at the top:
strace: Process 59 attached
syscall_0xf0003ffff128(0xf0003ffff0c0, 0xf0003ffff0c0, 0xf0003ffff110, 0, 0x8000ffff782f2d90, 0xd7) = 0x8000ffff782f2fe4
syscall_0xf0003ffff128(0xf0003ffff0c0, 0, 0xf0003ffff110, 0, 0x800000181ed0, 0x87) = 0x8000001820f0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=..., si_uid=0, si_status=0, si_utime=1, si_stime=0} ---
syscall_0xf0003ffff128(0xf0003ffff0c0, 0, 0x6, 0, 0x8000ffff782f2d90, 0xde) = 0x8000ffff782f2fe4
read(0, NULL, 0) = 140737489936624
[ Process PID=59 runs in x32 mode. ]
syscall_0x5554ffffbf40(0x4, 0x5554ffffa94f, 0x11, 0, 0x2, 0x3f) = 0xffffffff
syscall_0x5554fffff4b8(0x4, 0x5554ffffa94f, 0x11, 0, 0x2, 0x40) = 0xffffffff
syscall_0x5554fffff4b8(0x4, 0x5554ffffa94f, 0x11, 0, 0x2, 0x40) = 0xffffffff
[ Process PID=59 runs in 64 bit mode. ]
read(0, 0xc000, 3) = 9223653509552877540
read(0, NULL, 3) = 4294967295
These lines are identical every time except for the si_pid
value since a different child exits.
from libuv.
[ Process PID=59 runs in x32 mode. ]
...
[ Process PID=59 runs in 64 bit mode. ]
That's... unusual. strace is saying the process's personality changed between syscalls.
It checks bit 30 in the syscall number (i.e. rax & 0x40000000
) to distinguish between x86_64 and x32 syscalls, so maybe it gets confused because all the other values are bogus-looking too.
I can't really make heads or tails of that trace. Can you perhaps trying dumping core or attaching with gdb when the process hangs and a) inspect registers, and b) obtain a backtrace?
from libuv.
I think strace's heuristics are getting confused. It prints the x32/64 bit messages repeatedly. It's also printing most of the syscalls in raw form.
gdb doesn't work well in the container either. I found some posts about using ROSETTA_DEBUGSERVER_PORT to run a process in a gdbserver and debug it from another terminal, but that doesn't actually run the process until a remote debugger attaches to the session. When I do that and run the "continue" step in gdb the process hangs immediately in epoll_pwait
:
gdb Immediate Hang Backtrace (click to expand)
(gdb) where
#0 0x00007fffffebcb10 in ?? ()
#1 0x0000555555574130 in uv__io_poll (loop=0x5554fffff1d0, timeout=-1) at /root/libuv/src/unix/linux.c:1430
#2 0x000055555555eb95 in uv_run (loop=0x5554fffff1d0, mode=UV_RUN_DEFAULT) at /root/libuv/src/unix/core.c:447
#3 0x0000555555559dcb in run () at test-uv.c:83
#4 0x0000555555559e8c in main () at test-uv.c:99
I've not been able to get a backtrace for the real hang, but I patched libuv as follows to print more information:
debug-prints.patch (Click to expand)
diff --git a/src/unix/linux.c b/src/unix/linux.c
index 8eeb352e..64a6d351 100644
--- a/src/unix/linux.c
+++ b/src/unix/linux.c
@@ -1427,7 +1427,9 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
*/
lfields->current_timeout = timeout;
+ fprintf(stderr, "before epoll_pwait\n");
nfds = epoll_pwait(epollfd, events, ARRAY_SIZE(events), timeout, sigmask);
+ fprintf(stderr, "after epoll_pwait\n");
/* Update loop->time unconditionally. It's tempting to skip the update when
* timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the
diff --git a/src/unix/process.c b/src/unix/process.c
index dd58c18d..12b6b02c 100644
--- a/src/unix/process.c
+++ b/src/unix/process.c
@@ -76,6 +76,7 @@ extern char **environ;
#ifdef UV_USE_SIGCHLD
static void uv__chld(uv_signal_t* handle, int signum) {
+ write(2, "c\n", 2);
assert(signum == SIGCHLD);
uv__wait_children(handle->loop);
}
diff --git a/src/unix/signal.c b/src/unix/signal.c
index bc4206e6..46139c56 100644
--- a/src/unix/signal.c
+++ b/src/unix/signal.c
@@ -185,6 +185,8 @@ static void uv__signal_handler(int signum) {
uv_signal_t* handle;
int saved_errno;
+ write(2, "a\n", 2);
+
saved_errno = errno;
memset(&msg, 0, sizeof msg);
@@ -197,6 +199,7 @@ static void uv__signal_handler(int signum) {
handle != NULL && handle->signum == signum;
handle = RB_NEXT(uv__signal_tree_s, &uv__signal_tree, handle)) {
int r;
+ write(2, "b\n", 2);
msg.signum = signum;
msg.handle = handle;
The changes print before and after epoll_pwait
, and print a
, b
, and c
at various points of uv__signal_handler
and uv__chld
. With that the test program produces the output:
...
0006
before epoll_pwait
after epoll_pwait
echo
before epoll_pwait
after epoll_pwait
before epoll_pwait
a
b
after epoll_pwait
before epoll_pwait
after epoll_pwait
c
0007
before epoll_pwait
after epoll_pwait
echo
before epoll_pwait
after epoll_pwait
before epoll_pwait
This shows that the signal handling and process reaping works several times (omitted in ...
, the last copy is shown as 0006), but eventually the process hangs in epoll_pwait
and SIGCHLD is not received/processed. The number of iterations before it hangs seems random. Sometimes it takes a few hundred. I can wake up the hung process with strace -p $pid
, and then it processes SIGCHLD and output continues as normal:
a
b
after epoll_pwait
before epoll_pwait
after epoll_pwait
c
0008
...
Then the hang doesn't occur again until I Ctrl-C the strace
process.
from libuv.
The code to handle signals does look like there is an illegal data race here, since this variable update might need to be seq-cst (it is read outside the lock, but needs to be sequenced-with the read from the previous epoll read--but this variable is written after the pipe write, so it would appear to be a data race even with seq-cst annotations). It currently isn't even marked atomic on read or write:
Line 325 in a7cbda9
from libuv.
We should maybe consider refactoring the code so that we can share this with uv_async, since that code is correctly sequenced for use from signals, and this signal-handler code does not look correct for use even from threads:
Lines 65 to 70 in a7cbda9
from libuv.
but this variable is written after the pipe write
I assume this == handle->caught_signals? It's "protected" by a syscall-induced memory barrier but yeah, non-atomic stores can take a while to propagate to other cores so it's quite possible for events to get lost. I'll open a pull request to fix that.
handle->dispatched_signals is only accessed from the event loop thread. There should be no synchronization issues there.
from libuv.
from libuv.
Ah, I see now that yes, it has a lock exclusion around it, so non-atomic access is okay, as it only gets accessed on the main thread when the lock is released and it is not in the list of handlers, but changing the list requires that lock, and releasing the lock provides the needed memory barrier
from libuv.
I guess the trace above does seem to indicate a kernel bug, since the signal itself appears to be missing until something triggers the kernel to check for it? Although it might be a issue with the rosetta/docker emulator also? I ran the test inside an ubuntu-aarch64 vm, and did not run into issue.
from libuv.
The simplest fix might be to change to use signalfd instead (since linux 2.6.27 / glibc 2.8), which works almost identically to the libuv emulation of it. We already did this on macOS for essentially identical reasons (though kevent supports waitpid natively for fast native work, while poll/epoll seems to require some inefficient workarounds)
from libuv.
I saw a similar hang when signals are delivered while in a signal handler: https://github.com/libuv/libuv/pull/3755/files does the added test look similar to the issue you are seeing?
from libuv.
No that is a different issue. In that example, you showed there is a case where handle->dispatched_signals++
is not being counted correctly, since it is supposed to be a count of messages read from the pipe, but we incorrectly skip counting messages that were read but skipped. It seems that count should be incremented with each message read, moving the increment a bit earlier in that method. The PR got stuck because it changed a lot more than that, which obscured the necessary part of your fix.
EDIT: misread slightly the PR diff. This case seems to be an issue with the PR itself, but not an issue with v1.x code
from libuv.
Although it might be a issue with the rosetta/docker emulator also?
I'm leaning towards this. The fact that it manifests neither on native ARM or native x86 is suggestive.
Switching to signalfd won't work. Any thread can be the receiver of a signal. signalfd only works correctly when combined with pthread_sigmask.
from libuv.
Oh, yeah I see now the design of signalfd seems to be just as bad as for sigwaitinfo, which it states it is identical to (and which was the other possible option for fixing this). I assumed they had designed the handling better, to be like kevent, but alas, no. The problem with most alternative options (except kevent, which doesn't have this issue) is indeed that the signal must be blocked on all threads, as setting it to SIG_IGN globally also breaks signalfd.
I am also not certain if this is actually considered a kernel bug though, or just undefined behavior in the posix implementation on linux--I have observed that the handling of signals is such that the kernel picks a thread at random, and then waits to run the signal handler if that thread later is eligible to be scheduled and isn't blocking signals. But if that thread dies or doesn't need to be rescheduled, then the kernel may choose to delay delivery of that signal indefinitely. (I have run into this exact problem in the past because I had blocked SIGCHLD on a high-priority thread once, and it caused libuv to miss the occasional SIGCHLD notifications, because the kernel happened to pick that thread to choose to handle them and that one thread blocked them)
from libuv.
I've had a few reports of libuv hanging in uv__io_poll
on kevent
on macOS without emulation or containerization.
This sample is from CMake 3.28.1 running on an arm64 device without Rosetta emulation.
Call graph:
7710 Thread_132034 DispatchQueue_1: com.apple.main-thread (serial)
7710 start (in dyld) + 2360 [0x18a0cd0e0]
7710 main (in cmake) + 9284 [0x100f9d9a0]
7710 cmcmd::ExecuteCMakeCommand(std::vector<std::basic_string<char>> const&, std::unique_ptr<cmConsoleBuf>) (in cmake) + 8460 [0x1011b6b14]
7710 cmSystemTools::RunSingleCommand(std::vector<std::basic_string<char>> const&, std::basic_string<char>*, std::basic_string<char>*, int*, char const*, cmSystemTools::OutputOption, std::chrono::duration<double, std::ratio<1l, 1l>>, cmProcessOutput::Encoding) (in cmake) + 820 [0x100e90114]
7710 uv_run (in cmake) + 284 [0x101160a8c]
7710 uv__io_poll (in cmake) + 712 [0x101166e10]
7710 kevent (in libsystem_kernel.dylib) + 8 [0x18a40ea00]
from libuv.
That must be unrelated, since it is a different kernel and does not use signals, but yes, it sounds similar (c.f. https://gitlab.kitware.com/cmake/cmake/-/issues/25562)
from libuv.
Related Issues (20)
- test: udp_multicast_join, udp_multicast_join6 failed under loongarch64 HOT 3
- Abstract socket namespace not work for 1.48 HOT 4
- several tests ha failed HOT 4
- unix udp close crash HOT 9
- linux,udp: don't use sendmmsg for single datagrams
- bug in libuv io_uring causes incorrect event reports to epoll and busy loop HOT 14
- `-Wstringop-overread` warning with libuv v1.48.0 (`unix/tcp.c:295`) with IBM AT17.0 (GCC 13.2.1) HOT 3
- Mixed allocator usage with `realpath` HOT 2
- Test failure with 1.48.0 on OSX x86 HOT 10
- Bug in TCP keep-alive with unix socket
- MSVC compiler bug broke env_vars test in ASAN (x64 windows 2022+asan) HOT 4
- Additional Libuv Metrics HOT 2
- test: flaky fs_event_close_with_pending_event HOT 2
- _alloca Requested memory may have edge problems causing exceptions HOT 3
- multicast: The Android server(apk) doesn't always receive multicast messages from ubuntu.
- test,ci: flaky fs_event_error_reporting
- android: enable fdsan?
- Send udp broadcast to 255.255.255.255 cause permission denied error HOT 2
- macos: use absolute timeouts for condition variables HOT 1
- fs_event_watch_dir test fails on alpine 3.19 HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from libuv.