Mercurial
diff third_party/libuv/test/test-fork.c @ 160:948de3f54cea
[ThirdParty] Added libuv
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Wed, 14 Jan 2026 19:39:52 -0800 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/third_party/libuv/test/test-fork.c Wed Jan 14 19:39:52 2026 -0800 @@ -0,0 +1,769 @@ +/* Copyright libuv project contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/* These tests are Unix only. */ +#ifndef _WIN32 + +#include <unistd.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <string.h> + +#ifdef __APPLE__ +#include <TargetConditionals.h> +#endif + +#include "uv.h" +#include "task.h" + +static int timer_cb_called; +static int socket_cb_called; + +static void timer_cb(uv_timer_t* timer) { + timer_cb_called++; + uv_close((uv_handle_t*) timer, NULL); +} + + +static int socket_cb_read_fd; +static int socket_cb_read_size; +static char socket_cb_read_buf[1024]; + + +static void socket_cb(uv_poll_t* poll, int status, int events) { + ssize_t cnt; + socket_cb_called++; + ASSERT_OK(status); + printf("Socket cb got events %d\n", events); + ASSERT_EQ(UV_READABLE, (events & UV_READABLE)); + if (socket_cb_read_fd) { + cnt = read(socket_cb_read_fd, socket_cb_read_buf, socket_cb_read_size); + ASSERT_EQ(cnt, socket_cb_read_size); + } + uv_close((uv_handle_t*) poll, NULL); +} + + +static void run_timer_loop_once(void) { + uv_loop_t loop; + uv_timer_t timer_handle; + + ASSERT_OK(uv_loop_init(&loop)); + + timer_cb_called = 0; /* Reset for the child. */ + + ASSERT_OK(uv_timer_init(&loop, &timer_handle)); + ASSERT_OK(uv_timer_start(&timer_handle, timer_cb, 1, 0)); + ASSERT_OK(uv_run(&loop, UV_RUN_DEFAULT)); + ASSERT_EQ(1, timer_cb_called); + ASSERT_OK(uv_loop_close(&loop)); +} + + +static void assert_wait_child(pid_t child_pid) { + pid_t waited_pid; + int child_stat; + + waited_pid = waitpid(child_pid, &child_stat, 0); + printf("Waited pid is %d with status %d\n", waited_pid, child_stat); + if (waited_pid == -1) { + perror("Failed to wait"); + } + ASSERT_EQ(child_pid, waited_pid); + ASSERT(WIFEXITED(child_stat)); /* Clean exit, not a signal. */ + ASSERT(!WIFSIGNALED(child_stat)); + ASSERT_OK(WEXITSTATUS(child_stat)); +} + + +TEST_IMPL(fork_timer) { + /* Timers continue to work after we fork. */ + + /* + * Establish the loop before we fork to make sure that it + * has state to get reset after the fork. + */ + pid_t child_pid; + + run_timer_loop_once(); +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* parent */ + assert_wait_child(child_pid); + } else { + /* child */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + run_timer_loop_once(); + } + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + + +TEST_IMPL(fork_socketpair) { + /* A socket opened in the parent and accept'd in the + child works after a fork. */ + pid_t child_pid; + int socket_fds[2]; + uv_poll_t poll_handle; + + /* Prime the loop. */ + run_timer_loop_once(); + + ASSERT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds)); + + /* Create the server watcher in the parent, use it in the child. */ + ASSERT_OK(uv_poll_init(uv_default_loop(), &poll_handle, socket_fds[0])); + +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* parent */ + ASSERT_EQ(3, send(socket_fds[1], "hi\n", 3, 0)); + assert_wait_child(child_pid); + } else { + /* child */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + ASSERT_OK(socket_cb_called); + ASSERT_OK(uv_poll_start(&poll_handle, UV_READABLE, socket_cb)); + printf("Going to run the loop in the child\n"); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(1, socket_cb_called); + } + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + + +TEST_IMPL(fork_socketpair_started) { + /* A socket opened in the parent and accept'd in the + child works after a fork, even if the watcher was already + started, and then stopped in the parent. */ + pid_t child_pid; + int socket_fds[2]; + int sync_pipe[2]; + char sync_buf[1]; + uv_poll_t poll_handle; + + ASSERT_OK(pipe(sync_pipe)); + + /* Prime the loop. */ + run_timer_loop_once(); + + ASSERT_OK(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds)); + + /* Create and start the server watcher in the parent, use it in the child. */ + ASSERT_OK(uv_poll_init(uv_default_loop(), &poll_handle, socket_fds[0])); + ASSERT_OK(uv_poll_start(&poll_handle, UV_READABLE, socket_cb)); + + /* Run the loop AFTER the poll watcher is registered to make sure it + gets passed to the kernel. Use NOWAIT and expect a non-zero + return to prove the poll watcher is active. + */ + ASSERT_EQ(1, uv_run(uv_default_loop(), UV_RUN_NOWAIT)); + +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* parent */ + ASSERT_OK(uv_poll_stop(&poll_handle)); + uv_close((uv_handle_t*)&poll_handle, NULL); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_OK(socket_cb_called); + ASSERT_EQ(1, write(sync_pipe[1], "1", 1)); /* alert child */ + ASSERT_EQ(3, send(socket_fds[1], "hi\n", 3, 0)); + + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_OK(socket_cb_called); + + assert_wait_child(child_pid); + } else { + /* child */ + printf("Child is %d\n", getpid()); + ASSERT_EQ(1, read(sync_pipe[0], sync_buf, 1)); /* wait for parent */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + ASSERT_OK(socket_cb_called); + + printf("Going to run the loop in the child\n"); + socket_cb_read_fd = socket_fds[0]; + socket_cb_read_size = 3; + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT_EQ(1, socket_cb_called); + printf("Buf %s\n", socket_cb_read_buf); + ASSERT_OK(strcmp("hi\n", socket_cb_read_buf)); + } + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + + +static int fork_signal_cb_called; + +void fork_signal_to_child_cb(uv_signal_t* handle, int signum) +{ + fork_signal_cb_called = signum; + uv_close((uv_handle_t*)handle, NULL); +} + + +TEST_IMPL(fork_signal_to_child) { + /* A signal handler installed before forking + is run only in the child when the child is signalled. */ + uv_signal_t signal_handle; + pid_t child_pid; + int sync_pipe[2]; + char sync_buf[1]; + + fork_signal_cb_called = 0; /* reset */ + + ASSERT_OK(pipe(sync_pipe)); + + /* Prime the loop. */ + run_timer_loop_once(); + + ASSERT_OK(uv_signal_init(uv_default_loop(), &signal_handle)); + ASSERT_OK(uv_signal_start(&signal_handle, + fork_signal_to_child_cb, + SIGUSR1)); + +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* parent */ + ASSERT_EQ(1, read(sync_pipe[0], sync_buf, 1)); /* wait for child */ + ASSERT_OK(kill(child_pid, SIGUSR1)); + /* Run the loop, make sure we don't get the signal. */ + printf("Running loop in parent\n"); + uv_unref((uv_handle_t*)&signal_handle); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_NOWAIT)); + ASSERT_OK(fork_signal_cb_called); + printf("Waiting for child in parent\n"); + assert_wait_child(child_pid); + } else { + /* child */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + ASSERT_EQ(1, write(sync_pipe[1], "1", 1)); /* alert parent */ + /* Get the signal. */ + ASSERT_NE(0, uv_loop_alive(uv_default_loop())); + printf("Running loop in child\n"); + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_ONCE)); + ASSERT_EQ(SIGUSR1, fork_signal_cb_called); + } + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + + +TEST_IMPL(fork_signal_to_child_closed) { + /* A signal handler installed before forking + doesn't get received anywhere when the child is signalled, + but isnt running the loop. */ + uv_signal_t signal_handle; + pid_t child_pid; + int sync_pipe[2]; + int sync_pipe2[2]; + char sync_buf[1]; + int r; + + fork_signal_cb_called = 0; /* reset */ + + ASSERT_OK(pipe(sync_pipe)); + ASSERT_OK(pipe(sync_pipe2)); + + /* Prime the loop. */ + run_timer_loop_once(); + + ASSERT_OK(uv_signal_init(uv_default_loop(), &signal_handle)); + ASSERT_OK(uv_signal_start(&signal_handle, + fork_signal_to_child_cb, + SIGUSR1)); + +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* parent */ + printf("Wating on child in parent\n"); + ASSERT_EQ(1, read(sync_pipe[0], sync_buf, 1)); /* wait for child */ + printf("Parent killing child\n"); + ASSERT_OK(kill(child_pid, SIGUSR1)); + /* Run the loop, make sure we don't get the signal. */ + printf("Running loop in parent\n"); + uv_unref((uv_handle_t*)&signal_handle); /* so the loop can exit; + we *shouldn't* get any signals */ + run_timer_loop_once(); /* but while we share a pipe, we do, so + have something active. */ + ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_ONCE)); + printf("Signal in parent %d\n", fork_signal_cb_called); + ASSERT_OK(fork_signal_cb_called); + ASSERT_EQ(1, write(sync_pipe2[1], "1", 1)); /* alert child */ + printf("Waiting for child in parent\n"); + assert_wait_child(child_pid); + } else { + /* Child. Our signal handler should still be installed. */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + printf("Checking loop in child\n"); + ASSERT_NE(0, uv_loop_alive(uv_default_loop())); + printf("Alerting parent in child\n"); + ASSERT_EQ(1, write(sync_pipe[1], "1", 1)); /* alert parent */ + /* Don't run the loop. Wait for the parent to call us */ + printf("Waiting on parent in child\n"); + /* Wait for parent. read may fail if the parent tripped an ASSERT + and exited, so this ASSERT is generous. + */ + r = read(sync_pipe2[0], sync_buf, 1); + ASSERT(-1 <= r && r <= 1); + ASSERT_OK(fork_signal_cb_called); + printf("Exiting child \n"); + /* Note that we're deliberately not running the loop + * in the child, and also not closing the loop's handles, + * so the child default loop can't be cleanly closed. + * We need to explicitly exit to avoid an automatic failure + * in that case. + */ + exit(0); + } + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} + +static void fork_signal_cb(uv_signal_t* h, int s) { + fork_signal_cb_called = s; +} +static void empty_close_cb(uv_handle_t* h){} + +TEST_IMPL(fork_close_signal_in_child) { + uv_loop_t loop; + uv_signal_t signal_handle; + pid_t child_pid; + + ASSERT_OK(uv_loop_init(&loop)); + ASSERT_OK(uv_signal_init(&loop, &signal_handle)); + ASSERT_OK(uv_signal_start(&signal_handle, &fork_signal_cb, SIGHUP)); + + ASSERT_OK(kill(getpid(), SIGHUP)); + child_pid = fork(); + ASSERT_NE(child_pid, -1); + ASSERT_OK(fork_signal_cb_called); + + if (!child_pid) { + uv_loop_fork(&loop); + uv_close((uv_handle_t*)&signal_handle, &empty_close_cb); + uv_run(&loop, UV_RUN_DEFAULT); + /* Child doesn't receive the signal */ + ASSERT_OK(fork_signal_cb_called); + } else { + /* Parent. Runing once to receive the signal */ + uv_run(&loop, UV_RUN_ONCE); + ASSERT_EQ(SIGHUP, fork_signal_cb_called); + + /* loop should stop after closing the only handle */ + uv_close((uv_handle_t*)&signal_handle, &empty_close_cb); + ASSERT_OK(uv_run(&loop, UV_RUN_DEFAULT)); + + assert_wait_child(child_pid); + } + + MAKE_VALGRIND_HAPPY(&loop); + return 0; +} + + +static void create_file(const char* name) { + int r; + uv_file file; + uv_fs_t req; + + r = uv_fs_open(NULL, &req, name, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR, NULL); + ASSERT_GE(r, 0); + file = r; + uv_fs_req_cleanup(&req); + r = uv_fs_close(NULL, &req, file, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); +} + + +static void touch_file(const char* name) { + int r; + uv_file file; + uv_fs_t req; + uv_buf_t buf; + + r = uv_fs_open(NULL, &req, name, O_RDWR, 0, NULL); + ASSERT_GE(r, 0); + file = r; + uv_fs_req_cleanup(&req); + + buf = uv_buf_init("foo", 4); + r = uv_fs_write(NULL, &req, file, &buf, 1, -1, NULL); + ASSERT_GE(r, 0); + uv_fs_req_cleanup(&req); + + r = uv_fs_close(NULL, &req, file, NULL); + ASSERT_OK(r); + uv_fs_req_cleanup(&req); +} + + +static int timer_cb_touch_called; + +static void timer_cb_touch(uv_timer_t* timer) { + uv_close((uv_handle_t*)timer, NULL); + touch_file("watch_file"); + timer_cb_touch_called++; +} + + +static int fs_event_cb_called; + +static void fs_event_cb_file_current_dir(uv_fs_event_t* handle, + const char* filename, + int events, + int status) { + ASSERT_OK(fs_event_cb_called); + ++fs_event_cb_called; + ASSERT_OK(status); +#if defined(__APPLE__) || defined(__linux__) + ASSERT_OK(strcmp(filename, "watch_file")); +#else + ASSERT(filename == NULL || strcmp(filename, "watch_file") == 0); +#endif + uv_close((uv_handle_t*)handle, NULL); +} + + +static void assert_watch_file_current_dir(uv_loop_t* const loop, int file_or_dir) { + uv_timer_t timer; + uv_fs_event_t fs_event; + int r; + + /* Setup */ + remove("watch_file"); + create_file("watch_file"); + + r = uv_fs_event_init(loop, &fs_event); + ASSERT_OK(r); + /* watching a dir is the only way to get fsevents involved on apple + platforms */ + r = uv_fs_event_start(&fs_event, + fs_event_cb_file_current_dir, + file_or_dir == 1 ? "." : "watch_file", + 0); + ASSERT_OK(r); + + r = uv_timer_init(loop, &timer); + ASSERT_OK(r); + + r = uv_timer_start(&timer, timer_cb_touch, 100, 0); + ASSERT_OK(r); + + ASSERT_OK(timer_cb_touch_called); + ASSERT_OK(fs_event_cb_called); + + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT_EQ(1, timer_cb_touch_called); + ASSERT_EQ(1, fs_event_cb_called); + + /* Cleanup */ + remove("watch_file"); + fs_event_cb_called = 0; + timer_cb_touch_called = 0; + uv_run(loop, UV_RUN_DEFAULT); /* flush pending closes */ +} + + +#define FS_TEST_FILE 0 +#define FS_TEST_DIR 1 + +static int _do_fork_fs_events_child(int file_or_dir) { + /* basic fsevents work in the child after a fork */ + pid_t child_pid; + uv_loop_t loop; + + /* Watch in the parent, prime the loop and/or threads. */ + assert_watch_file_current_dir(uv_default_loop(), file_or_dir); +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* parent */ + assert_wait_child(child_pid); + } else { + /* child */ + /* Ee can watch in a new loop, but dirs only work + if we're on linux. */ +#if defined(__APPLE__) + file_or_dir = FS_TEST_FILE; +#endif + printf("Running child\n"); + uv_loop_init(&loop); + printf("Child first watch\n"); + assert_watch_file_current_dir(&loop, file_or_dir); + ASSERT_OK(uv_loop_close(&loop)); + printf("Child second watch default loop\n"); + /* Ee can watch in the default loop. */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + /* On some platforms (OS X), if we don't update the time now, + * the timer cb fires before the event loop enters uv__io_poll, + * instead of after, meaning we don't see the change! This may be + * a general race. + */ + uv_update_time(uv_default_loop()); + assert_watch_file_current_dir(uv_default_loop(), file_or_dir); + + /* We can close the parent loop successfully too. This is + especially important on Apple platforms where if we're not + careful trying to touch the CFRunLoop, even just to shut it + down, that we allocated in the FS_TEST_DIR case would crash. */ + ASSERT_OK(uv_loop_close(uv_default_loop())); + + printf("Exiting child \n"); + } + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; + +} + + +TEST_IMPL(fork_fs_events_child) { +#if defined(NO_FS_EVENTS) + RETURN_SKIP(NO_FS_EVENTS); +#endif + return _do_fork_fs_events_child(FS_TEST_FILE); +} + + +TEST_IMPL(fork_fs_events_child_dir) { +#if defined(NO_FS_EVENTS) + RETURN_SKIP(NO_FS_EVENTS); +#endif +#if defined(__APPLE__) || defined (__linux__) + return _do_fork_fs_events_child(FS_TEST_DIR); +#else + /* You can't spin up a cfrunloop thread on an apple platform + and then fork. See + http://objectivistc.tumblr.com/post/16187948939/you-must-exec-a-core-foundation-fork-safety-tale + */ + return 0; +#endif +} + + +TEST_IMPL(fork_fs_events_file_parent_child) { +#if defined(NO_FS_EVENTS) + RETURN_SKIP(NO_FS_EVENTS); +#endif +#if defined(__sun) || defined(_AIX) || defined(__MVS__) + /* It's not possible to implement this without additional + * bookkeeping on SunOS. For AIX it is possible, but has to be + * written. See https://github.com/libuv/libuv/pull/846#issuecomment-287170420 + * TODO: On z/OS, we need to open another message queue and subscribe to the + * same events as the parent. + */ + return 0; +#else + /* Establishing a started fs events watcher in the parent should + still work in the child. */ + uv_timer_t timer; + uv_fs_event_t fs_event; + int r; + pid_t child_pid; + uv_loop_t* loop; + + loop = uv_default_loop(); + + /* Setup */ + remove("watch_file"); + create_file("watch_file"); + + r = uv_fs_event_init(loop, &fs_event); + ASSERT_OK(r); + r = uv_fs_event_start(&fs_event, + fs_event_cb_file_current_dir, + "watch_file", + 0); + ASSERT_OK(r); + + r = uv_timer_init(loop, &timer); + ASSERT_OK(r); + +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + if (child_pid != 0) { + /* parent */ + assert_wait_child(child_pid); + } else { + /* child */ + printf("Running child\n"); + ASSERT_OK(uv_loop_fork(loop)); + + r = uv_timer_start(&timer, timer_cb_touch, 100, 0); + ASSERT_OK(r); + + ASSERT_OK(timer_cb_touch_called); + ASSERT_OK(fs_event_cb_called); + printf("Running loop in child \n"); + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT_EQ(1, timer_cb_touch_called); + ASSERT_EQ(1, fs_event_cb_called); + + /* Cleanup */ + remove("watch_file"); + fs_event_cb_called = 0; + timer_cb_touch_called = 0; + uv_run(loop, UV_RUN_DEFAULT); /* Flush pending closes. */ + } + + + MAKE_VALGRIND_HAPPY(loop); + return 0; +#endif +} + + +static int work_cb_count; +static int after_work_cb_count; + + +static void work_cb(uv_work_t* req) { + work_cb_count++; +} + + +static void after_work_cb(uv_work_t* req, int status) { + ASSERT_OK(status); + after_work_cb_count++; +} + + +static void assert_run_work(uv_loop_t* const loop) { + uv_work_t work_req; + int r; + + ASSERT_OK(work_cb_count); + ASSERT_OK(after_work_cb_count); + printf("Queue in %d\n", getpid()); + r = uv_queue_work(loop, &work_req, work_cb, after_work_cb); + ASSERT_OK(r); + printf("Running in %d\n", getpid()); + uv_run(loop, UV_RUN_DEFAULT); + + ASSERT_EQ(1, work_cb_count); + ASSERT_EQ(1, after_work_cb_count); + + /* cleanup */ + work_cb_count = 0; + after_work_cb_count = 0; +} + + +#ifndef __MVS__ +TEST_IMPL(fork_threadpool_queue_work_simple) { + /* The threadpool works in a child process. */ + + pid_t child_pid; + uv_loop_t loop; + +#ifdef __TSAN__ + RETURN_SKIP("ThreadSanitizer doesn't support multi-threaded fork"); +#endif + + /* Prime the pool and default loop. */ + assert_run_work(uv_default_loop()); + +#if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) + child_pid = -1; +#else + child_pid = fork(); +#endif + ASSERT_NE(child_pid, -1); + + if (child_pid != 0) { + /* Parent. We can still run work. */ + assert_run_work(uv_default_loop()); + assert_wait_child(child_pid); + } else { + /* Child. We can work in a new loop. */ + printf("Running child in %d\n", getpid()); + uv_loop_init(&loop); + printf("Child first watch\n"); + assert_run_work(&loop); + uv_loop_close(&loop); + printf("Child second watch default loop\n"); + /* We can work in the default loop. */ + ASSERT_OK(uv_loop_fork(uv_default_loop())); + assert_run_work(uv_default_loop()); + printf("Exiting child \n"); + } + + + MAKE_VALGRIND_HAPPY(uv_default_loop()); + return 0; +} +#endif /* !__MVS__ */ + +#else + +typedef int file_has_no_tests; /* ISO C forbids an empty translation unit. */ + +#endif /* !_WIN32 */