Skip to content

win,pipe: support vectored writes, fix IPC deadlock#1843

Merged
piscisaureus merged 9 commits intov1.xfrom
bert/fix
May 29, 2018
Merged

win,pipe: support vectored writes, fix IPC deadlock#1843
piscisaureus merged 9 commits intov1.xfrom
bert/fix

Conversation

@piscisaureus
Copy link
Contributor

@piscisaureus piscisaureus commented May 24, 2018

UV_REQ_FIELDS
uv_write_cb cb;
uv_stream_t* send_handle;
uv_stream_t* send_handle; /* TODO: make private and unix-only in v2.x. */

This comment was marked as off-topic.

This comment was marked as off-topic.

src/win/pipe.c Outdated
}
} else {
uv_pipe_read_error_or_eof(loop, handle, GetLastError(), buf);
/* It is possible that more bytes more bytes were read than we thought
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"more bytes more bytes"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@bzoz
Copy link
Member

bzoz commented May 24, 2018

@bzoz
Copy link
Member

bzoz commented May 24, 2018

CI is red, on Linux it complains about undefined reference to ipc_helper_heavy_traffic_deadlock_bug, on Windows one of the test is failing:

not ok 150 - pipe_getsockname_blocking
# exit code -1073741819
# Output from process `pipe_getsockname_blocking`: (no output)

@piscisaureus piscisaureus force-pushed the bert/fix branch 2 times, most recently from eaa1388 to 04a5b3c Compare May 24, 2018 09:18
@piscisaureus
Copy link
Contributor Author

@bnoordhuis
I am at a loss about why the test added in be7978d fails on linux.
It seems that writev() blocks after writing ~160kb of data to the socket.
I would not expect it to do that; it is properly set to nonblocking mode, so why doesn't it fail with EAGAIN?

Copy link
Member

@santigimeno santigimeno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments, style mainly.

@@ -0,0 +1,151 @@
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're using this header now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, thanks, copypasta.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

TEST_IMPL(ipc_heavy_traffic_deadlock_bug) {
uv_loop_t* loop = uv_default_loop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: separate declaration from assignment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loop was unused anyway, so I removed it.

'test-idle.c',
'test-ip6-addr.c',
'test-ipc.c',
'test-ipc-heavy-traffic-deadlock-bug.c',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this file to checksparse.sh as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

src/win/pipe.c Outdated
data_length = 0;
for (i = 0; i < nbufs; i++) {
data_length += bufs[i].len;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: drop braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

src/win/pipe.c Outdated
* because WriteFile() won't accept buffers larger than that. */
if (data_length > UINT32_MAX) {
return WSAENOBUFS; /* Maps to UV_ENOBUFS. */
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: drop braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

src/win/pipe.c Outdated
heap_buffer = uv__malloc(heap_buffer_length);
if (heap_buffer == NULL) {
return ERROR_NOT_ENOUGH_MEMORY; /* Maps to UV_ENOMEM. */
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: drop braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

src/win/pipe.c Outdated
}
if (data_length > UINT32_MAX) {
return WSAENOBUFS; /* Maps to UV_ENOBUFS. */
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: drop braces

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

req->u.io.queued_bytes = write_buf.len;
handle->write_queue_size += req->u.io.queued_bytes;
if (WaitForSingleObject(req->u.io.overlapped.hEvent, INFINITE) !=
WAIT_OBJECT_0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: we're using uv_translate_sys_error while in the rest on the function the error is returned directly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that looks incorrect. The code path is likely never taken, I don't think WaitForSingleObject can conceivably fail, unless hEvent is invalid, in which case there is a bug elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@santigimeno
Copy link
Member

It seems that writev() blocks after writing ~160kb of data to the socket.

I haven't analyzed the test but by calling the test with strace it does not seem that way: it seems every call to writev completes. These are the last lines before the test runner kills the test due to exceed the timeout

[pid  4225] 12:42:38.858250 <... writev resumed> ) = 24576
[pid  4223] 12:42:38.858257 <... epoll_wait resumed> [{EPOLLIN, {u32=11, u64=11}}], 1024, -1) = 1
[pid  4225] 12:42:38.858267 writev(0, [{iov_base="********************************"..., iov_len=8192}, {iov_base="********************************"..., iov_len=8192}, {iov_base="********************************"..., iov_len=8192}], 3 <unfinished ...>
[pid  4223] 12:42:38.858282 recvmsg(11,  <unfinished ...>
[pid  4225] 12:42:38.858293 <... writev resumed> ) = 24576
[pid  4223] 12:42:38.858306 <... recvmsg resumed> {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="********************************"..., iov_len=65536}], msg_iovlen=1, msg_controllen=0, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 49152
[pid  4225] 12:42:38.858318 brk(0x556880b48000 <unfinished ...>
[pid  4223] 12:42:38.858353 epoll_wait(4,  <unfinished ...>
[pid  4225] 12:42:38.858751 <... brk resumed> ) = 0x556880b48000
[pid  4225] 12:42:38.858766 epoll_wait(4, [{EPOLLOUT, {u32=0, u64=0}}], 1024, 0) = 1
[pid  4225] 12:42:38.858792 epoll_ctl(4, EPOLL_CTL_DEL, 0, 0x7ffe5796edb0) = 0
[pid  4225] 12:42:38.858822 epoll_wait(4,

@piscisaureus
Copy link
Contributor Author

@santigimeno If you run the test without supervision of the test harness (out/Debug/run-tests ipc_heavy_traffic_deadlock_bug ipc_heavy_traffic_deadlock_bug), does it complete for you on linux?

@gireeshpunathil
Copy link
Contributor

I see two potential issues:

  • ASSERT(nread >= 0); in read_cb : I guess we cannot guarantee this given the uv_read can return negative values on error?
  • it is so possible that the test takes longer than the prescribed timeout by the runner?

@santigimeno
Copy link
Member

What I see is that the child process receives all the data and exits correctly but the parent process is stuck. It looks some pending handle needs to be closed.

@piscisaureus
Copy link
Contributor Author

@gireeshpunathil

ASSERT(nread >= 0); in read_cb : I guess we cannot guarantee this given the uv_read can return negative values on error?

Where in the code is this / should it be?

It is so possible that the test takes longer than the prescribed timeout by the runner?

I think that's unlikely. The tests makes 256 write calls and sends a grand total of 6mb of data to the child process. The child process does the same in the other direction. That should not even take a second.

ssize_t i;
int r;

ASSERT(nread >= 0);
Copy link
Contributor

@gireeshpunathil gireeshpunathil May 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe nread here is overloaded with the number of bytes written | error code from the syscall (which can be negative)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. The assert is here to check that there were no read errors (we don't expect any).

@gireeshpunathil
Copy link
Contributor

@piscisaureus - highlighted the point above. Agreed on the timeout stuff, shouldn't take that much ( I was speculating). Now that @santigimeno has spotted a hang, I guess that looks to be the right direction.

@santigimeno
Copy link
Member

The ipc channel needs to be closed. This patch fixes the issue locally for me:

diff --git a/test/test-ipc-heavy-traffic-deadlock-bug.c b/test/test-ipc-heavy-traffic-deadlock-bug.c
index 62868e6a..d1b54bff 100644
--- a/test/test-ipc-heavy-traffic-deadlock-bug.c
+++ b/test/test-ipc-heavy-traffic-deadlock-bug.c
@@ -81,6 +81,15 @@ static void alloc_cb(uv_handle_t* handle,
 static void read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) {
   ssize_t i;
   int r;
+  uv_shutdown_t shut_req;
+
+  if (nread == UV_EOF) {
+    ASSERT(bytes_read == XFER_SIZE);
+    r = uv_read_stop(handle);
+    ASSERT(r == 0);
+    uv_close((uv_handle_t*)handle, NULL);
+    return;
+  }
 
   ASSERT(nread >= 0);
   bytes_read += nread;
@@ -91,7 +100,7 @@ static void read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) {
   free(buf->base);
 
   if (bytes_read >= XFER_SIZE) {
-    r = uv_read_stop(handle);
+    uv_shutdown(&shut_req, handle, NULL);
     ASSERT(r == 0);
   }
 }

@piscisaureus piscisaureus force-pushed the bert/fix branch 4 times, most recently from 5ff9808 to 973d73f Compare May 24, 2018 19:25
@piscisaureus piscisaureus changed the title Windows: fix pipe IPC deadlock Windows: support vectored writes to pipe, fix IPC deadlock May 29, 2018
@piscisaureus piscisaureus changed the title Windows: support vectored writes to pipe, fix IPC deadlock win,pipe: support vectored writes, fix IPC deadlock May 29, 2018
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
Fixes: #794
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
* Don't silently ignore ERROR_OPERATION_ABORTED.

  Code to silently ignore this error was added in c42a4ca, under the
  false premise that this error is somehow equivalent to EINTR on posix
  platforms. This isn't true; ERROR_OPERATION_ABORTED doesn't happen
  unless the application explicitly aborts an I/O operation.

  Silently ignoring this error elsewhere could potentially hide bugs,
  hence libuv shouldn't do it. Instead, explicitly deal with it where
  it is expected.

* Don't mark aborted reads as successful.

  The worker thread used to call ReadFile() on synchronous pipes would
  incorrectly mark cancelled read requests as successful, leading to
  issues later on.

* Rely on main thread to restart aborted reads.

  After a blocking ReadFile() call was cancelled, the worker thread
  would previously attempt to restart it immediately, making
  synchronization logic needlessly complex. Instead, we simply cancel
  the operation, and leave it to the main loop to restart it if so
  desired.

  Since we now realy on the main thread to restart interrupted
  ReadFile() calls, we can now have a single function interrupts a
  synchronous read until the event loop restarts it again.

* Clean up uv__pipe_read_stop().

  A single function to interrupt blocking reads also allows us to
  remove weird logic in uv__pipe_read_stop() that quickly pauses and
  unpauses a read operation in the hope of cancelling it.

* Assume CancelIo() and CancelSynchronousIo() are always available.

  Since libuv doesn't support windows XP and Server 2003 any more, we
  can assume that these APIs are always available, and do away with
  branching around them.

PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
This fixes a bug where IPC pipe communication would deadlock when both
ends of the pipe are written to simultaneously, and the kernel pipe
buffer has already been filled up by earlier writes.

The root cause of the deadlock is that, while writes to an IPC pipe are
generally asynchronous, the IPC frame header is written synchronously.
So when both ends of the pipe are sending a frame header at the same
time, neither will read data off the pipe, causing both header writes
to block indefinitely.

Additionally, this patch somewhat reduces the spaghetti level in
win/pipe.c.

Fixes: #1099
Refs: nodejs/node#7657
Refs: electron/electron#10107
Refs: parcel-bundler/parcel#637
Refs: parcel-bundler/parcel#900
Refs: parcel-bundler/parcel#1137
PR-URL: #1843
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Bartosz Sosnowski <bartosz@janeasystems.com>
Reviewed-By: Gireesh Punathil <gpunathi@in.ibm.com>
@piscisaureus piscisaureus merged commit 4e53af9 into v1.x May 29, 2018
@piscisaureus piscisaureus deleted the bert/fix branch May 29, 2018 20:05
cjihrig added a commit to cjihrig/node that referenced this pull request Jun 25, 2018
Notable changes:

- Building via cmake is now supported.
  PR-URL: libuv/libuv#1850
- Stricter checks have been added to prevent watching the same
  file descriptor multiple times.
  PR-URL: libuv/libuv#1851
  Refs: nodejs#3604
- An IPC deadlock on Windows has been fixed.
  PR-URL: libuv/libuv#1843
  Fixes: nodejs#9706
  Fixes: nodejs#7657
- uv_fs_lchown() has been added.
  PR-URL: libuv/libuv#1826
  Refs: nodejs#19868
- uv_fs_copyfile() sets errno on error.
  PR-URL: libuv/libuv#1881
  Fixes: nodejs#21329
- uv_fs_fchmod() supports -A files on Windows.
  PR-URL: libuv/libuv#1819
  Refs: nodejs#12803

PR-URL: nodejs#21466
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
targos pushed a commit to nodejs/node that referenced this pull request Jun 25, 2018
Notable changes:

- Building via cmake is now supported.
  PR-URL: libuv/libuv#1850
- Stricter checks have been added to prevent watching the same
  file descriptor multiple times.
  PR-URL: libuv/libuv#1851
  Refs: #3604
- An IPC deadlock on Windows has been fixed.
  PR-URL: libuv/libuv#1843
  Fixes: #9706
  Fixes: #7657
- uv_fs_lchown() has been added.
  PR-URL: libuv/libuv#1826
  Refs: #19868
- uv_fs_copyfile() sets errno on error.
  PR-URL: libuv/libuv#1881
  Fixes: #21329
- uv_fs_fchmod() supports -A files on Windows.
  PR-URL: libuv/libuv#1819
  Refs: #12803

PR-URL: #21466
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MylesBorins pushed a commit to MylesBorins/node that referenced this pull request Nov 5, 2018
Notable changes:

- Building via cmake is now supported.
  PR-URL: libuv/libuv#1850
- Stricter checks have been added to prevent watching the same
  file descriptor multiple times.
  PR-URL: libuv/libuv#1851
  Refs: nodejs#3604
- An IPC deadlock on Windows has been fixed.
  PR-URL: libuv/libuv#1843
  Fixes: nodejs#9706
  Fixes: nodejs#7657
- uv_fs_lchown() has been added.
  PR-URL: libuv/libuv#1826
  Refs: nodejs#19868
- uv_fs_copyfile() sets errno on error.
  PR-URL: libuv/libuv#1881
  Fixes: nodejs#21329
- uv_fs_fchmod() supports -A files on Windows.
  PR-URL: libuv/libuv#1819
  Refs: nodejs#12803

PR-URL: nodejs#21466
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
MylesBorins pushed a commit to nodejs/node that referenced this pull request Nov 11, 2018
Notable changes:

- Building via cmake is now supported.
  PR-URL: libuv/libuv#1850
- Stricter checks have been added to prevent watching the same
  file descriptor multiple times.
  PR-URL: libuv/libuv#1851
  Refs: #3604
- An IPC deadlock on Windows has been fixed.
  PR-URL: libuv/libuv#1843
  Fixes: #9706
  Fixes: #7657
- uv_fs_lchown() has been added.
  PR-URL: libuv/libuv#1826
  Refs: #19868
- uv_fs_copyfile() sets errno on error.
  PR-URL: libuv/libuv#1881
  Fixes: #21329
- uv_fs_fchmod() supports -A files on Windows.
  PR-URL: libuv/libuv#1819
  Refs: #12803

Backport-PR-URL: #24103
PR-URL: #21466
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
bnoordhuis added a commit to bnoordhuis/libuv that referenced this pull request Nov 27, 2018
Guard against sending the handle over the UNIX domain socket twice
when the first sendmsg() didn't write all bytes.

The changes to src/win partially undo changes made earlier this year,
see the referenced pull request for details.

Libuv never made promises about the value of `req->send_handle` at
different points in time so this should be a safe, non-breaking change.

No tests because this particular condition is pretty much impossible
to hit reliably.

Fixes: libuv#2086
Refs: libuv#1843
bnoordhuis added a commit to bnoordhuis/libuv that referenced this pull request Dec 30, 2018
Guard against sending the handle over the UNIX domain socket twice
when the first sendmsg() didn't write all bytes.

The changes to src/win partially undo changes made earlier this year,
see the referenced pull request for details.

Libuv never made promises about the value of `req->send_handle` at
different points in time so this should be a safe, non-breaking change.

No tests because this particular condition is hard to hit reliably
across platforms. I spent a lot of time trying to write one but it
turned out hideously complex, and worse, flaky.

Fixes: libuv#2086
PR-URL: libuv#2097
Refs: libuv#1843
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
njlr pushed a commit to buckaroo-pm/libuv that referenced this pull request Apr 5, 2019
Guard against sending the handle over the UNIX domain socket twice
when the first sendmsg() didn't write all bytes.

The changes to src/win partially undo changes made earlier this year,
see the referenced pull request for details.

Libuv never made promises about the value of `req->send_handle` at
different points in time so this should be a safe, non-breaking change.

No tests because this particular condition is hard to hit reliably
across platforms. I spent a lot of time trying to write one but it
turned out hideously complex, and worse, flaky.

Fixes: libuv#2086
PR-URL: libuv#2097
Refs: libuv#1843
Reviewed-By: Santiago Gimeno <santiago.gimeno@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants