From 7c3574bfee2ac3a1b1ede30913ec1475c1841f8b Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 9 Sep 2016 17:41:01 -0400 Subject: [PATCH 001/109] tests: cleanup: Avoid `.pop().unwrap()` in cross_process_sender_transfer() Since we assert the number of elements in the vector to be exactly one right before this, we can just index the element directly. --- src/platform/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 064eb00eb..683c01e26 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -678,7 +678,7 @@ fn cross_process_sender_transfer() { let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); assert_eq!(received_channels.len(), 1); - let sub_tx = received_channels.pop().unwrap().to_sender(); + let sub_tx = received_channels[0].to_sender(); let data: &[u8] = b"baz"; sub_tx.send(data, vec![], vec![]).unwrap(); From d3aeaa57b4e206bf92b1b47f4f1188395683734e Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 9 Sep 2016 17:41:01 -0400 Subject: [PATCH 002/109] tests: Add variants of cross-process test, using `spawn()` Since `fork()` isn't available on Windows, we need to do the tests using `spawn()` instead. The `spawn()` variants work both on Windows and on Unix platforms (including MacOS), making the `fork()` variants pretty redundant. However, it doesn't really hurt to keep them around, just in case. (It might help narrowing down any problems coming up, for example.) --- src/platform/test.rs | 117 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 683c01e26..57d223fa5 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -10,16 +10,35 @@ use platform::{self, OsIpcChannel, OsIpcReceiverSet}; use platform::{OsIpcSharedMemory}; use std::collections::HashMap; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::{Duration, Instant}; use std::thread; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::env; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use libc; use platform::{OsIpcSender, OsIpcOneShotServer}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use test::{fork, Wait}; +// Helper to get a channel_name argument passed in; used for the +// cross-process spawn server tests. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +fn get_channel_name_arg() -> Option { + for arg in env::args() { + let arg_str = "channel_name:"; + if arg.starts_with(arg_str) { + return Some(arg[arg_str.len()..].to_owned()); + } + } + None +} + #[test] fn simple() { let (tx, rx) = platform::channel().unwrap(); @@ -643,9 +662,51 @@ fn server_connect_first() { (data, vec![], vec![])); } +// Note! This test is actually used by the cross_process_spawn() test +// below as a second process. Running it by itself is meaningless, but +// passes. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_server() +{ + let data: &[u8] = b"1234567"; + let channel_name = get_channel_name_arg(); + if channel_name.is_none() { + return; + } + + let tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + tx.send(data, vec![], vec![]).unwrap(); + unsafe { libc::exit(0); } +} + +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +fn cross_process_spawn() { + let (server, name) = OsIpcOneShotServer::new().unwrap(); + let data: &[u8] = b"1234567"; + + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_server") + .arg(format!("channel_name:{}", name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + let (_, received_data, received_channels, received_shared_memory_regions) = + server.accept().unwrap(); + child_pid.wait().expect("failed to wait on child"); + assert_eq!((&received_data[..], received_channels, received_shared_memory_regions), + (data, vec![], vec![])); +} + #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -fn cross_process() { +fn cross_process_fork() { let (server, name) = OsIpcOneShotServer::new().unwrap(); let data: &[u8] = b"1234567"; @@ -661,9 +722,61 @@ fn cross_process() { (data, vec![], vec![])); } +// Note! This test is actually used by the cross_process_sender_transfer_spawn() test +// below as a second process. Running it by itself is meaningless, but +// passes. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_sender_transfer_server() +{ + let channel_name = get_channel_name_arg(); + if channel_name.is_none() { + return; + } + + let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + let (sub_tx, sub_rx) = platform::channel().unwrap(); + let data: &[u8] = b"foo"; + super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + sub_rx.recv().unwrap(); + let data: &[u8] = b"bar"; + super_tx.send(data, vec![], vec![]).unwrap(); + unsafe { libc::exit(0); } +} + +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +fn cross_process_sender_transfer_spawn() { + let (server, name) = OsIpcOneShotServer::new().unwrap(); + + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_sender_transfer_server") + .arg(format!("channel_name:{}", name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); + assert_eq!(received_channels.len(), 1); + let sub_tx = received_channels[0].to_sender(); + let data: &[u8] = b"baz"; + sub_tx.send(data, vec![], vec![]).unwrap(); + + let data: &[u8] = b"bar"; + let (received_data, received_channels, received_shared_memory_regions) = + super_rx.recv().unwrap(); + child_pid.wait().expect("failed to wait on child"); + assert_eq!((&received_data[..], received_channels, received_shared_memory_regions), + (data, vec![], vec![])); +} + #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -fn cross_process_sender_transfer() { +fn cross_process_sender_transfer_fork() { let (server, name) = OsIpcOneShotServer::new().unwrap(); let child_pid = unsafe { fork(|| { From e38c40c10e347a10ff015dafa086a4e1406ed8ca Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Thu, 15 Dec 2016 11:09:27 -0500 Subject: [PATCH 003/109] Add inception channel-in-channel multi-process transfer test --- src/platform/test.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/platform/test.rs b/src/platform/test.rs index 57d223fa5..b8abab3f6 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -1081,3 +1081,95 @@ mod sync_test { platform::OsIpcSender::test_not_sync(); } } + +// Note! This test is actually used by the +// cross_process_two_step_transfer_spawn() test below. Running it by +// itself is meaningless, but it passes if run this way. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_two_step_transfer_server() +{ + let cookie: &[u8] = b"cookie"; + let channel_name = get_channel_name_arg(); + if channel_name.is_none() { + return; + } + + // connect by name to our other process + let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + + // create a channel for real communication between the two processes + let (sub_tx, sub_rx) = platform::channel().unwrap(); + + // send the other process the tx side, so it can send us the channels + super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + + // get two_rx from the other process + let (_, mut received_channels, _) = sub_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let two_rx = received_channels[0].to_receiver(); + + // get one_rx from two_rx's buffer + let (_, mut received_channels, _) = two_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let one_rx = received_channels[0].to_receiver(); + + // get a cookie from one_rx + let (data, _, _) = one_rx.recv().unwrap(); + assert_eq!(&data[..], cookie); + + // finally, send a cookie back + super_tx.send(&data, vec![], vec![]).unwrap(); + + // terminate + unsafe { libc::exit(0); } +} + +// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! +// Needs investigation. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg_attr(target_os = "macos", ignore)] +#[test] +fn cross_process_two_step_transfer_spawn() { + let cookie: &[u8] = b"cookie"; + + // create channel 1 + let (one_tx, one_rx) = platform::channel().unwrap(); + // put data in channel 1's pipe + one_tx.send(cookie, vec![], vec![]).unwrap(); + + // create channel 2 + let (two_tx, two_rx) = platform::channel().unwrap(); + // put channel 1's rx end in channel 2's pipe + two_tx.send(&[], vec![OsIpcChannel::Receiver(one_rx)], vec![]).unwrap(); + + // create a one-shot server, and spawn another process + let (server, name) = OsIpcOneShotServer::new().unwrap(); + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_two_step_transfer_server") + .arg(format!("channel_name:{}", name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + // The other process will have sent us a transmit channel in received channels + let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); + assert_eq!(received_channels.len(), 1); + let sub_tx = received_channels[0].to_sender(); + + // Send the outer payload channel, so the server can use it to + // retrive the inner payload and the cookie + sub_tx.send(&[], vec![OsIpcChannel::Receiver(two_rx)], vec![]).unwrap(); + + // Then we wait for the cookie to make its way back to us + let (received_data, received_channels, received_shared_memory_regions) = + super_rx.recv().unwrap(); + let child_exit_code = child_pid.wait().expect("failed to wait on child"); + assert!(child_exit_code.success()); + assert_eq!((&received_data[..], received_channels, received_shared_memory_regions), + (cookie, vec![], vec![])); +} From 596e4564a00859a05c7b732c9f8f8e62f931d39a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 20:25:22 +0200 Subject: [PATCH 004/109] tests: Introduce `which` parameter in `get_channel_name_arg()` helper In preparation for introducing spawn tests using more than one channel, we need a way to extract specific channel name args by distinctive labels. All existing `*_spawn()` tests are adapted to include the label in the passed command line argument; and all `*_server()` helpers are updated with the new argument in the `get_channel_name_arg()` calls. --- src/platform/test.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index b8abab3f6..2015e04cd 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -29,9 +29,9 @@ use test::{fork, Wait}; // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -fn get_channel_name_arg() -> Option { +pub fn get_channel_name_arg(which: &str) -> Option { for arg in env::args() { - let arg_str = "channel_name:"; + let arg_str = &*format!("channel_name-{}:", which); if arg.starts_with(arg_str) { return Some(arg[arg_str.len()..].to_owned()); } @@ -671,7 +671,7 @@ fn server_connect_first() { fn cross_process_server() { let data: &[u8] = b"1234567"; - let channel_name = get_channel_name_arg(); + let channel_name = get_channel_name_arg("server"); if channel_name.is_none() { return; } @@ -690,7 +690,7 @@ fn cross_process_spawn() { let mut child_pid = Command::new(env::current_exe().unwrap()) .arg("--ignored") .arg("cross_process_server") - .arg(format!("channel_name:{}", name)) + .arg(format!("channel_name-server:{}", name)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -730,7 +730,7 @@ fn cross_process_fork() { #[ignore] fn cross_process_sender_transfer_server() { - let channel_name = get_channel_name_arg(); + let channel_name = get_channel_name_arg("server"); if channel_name.is_none() { return; } @@ -753,7 +753,7 @@ fn cross_process_sender_transfer_spawn() { let mut child_pid = Command::new(env::current_exe().unwrap()) .arg("--ignored") .arg("cross_process_sender_transfer_server") - .arg(format!("channel_name:{}", name)) + .arg(format!("channel_name-server:{}", name)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -1091,7 +1091,7 @@ mod sync_test { fn cross_process_two_step_transfer_server() { let cookie: &[u8] = b"cookie"; - let channel_name = get_channel_name_arg(); + let channel_name = get_channel_name_arg("server"); if channel_name.is_none() { return; } @@ -1149,7 +1149,7 @@ fn cross_process_two_step_transfer_spawn() { let mut child_pid = Command::new(env::current_exe().unwrap()) .arg("--ignored") .arg("cross_process_two_step_transfer_server") - .arg(format!("channel_name:{}", name)) + .arg(format!("channel_name-server:{}", name)) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) From 45f8264b74b24ef926362886645ef2f2363e80e8 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 20:38:56 +0200 Subject: [PATCH 005/109] tests: Move `get_channel_name_arg()` helper to `src/test.rs` In preparation for adding `_spawn()` test variants for the tests in `src/test.rs` as well, we need to move the helper there, so it can be used in both `src/test.rs` and `src/platform/test.rs` -- just like the `fork()` helpers. --- src/platform/test.rs | 13 +------------ src/test.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 2015e04cd..344067645 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -25,19 +25,8 @@ use platform::{OsIpcSender, OsIpcOneShotServer}; use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use test::{fork, Wait}; - -// Helper to get a channel_name argument passed in; used for the -// cross-process spawn server tests. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -pub fn get_channel_name_arg(which: &str) -> Option { - for arg in env::args() { - let arg_str = &*format!("channel_name-{}:", which); - if arg.starts_with(arg_str) { - return Some(arg[arg_str.len()..].to_owned()); - } - } - None -} +use test::get_channel_name_arg; #[test] fn simple() { diff --git a/src/test.rs b/src/test.rs index 4894d9081..3198525af 100644 --- a/src/test.rs +++ b/src/test.rs @@ -15,6 +15,8 @@ use router::ROUTER; use libc; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cell::RefCell; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::env; use std::iter; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use std::ptr; @@ -55,6 +57,19 @@ impl Wait for libc::pid_t { } } +// Helper to get a channel_name argument passed in; used for the +// cross-process spawn server tests. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +pub fn get_channel_name_arg(which: &str) -> Option { + for arg in env::args() { + let arg_str = &*format!("channel_name-{}:", which); + if arg.starts_with(arg_str) { + return Some(arg[arg_str.len()..].to_owned()); + } + } + None +} + type Person = (String, u32); #[test] From 5afef4868159d8dd6d8e1fc50c2ba53c5834a3f5 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 20:41:53 +0200 Subject: [PATCH 006/109] tests: Introduce `cross_process_embedded_senders_spawn()` Just like the other cross-process tests, this one needs a variant using `spawn()` instead of `fork()`, so it can work on platforms that don't provide `fork()`. --- src/test.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/test.rs b/src/test.rs index 3198525af..67da27641 100644 --- a/src/test.rs +++ b/src/test.rs @@ -19,6 +19,8 @@ use std::cell::RefCell; use std::env; use std::iter; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +use std::process::{Command, Stdio}; +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use std::ptr; use std::sync::Arc; use std::sync::mpsc::{self, Sender}; @@ -152,9 +154,67 @@ fn select() { } } +// Note! This test is actually used by the cross_process_embedded_senders_spawn() test +// below as a second process. Running it by itself is meaningless, but +// passes. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +#[ignore] +fn cross_process_embedded_senders_server() { + let person = ("Patrick Walton".to_owned(), 29); + + let server0_name = if let Some(name) = get_channel_name_arg("server0") { + name + } else { + return + }; + let server2_name = if let Some(name) = get_channel_name_arg("server2") { + name + } else { + return + }; + + let (tx1, rx1): (IpcSender, IpcReceiver) = ipc::channel().unwrap(); + let tx0 = IpcSender::connect(server0_name).unwrap(); + tx0.send(tx1).unwrap(); + rx1.recv().unwrap(); + let tx2: IpcSender = IpcSender::connect(server2_name).unwrap(); + tx2.send(person.clone()).unwrap(); + + unsafe { libc::exit(0); } +} + +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[test] +fn cross_process_embedded_senders_spawn() { + let person = ("Patrick Walton".to_owned(), 29); + + let (server0, server0_name) = IpcOneShotServer::new().unwrap(); + let (server2, server2_name) = IpcOneShotServer::new().unwrap(); + + let mut child_pid = Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg("cross_process_embedded_senders_server") + .arg(format!("channel_name-server0:{}", server0_name)) + .arg(format!("channel_name-server2:{}", server2_name)) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process"); + + let (_, tx1): (_, IpcSender) = server0.accept().unwrap(); + tx1.send(person.clone()).unwrap(); + let (_, received_person): (_, Person) = server2.accept().unwrap(); + + child_pid.wait().expect("failed to wait on child"); + + assert_eq!(received_person, person); +} + #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -fn cross_process_embedded_senders() { +fn cross_process_embedded_senders_fork() { let person = ("Patrick Walton".to_owned(), 29); let (server0, server0_name) = IpcOneShotServer::new().unwrap(); let (server2, server2_name) = IpcOneShotServer::new().unwrap(); From e3e52a1fb37d9978fbcfc0f6d1d1245c929f28c3 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 12 Jul 2017 21:26:49 +0200 Subject: [PATCH 007/109] tests: cleanup: Streamline structure of `_server()` helpers Change the way parameter unwrapping / conditional execution is handled, so the active code in the `*_server()` functions is more in line with the corresponding `*_fork()` variants. This should faciliate keeping the fork/spawn variants in sync. --- src/platform/test.rs | 80 +++++++++++++++++++++----------------------- src/test.rs | 29 ++++++---------- 2 files changed, 49 insertions(+), 60 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 344067645..a649d6eb4 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -661,13 +661,12 @@ fn cross_process_server() { let data: &[u8] = b"1234567"; let channel_name = get_channel_name_arg("server"); - if channel_name.is_none() { - return; - } + if let Some(channel_name) = channel_name { + let tx = OsIpcSender::connect(channel_name).unwrap(); + tx.send(data, vec![], vec![]).unwrap(); - let tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); - tx.send(data, vec![], vec![]).unwrap(); - unsafe { libc::exit(0); } + unsafe { libc::exit(0); } + } } #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] @@ -720,18 +719,17 @@ fn cross_process_fork() { fn cross_process_sender_transfer_server() { let channel_name = get_channel_name_arg("server"); - if channel_name.is_none() { - return; - } + if let Some(channel_name) = channel_name { + let super_tx = OsIpcSender::connect(channel_name).unwrap(); + let (sub_tx, sub_rx) = platform::channel().unwrap(); + let data: &[u8] = b"foo"; + super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + sub_rx.recv().unwrap(); + let data: &[u8] = b"bar"; + super_tx.send(data, vec![], vec![]).unwrap(); - let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); - let (sub_tx, sub_rx) = platform::channel().unwrap(); - let data: &[u8] = b"foo"; - super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); - sub_rx.recv().unwrap(); - let data: &[u8] = b"bar"; - super_tx.send(data, vec![], vec![]).unwrap(); - unsafe { libc::exit(0); } + unsafe { libc::exit(0); } + } } #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] @@ -1081,38 +1079,36 @@ fn cross_process_two_step_transfer_server() { let cookie: &[u8] = b"cookie"; let channel_name = get_channel_name_arg("server"); - if channel_name.is_none() { - return; - } - - // connect by name to our other process - let super_tx = OsIpcSender::connect(channel_name.unwrap()).unwrap(); + if let Some(channel_name) = channel_name { + // connect by name to our other process + let super_tx = OsIpcSender::connect(channel_name).unwrap(); - // create a channel for real communication between the two processes - let (sub_tx, sub_rx) = platform::channel().unwrap(); + // create a channel for real communication between the two processes + let (sub_tx, sub_rx) = platform::channel().unwrap(); - // send the other process the tx side, so it can send us the channels - super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); + // send the other process the tx side, so it can send us the channels + super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap(); - // get two_rx from the other process - let (_, mut received_channels, _) = sub_rx.recv().unwrap(); - assert_eq!(received_channels.len(), 1); - let two_rx = received_channels[0].to_receiver(); + // get two_rx from the other process + let (_, mut received_channels, _) = sub_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let two_rx = received_channels[0].to_receiver(); - // get one_rx from two_rx's buffer - let (_, mut received_channels, _) = two_rx.recv().unwrap(); - assert_eq!(received_channels.len(), 1); - let one_rx = received_channels[0].to_receiver(); + // get one_rx from two_rx's buffer + let (_, mut received_channels, _) = two_rx.recv().unwrap(); + assert_eq!(received_channels.len(), 1); + let one_rx = received_channels[0].to_receiver(); - // get a cookie from one_rx - let (data, _, _) = one_rx.recv().unwrap(); - assert_eq!(&data[..], cookie); + // get a cookie from one_rx + let (data, _, _) = one_rx.recv().unwrap(); + assert_eq!(&data[..], cookie); - // finally, send a cookie back - super_tx.send(&data, vec![], vec![]).unwrap(); + // finally, send a cookie back + super_tx.send(&data, vec![], vec![]).unwrap(); - // terminate - unsafe { libc::exit(0); } + // terminate + unsafe { libc::exit(0); } + } } // TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! diff --git a/src/test.rs b/src/test.rs index 67da27641..6d99c738a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -163,25 +163,18 @@ fn select() { fn cross_process_embedded_senders_server() { let person = ("Patrick Walton".to_owned(), 29); - let server0_name = if let Some(name) = get_channel_name_arg("server0") { - name - } else { - return - }; - let server2_name = if let Some(name) = get_channel_name_arg("server2") { - name - } else { - return - }; - - let (tx1, rx1): (IpcSender, IpcReceiver) = ipc::channel().unwrap(); - let tx0 = IpcSender::connect(server0_name).unwrap(); - tx0.send(tx1).unwrap(); - rx1.recv().unwrap(); - let tx2: IpcSender = IpcSender::connect(server2_name).unwrap(); - tx2.send(person.clone()).unwrap(); + let server0_name = get_channel_name_arg("server0"); + let server2_name = get_channel_name_arg("server2"); + if let (Some(server0_name), Some(server2_name)) = (server0_name, server2_name) { + let (tx1, rx1): (IpcSender, IpcReceiver) = ipc::channel().unwrap(); + let tx0 = IpcSender::connect(server0_name).unwrap(); + tx0.send(tx1).unwrap(); + rx1.recv().unwrap(); + let tx2: IpcSender = IpcSender::connect(server2_name).unwrap(); + tx2.send(person.clone()).unwrap(); - unsafe { libc::exit(0); } + unsafe { libc::exit(0); } + } } #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] From a2529f8f898a791cf93be00b8d3dcf17bb939c29 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 26 Jul 2017 21:17:19 +0200 Subject: [PATCH 008/109] tests: cleanup: Introduce a common `spawn_server()` helper function --- src/platform/test.rs | 38 ++++++-------------------------------- src/test.rs | 30 +++++++++++++++++++----------- 2 files changed, 25 insertions(+), 43 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index a649d6eb4..49ba2c12d 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -10,13 +10,9 @@ use platform::{self, OsIpcChannel, OsIpcReceiverSet}; use platform::{OsIpcSharedMemory}; use std::collections::HashMap; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use std::process::{Command, Stdio}; use std::sync::Arc; use std::time::{Duration, Instant}; use std::thread; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use std::env; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc; @@ -26,7 +22,7 @@ use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use test::{fork, Wait}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use test::get_channel_name_arg; +use test::{get_channel_name_arg, spawn_server}; #[test] fn simple() { @@ -675,15 +671,7 @@ fn cross_process_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); let data: &[u8] = b"1234567"; - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_server") - .arg(format!("channel_name-server:{}", name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_server", &[("server", &*name)]); let (_, received_data, received_channels, received_shared_memory_regions) = server.accept().unwrap(); @@ -737,15 +725,8 @@ fn cross_process_sender_transfer_server() fn cross_process_sender_transfer_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_sender_transfer_server") - .arg(format!("channel_name-server:{}", name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_sender_transfer_server", + &[("server", &*name)]); let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); assert_eq!(received_channels.len(), 1); @@ -1131,15 +1112,8 @@ fn cross_process_two_step_transfer_spawn() { // create a one-shot server, and spawn another process let (server, name) = OsIpcOneShotServer::new().unwrap(); - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_two_step_transfer_server") - .arg(format!("channel_name-server:{}", name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_two_step_transfer_server", + &[("server", &*name)]); // The other process will have sent us a transmit channel in received channels let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); diff --git a/src/test.rs b/src/test.rs index 6d99c738a..984150520 100644 --- a/src/test.rs +++ b/src/test.rs @@ -19,7 +19,7 @@ use std::cell::RefCell; use std::env; use std::iter; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -use std::process::{Command, Stdio}; +use std::process::{self, Command, Stdio}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use std::ptr; use std::sync::Arc; @@ -72,6 +72,22 @@ pub fn get_channel_name_arg(which: &str) -> Option { None } +// Helper to get a channel_name argument passed in; used for the +// cross-process spawn server tests. +#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +pub fn spawn_server(test_name: &str, server_args: &[(&str, &str)]) -> process::Child { + Command::new(env::current_exe().unwrap()) + .arg("--ignored") + .arg(test_name) + .args(server_args.iter() + .map(|&(ref name, ref val)| format!("channel_name-{}:{}", name, val))) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .expect("failed to execute server process") +} + type Person = (String, u32); #[test] @@ -185,16 +201,8 @@ fn cross_process_embedded_senders_spawn() { let (server0, server0_name) = IpcOneShotServer::new().unwrap(); let (server2, server2_name) = IpcOneShotServer::new().unwrap(); - let mut child_pid = Command::new(env::current_exe().unwrap()) - .arg("--ignored") - .arg("cross_process_embedded_senders_server") - .arg(format!("channel_name-server0:{}", server0_name)) - .arg(format!("channel_name-server2:{}", server2_name)) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .expect("failed to execute server process"); + let mut child_pid = spawn_server("cross_process_embedded_senders_server", + &[("server0", &*server0_name), ("server2", &*server2_name)]); let (_, tx1): (_, IpcSender) = server0.accept().unwrap(); tx1.send(person.clone()).unwrap(); From 58733f6d6f7c633bcba70030858ab81ab2bfc1f0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 26 Jul 2017 21:37:13 +0200 Subject: [PATCH 009/109] tests: cleanup: Avoid need for separate `_server()` pseudo-tests Integrate the server functionality right into the main `_spawn()` test functions: running either the server or the client portions as necessary. This further aligns the structure of these tests with the corresponding `_fork()` variants. It also avoids the need for the `#[ignore]`/`--ignored` hack, since all the test functions now always run -- either as normal tests (client parts); or as pseudo-tests, when self-spawned in server mode. --- src/platform/test.rs | 53 +++++++++----------------------------------- src/test.rs | 16 ++----------- 2 files changed, 13 insertions(+), 56 deletions(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index 49ba2c12d..c1ca9fad7 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -647,15 +647,11 @@ fn server_connect_first() { (data, vec![], vec![])); } -// Note! This test is actually used by the cross_process_spawn() test -// below as a second process. Running it by itself is meaningless, but -// passes. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -#[ignore] -fn cross_process_server() -{ +fn cross_process_spawn() { let data: &[u8] = b"1234567"; + let channel_name = get_channel_name_arg("server"); if let Some(channel_name) = channel_name { let tx = OsIpcSender::connect(channel_name).unwrap(); @@ -663,15 +659,9 @@ fn cross_process_server() unsafe { libc::exit(0); } } -} -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[test] -fn cross_process_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); - let data: &[u8] = b"1234567"; - - let mut child_pid = spawn_server("cross_process_server", &[("server", &*name)]); + let mut child_pid = spawn_server("cross_process_spawn", &[("server", &*name)]); let (_, received_data, received_channels, received_shared_memory_regions) = server.accept().unwrap(); @@ -698,14 +688,9 @@ fn cross_process_fork() { (data, vec![], vec![])); } -// Note! This test is actually used by the cross_process_sender_transfer_spawn() test -// below as a second process. Running it by itself is meaningless, but -// passes. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -#[ignore] -fn cross_process_sender_transfer_server() -{ +fn cross_process_sender_transfer_spawn() { let channel_name = get_channel_name_arg("server"); if let Some(channel_name) = channel_name { let super_tx = OsIpcSender::connect(channel_name).unwrap(); @@ -718,15 +703,9 @@ fn cross_process_sender_transfer_server() unsafe { libc::exit(0); } } -} -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[test] -fn cross_process_sender_transfer_spawn() { let (server, name) = OsIpcOneShotServer::new().unwrap(); - - let mut child_pid = spawn_server("cross_process_sender_transfer_server", - &[("server", &*name)]); + let mut child_pid = spawn_server("cross_process_sender_transfer_spawn", &[("server", &*name)]); let (super_rx, _, mut received_channels, _) = server.accept().unwrap(); assert_eq!(received_channels.len(), 1); @@ -1050,15 +1029,14 @@ mod sync_test { } } -// Note! This test is actually used by the -// cross_process_two_step_transfer_spawn() test below. Running it by -// itself is meaningless, but it passes if run this way. +// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! +// Needs investigation. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg_attr(target_os = "macos", ignore)] #[test] -#[ignore] -fn cross_process_two_step_transfer_server() -{ +fn cross_process_two_step_transfer_spawn() { let cookie: &[u8] = b"cookie"; + let channel_name = get_channel_name_arg("server"); if let Some(channel_name) = channel_name { // connect by name to our other process @@ -1090,15 +1068,6 @@ fn cross_process_two_step_transfer_server() // terminate unsafe { libc::exit(0); } } -} - -// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! -// Needs investigation. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[cfg_attr(target_os = "macos", ignore)] -#[test] -fn cross_process_two_step_transfer_spawn() { - let cookie: &[u8] = b"cookie"; // create channel 1 let (one_tx, one_rx) = platform::channel().unwrap(); @@ -1112,7 +1081,7 @@ fn cross_process_two_step_transfer_spawn() { // create a one-shot server, and spawn another process let (server, name) = OsIpcOneShotServer::new().unwrap(); - let mut child_pid = spawn_server("cross_process_two_step_transfer_server", + let mut child_pid = spawn_server("cross_process_two_step_transfer_spawn", &[("server", &*name)]); // The other process will have sent us a transmit channel in received channels diff --git a/src/test.rs b/src/test.rs index 984150520..dffed783f 100644 --- a/src/test.rs +++ b/src/test.rs @@ -77,7 +77,6 @@ pub fn get_channel_name_arg(which: &str) -> Option { #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] pub fn spawn_server(test_name: &str, server_args: &[(&str, &str)]) -> process::Child { Command::new(env::current_exe().unwrap()) - .arg("--ignored") .arg(test_name) .args(server_args.iter() .map(|&(ref name, ref val)| format!("channel_name-{}:{}", name, val))) @@ -170,13 +169,9 @@ fn select() { } } -// Note! This test is actually used by the cross_process_embedded_senders_spawn() test -// below as a second process. Running it by itself is meaningless, but -// passes. #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] #[test] -#[ignore] -fn cross_process_embedded_senders_server() { +fn cross_process_embedded_senders_spawn() { let person = ("Patrick Walton".to_owned(), 29); let server0_name = get_channel_name_arg("server0"); @@ -191,17 +186,10 @@ fn cross_process_embedded_senders_server() { unsafe { libc::exit(0); } } -} - -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[test] -fn cross_process_embedded_senders_spawn() { - let person = ("Patrick Walton".to_owned(), 29); let (server0, server0_name) = IpcOneShotServer::new().unwrap(); let (server2, server2_name) = IpcOneShotServer::new().unwrap(); - - let mut child_pid = spawn_server("cross_process_embedded_senders_server", + let mut child_pid = spawn_server("cross_process_embedded_senders_spawn", &[("server0", &*server0_name), ("server2", &*server2_name)]); let (_, tx1): (_, IpcSender) = server0.accept().unwrap(); From 1a2df12b3892638c309d289fd7bdda8e336c22a1 Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 9 Sep 2016 17:41:01 -0400 Subject: [PATCH 010/109] Implement ipc-channel on Windows This implementation uses named pipes on Windows, with auto-generated uuid names. It takes advantage of DuplicateHandle to clone handles to target processes when sending handles cross-process. Shared memory is implemented using anonymous file mappings. select() and friends are implemented using IO Completion Ports. --- Cargo.toml | 5 + src/lib.rs | 8 +- src/platform/mod.rs | 11 +- src/platform/test.rs | 25 +- src/platform/windows/mod.rs | 1485 +++++++++++++++++++++++++++++++++++ src/test.rs | 25 +- 6 files changed, 1538 insertions(+), 21 deletions(-) create mode 100644 src/platform/windows/mod.rs diff --git a/Cargo.toml b/Cargo.toml index dc688042b..4f039bd06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ force-inprocess = [] memfd = ["sc"] unstable = [] async = ["futures"] +win32-trace = [] [dependencies] bincode = "1" @@ -30,3 +31,7 @@ futures = { version = "0.1", optional = true } [dev-dependencies] crossbeam = "0.2" + +[target.'cfg(target_os = "windows")'.dependencies] +winapi = "0.2" +kernel32-sys = "0.2" diff --git a/src/lib.rs b/src/lib.rs index a6fac93a2..517b57c2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![cfg_attr(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios"), +#![cfg_attr(any(feature = "force-inprocess", target_os = "android", target_os = "ios"), feature(mpsc_select))] #![cfg_attr(all(feature = "unstable", test), feature(specialization))] @@ -18,6 +18,7 @@ extern crate bincode; extern crate libc; extern crate rand; extern crate serde; + #[cfg(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios"))] extern crate uuid; extern crate tempfile; @@ -38,6 +39,11 @@ extern crate sc; extern crate futures; +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +extern crate winapi; +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +extern crate kernel32; + pub mod ipc; pub mod platform; pub mod router; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 4ba32d111..928a31245 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -25,9 +25,16 @@ mod os { pub use super::macos::*; } -#[cfg(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios"))] +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +mod windows; +#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))] +mod os { + pub use super::windows::*; +} + +#[cfg(any(feature = "force-inprocess", target_os = "android", target_os = "ios"))] mod inprocess; -#[cfg(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios"))] +#[cfg(any(feature = "force-inprocess", target_os = "android", target_os = "ios"))] mod os { pub use super::inprocess::*; } diff --git a/src/platform/test.rs b/src/platform/test.rs index c1ca9fad7..a321e44bc 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -14,14 +14,14 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use std::thread; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use libc; use platform::{OsIpcSender, OsIpcOneShotServer}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use libc::{kill, SIGSTOP, SIGCONT}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use test::{fork, Wait}; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use test::{get_channel_name_arg, spawn_server}; #[test] @@ -200,7 +200,8 @@ fn with_n_fds(n: usize, size: usize) { // These tests only apply to platforms that need fragmentation. #[cfg(all(not(feature = "force-inprocess"), any(target_os = "linux", - target_os = "freebsd")))] + target_os = "freebsd", + target_os = "windows")))] mod fragment_tests { use platform; use super::with_n_fds; @@ -647,7 +648,7 @@ fn server_connect_first() { (data, vec![], vec![])); } -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] #[test] fn cross_process_spawn() { let data: &[u8] = b"1234567"; @@ -688,7 +689,7 @@ fn cross_process_fork() { (data, vec![], vec![])); } -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] #[test] fn cross_process_sender_transfer_spawn() { let channel_name = get_channel_name_arg("server"); @@ -1029,10 +1030,16 @@ mod sync_test { } } -// TODO -- this fails on OSX with a MACH_SEND_INVALID_RIGHT! -// Needs investigation. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] -#[cfg_attr(target_os = "macos", ignore)] +// This test panics on Windows, because the other process will panic +// when it detects that it receives handles that are intended for another +// process. It's marked as ignore/known-fail on Windows for this reason. +// +// TODO -- this fails on OSX as well with a MACH_SEND_INVALID_RIGHT! +// Needs investigation. It may be a similar underlying issue, just done by +// the kernel instead of explicitly (ports in a message that's already +// buffered are intended for only one process). +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] +#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)] #[test] fn cross_process_two_step_transfer_spawn() { let cookie: &[u8] = b"cookie"; diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs new file mode 100644 index 000000000..573390749 --- /dev/null +++ b/src/platform/windows/mod.rs @@ -0,0 +1,1485 @@ +// Copyright 2016 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use serde; +use bincode; +use kernel32; +use libc::intptr_t; +use std::cell::{Cell, RefCell}; +use std::cmp::PartialEq; +use std::default::Default; +use std::env; +use std::ffi::CString; +use std::io::{Error, ErrorKind}; +use std::marker::{Send, Sync, PhantomData}; +use std::mem; +use std::ops::{Deref, DerefMut, RangeFrom}; +use std::ptr; +use std::slice; +use uuid::Uuid; +use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; +use winapi; + +lazy_static! { + static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; + static ref CURRENT_PROCESS_HANDLE: intptr_t = unsafe { kernel32::GetCurrentProcess() as intptr_t }; + + static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; +} + +// some debug bump macros to better track what's going on in case of errors +macro_rules! win32_trace { ($($rest:tt)*) => { + if cfg!(feature = "win32-trace") { + if *DEBUG_TRACE_ENABLED { println!($($rest)*); } + } +} } + +// When we create the pipe, how big of a write buffer do we specify? +// This is reserved in the nonpaged pool. The fragment size is the +// max we can write to the pipe without fragmentation, and the +// buffer size is what we tell the pipe it is, so we have room +// for out of band data etc. +const MAX_FRAGMENT_SIZE: usize = 64 * 1024; + +// Size of the pipe's write buffer, with excess room for the header. +const PIPE_BUFFER_SIZE: usize = MAX_FRAGMENT_SIZE + 4 * 1024; + +#[allow(non_snake_case)] +fn GetLastError() -> u32 { + unsafe { + kernel32::GetLastError() + } +} + +pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { + let pipe_id = make_pipe_id(); + let pipe_name = make_pipe_name(&pipe_id); + + let receiver = try!(OsIpcReceiver::new_named(&pipe_name)); + let sender = try!(OsIpcSender::connect_named(&pipe_name)); + + Ok((sender, receiver)) +} + +// Holds data len and out-of-band data len +struct MessageHeader(u32, u32); + +impl MessageHeader { + fn size() -> usize { + mem::size_of::() + } + + fn total_message_bytes_needed(&self) -> usize { + MessageHeader::size() + self.0 as usize + self.1 as usize + } +} + +struct Message<'data> { + data_len: usize, + oob_len: usize, + bytes: &'data [u8], +} + +impl<'data> Message<'data> { + fn from_bytes(bytes: &'data [u8]) -> Option { + if bytes.len() < MessageHeader::size() { + return None; + } + + unsafe { + let ref header = *(bytes.as_ptr() as *const MessageHeader); + if bytes.len() < header.total_message_bytes_needed() { + return None; + } + + Some(Message { + data_len: header.0 as usize, + oob_len: header.1 as usize, + bytes: &bytes[0..header.total_message_bytes_needed()], + }) + } + } + + fn data(&self) -> &[u8] { + &self.bytes[MessageHeader::size()..(MessageHeader::size() + self.data_len)] + } + + fn oob_bytes(&self) -> &[u8] { + &self.bytes[(MessageHeader::size() + self.data_len)..] + } + + fn oob_data(&self) -> Option { + if self.oob_len > 0 { + + let oob = bincode::deserialize::(self.oob_bytes()) + .expect("Failed to deserialize OOB data"); + if oob.target_process_id != *CURRENT_PROCESS_ID { + panic!("Windows IPC channel received handles intended for pid {}, but this is pid {}. \ + This likely happened because a receiver was transferred while it had outstanding data \ + that contained a channel or shared memory in its pipe. \ + This isn't supported in the Windows implementation.", + oob.target_process_id, *CURRENT_PROCESS_ID); + } + Some(oob) + } else { + None + } + } + + fn size(&self) -> usize { + MessageHeader::size() + self.data_len + self.oob_len + } +} + +// If we have any channel handles or shmem segments, then we'll send an +// OutOfBandMessage after the data message. +// +// This includes the receiver's process ID, which the receiver checks to +// make sure that the message was originally sent to it, and was not sitting +// in another channel's buffer when that channel got transferred to another +// process. On Windows, we duplicate handles on the sender side to a specific +// reciever. If the wrong receiver gets it, those handles are not valid. +// +// TODO(vlad): We could attempt to recover from the above situation by +// duplicating from the intended target process to ourselves (the receiver). +// That would only work if the intended process a) still exists; b) can be +// opened by the receiver with handle dup privileges. Another approach +// could be to use a separate dedicated process intended purely for handle +// passing, though that process would need to be global to any processes +// amongst which you want to share channels or connect one-shot servers to. +// There may be a system process that we could use for this purpose, but +// I haven't foundone -- and in the system process case, we'd need to ensure +// that we don't leak the handles (e.g. dup a handle to the system process, +// and then everything dies -- we don't want those resources to be leaked). +#[derive(Debug)] +struct OutOfBandMessage { + target_process_id: u32, + channel_handles: Vec, + shmem_handles: Vec<(intptr_t, u64)>, // handle and size + big_data_receiver_handle: Option<(intptr_t, u64)>, // handle and size +} + +impl OutOfBandMessage { + fn new(target_id: u32) -> OutOfBandMessage { + OutOfBandMessage { + target_process_id: target_id, + channel_handles: vec![], + shmem_handles: vec![], + big_data_receiver_handle: None, + } + } + + fn needs_to_be_sent(&self) -> bool { + !self.channel_handles.is_empty() || + !self.shmem_handles.is_empty() || + self.big_data_receiver_handle.is_some() + } +} + +impl serde::Serialize for OutOfBandMessage { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer + { + ((self.target_process_id, + &self.channel_handles, + &self.shmem_handles, + &self.big_data_receiver_handle)).serialize(serializer) + } +} + +impl<'de> serde::Deserialize<'de> for OutOfBandMessage { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> + { + let (target_process_id, channel_handles, shmem_handles, big_data_receiver_handle) = + try!(serde::Deserialize::deserialize(deserializer)); + Ok(OutOfBandMessage { + target_process_id: target_process_id, + channel_handles: channel_handles, + shmem_handles: shmem_handles, + big_data_receiver_handle: big_data_receiver_handle + }) + } +} + +fn make_pipe_id() -> Uuid { + Uuid::new_v4() +} + +fn make_pipe_name(pipe_id: &Uuid) -> CString { + CString::new(format!("\\\\.\\pipe\\rust-ipc-{}", pipe_id.to_string())).unwrap() +} + +// Duplicate a given handle from this process to the target one, passing the +// given flags to DuplicateHandle. +// +// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is +// also the pseudohandle for the current process). +unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE, flags: winapi::DWORD) + -> Result +{ + if handle == INVALID_HANDLE_VALUE { + return Ok(INVALID_HANDLE_VALUE); + } + + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let ok = kernel32::DuplicateHandle(*CURRENT_PROCESS_HANDLE as HANDLE, handle, + other_process, &mut new_handle, + 0, winapi::FALSE, flags); + if ok == winapi::FALSE { + Err(WinError::last("DuplicateHandle")) + } else { + Ok(new_handle) + } +} + +// duplicate a handle in the current process +fn dup_handle(handle: &WinHandle) -> Result { + dup_handle_to_process(handle, &WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE)) +} + +// duplicate a handle to the target process +fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { + unsafe { + let h = try!(dup_handle_to_process_with_flags( + **handle, **other_process, winapi::DUPLICATE_SAME_ACCESS)); + Ok(WinHandle::new(h)) + } +} + +// duplicate a handle to the target process, closing the source handle +fn move_handle_to_process(handle: &mut WinHandle, other_process: &WinHandle) -> Result { + unsafe { + let h = try!(dup_handle_to_process_with_flags( + handle.take(), **other_process, + winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS)); + Ok(WinHandle::new(h)) + } +} + +#[derive(Debug)] +struct WinHandle { + h: HANDLE +} + +unsafe impl Send for WinHandle { } +unsafe impl Sync for WinHandle { } + +impl Drop for WinHandle { + fn drop(&mut self) { + unsafe { + kernel32::CloseHandle(self.h); + } + } +} + +impl Default for WinHandle { + fn default() -> WinHandle { + WinHandle { h: INVALID_HANDLE_VALUE } + } +} + +impl Deref for WinHandle { + type Target = HANDLE; + + #[inline] + fn deref(&self) -> &HANDLE { + &self.h + } +} + +impl PartialEq for WinHandle { + fn eq(&self, other: &WinHandle) -> bool { + // FIXME this is not correct! We need to compare the object + // the handles refer to. On Windows 10, we have: + // unsafe { kernel32::CompareObjectHandles(self.h, other.h) == winapi::TRUE } + // But that + self.h == other.h + } +} + +impl WinHandle { + fn new(h: HANDLE) -> WinHandle { + WinHandle { h: h } + } + + fn invalid() -> WinHandle { + WinHandle { h: INVALID_HANDLE_VALUE } + } + + fn is_valid(&self) -> bool { + self.h != INVALID_HANDLE_VALUE + } + + fn take(&mut self) -> HANDLE { + mem::replace(&mut self.h, INVALID_HANDLE_VALUE) + } +} + +enum GetMessageResult { + NoMessage, + Message(Vec, Vec, Vec), +} + +// MessageReader implements blocking/nonblocking reads of messages +// from the handle +#[derive(Debug)] +struct MessageReader { + // The pipe read handle + handle: WinHandle, + + // The OVERLAPPED struct for async IO on this receiver; we'll only + // ever have one in flight + ov: Box, + + // A read buffer for any pending reads + read_buf: Vec, + + // If we have already issued an async read + read_in_progress: bool, + + // If we received a BROKEN_PIPE or other error + // indicating that the remote end has closed the pipe + closed: bool, + + // If this is part of a Set, then this is the ID that is used to identify + // this reader. If this is None, then this isn't part of a set. + set_id: Option, +} + +impl MessageReader { + fn new(handle: WinHandle) -> MessageReader { + MessageReader { + handle: handle, + ov: Box::new(unsafe { mem::zeroed::() }), + read_buf: Vec::new(), + read_in_progress: false, + closed: false, + set_id: None, + } + } + + fn cancel_io(&mut self) { + unsafe { + if self.read_in_progress { + kernel32::CancelIoEx(*self.handle, self.ov.deref_mut()); + self.read_in_progress = false; + } + } + } + + // Called when we receive an IO Completion Packet for this handle. + fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + win32_trace!("[$ {:?}] notify_completion", self.handle); + + // mark a read as no longer in progress even before we check errors + self.read_in_progress = false; + + if err == winapi::ERROR_BROKEN_PIPE { + assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); + self.closed = true; + return Ok(()); + } + + let nbytes = self.ov.InternalHigh as u32; + let offset = self.ov.Offset; + + assert!(offset == 0); + + // if the remote end closed... + if err != winapi::ERROR_SUCCESS { + // This should never happen + panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); + } + + unsafe { + let new_size = self.read_buf.len() + nbytes as usize; + win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", + nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); + assert!(new_size <= self.read_buf.capacity()); + self.read_buf.set_len(new_size); + } + + Ok(()) + } + + // kick off an asynchronous read + fn start_read(&mut self) -> Result<(),WinError> { + if self.read_in_progress || self.closed { + return Ok(()); + } + + win32_trace!("[$ {:?}] start_read", self.handle); + + let buf_len = self.read_buf.len(); + let mut buf_cap = self.read_buf.capacity(); + let mut bytes_read: u32 = 0; + + if buf_len == buf_cap { + self.read_buf.reserve(PIPE_BUFFER_SIZE); + buf_cap = self.read_buf.capacity(); + } + + // issue the read to the buffer, at the current length offset + unsafe { + *self.ov.deref_mut() = mem::zeroed(); + let buf_ptr = self.read_buf.as_mut_ptr() as LPVOID; + let max_read_bytes = buf_cap - buf_len; + let ok = kernel32::ReadFile(*self.handle, + buf_ptr.offset(buf_len as isize), + max_read_bytes as u32, + &mut bytes_read, + self.ov.deref_mut()); + + // ReadFile can return TRUE; if it does, an IO completion + // packet is still posted to any port, and the OVERLAPPED + // structure has the IO operation flagged as complete. + // + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + if ok == winapi::FALSE { + let err = GetLastError(); + if err == winapi::ERROR_BROKEN_PIPE { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + return Ok(()); + } + + if err == winapi::ERROR_IO_PENDING { + self.read_in_progress = true; + return Ok(()); + } + + Err(WinError::from_system(err, "ReadFile")) + } else { + self.read_in_progress = true; + Ok(()) + } + } + } + + // This is split between get_message and get_message_inner, so that + // this function can handle removing bytes from the buffer, since + // get_message_inner borrows the buffer. + fn get_message(&mut self) -> Result { + let drain_bytes; + let result; + if let Some(message) = Message::from_bytes(&self.read_buf) { + let mut channels: Vec = vec![]; + let mut shmems: Vec = vec![]; + let mut big_data = None; + + if let Some(oob) = message.oob_data() { + win32_trace!("[$ {:?}] msg with total {} bytes, {} channels, {} shmems, big data handle {:?}", + self.handle, message.data_len, oob.channel_handles.len(), oob.shmem_handles.len(), + oob.big_data_receiver_handle); + + unsafe { + for handle in oob.channel_handles.iter() { + channels.push(OsOpaqueIpcChannel::new(*handle as HANDLE)); + } + + for sh in oob.shmem_handles.iter() { + shmems.push(OsIpcSharedMemory::from_handle(sh.0 as HANDLE, sh.1 as usize).unwrap()); + } + + if oob.big_data_receiver_handle.is_some() { + let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); + let receiver = OsIpcReceiver::from_handle(handle as HANDLE); + big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); + } + } + } + + let buf_data = big_data.unwrap_or_else(|| message.data().to_vec()); + + win32_trace!("[$ {:?}] get_message success -> {} bytes, {} channels, {} shmems", + self.handle, buf_data.len(), channels.len(), shmems.len()); + drain_bytes = Some(message.size()); + result = GetMessageResult::Message(buf_data, channels, shmems); + } else { + drain_bytes = None; + result = GetMessageResult::NoMessage; + } + + if let Some(size) = drain_bytes { + // If the only valid bytes in the buffer are what we just + // consumed, then just set the vector's length to 0. This + // avoids reallocations as in the drain() case, and is + // a significant speedup. + if self.read_buf.len() == size { + self.read_buf.clear(); + } else { + self.read_buf.drain(0..size); + } + } + + Ok(result) + } + + fn add_to_iocp(&mut self, iocp: HANDLE, set_id: u64) -> Result<(),WinError> { + unsafe { + assert!(self.set_id.is_none()); + + let ret = kernel32::CreateIoCompletionPort(*self.handle, + iocp, + *self.handle as winapi::ULONG_PTR, + 0); + if ret.is_null() { + return Err(WinError::last("CreateIoCompletionPort")); + } + + self.set_id = Some(set_id); + + // Make sure that the reader has a read in flight, + // otherwise a later select() will hang. + try!(self.start_read()); + + Ok(()) + } + } + + // This is a specialized read when the buffser size is known ahead of time, + // and without our typical message framing. It's only valid to call this + // as the one and only call after creating a MessageReader. + fn read_raw_sized(&mut self, size: usize) -> Result,WinError> { + assert!(self.read_buf.len() == 0); + + // We use with_capacity() to allocate an uninitialized buffer, + // since we're going to read into it and don't need to + // zero it. + let mut buf = Vec::with_capacity(size); + while buf.len() < size { + // Because our handle is asynchronous, we have to do a two-part read -- + // first issue the operation, then wait for its completion. + unsafe { + let ov = self.ov.deref_mut(); + *ov = mem::zeroed(); + + let buf_len = buf.len(); + let dest_ptr = buf.as_mut_ptr().offset(buf_len as isize) as LPVOID; + + let bytes_left = (size - buf_len) as u32; + let mut bytes_read: u32 = 0; + + let ok = kernel32::ReadFile(*self.handle, + dest_ptr, + bytes_left, + &mut bytes_read, + ov); + if ok == winapi::FALSE && GetLastError() != winapi::ERROR_IO_PENDING { + return Err(WinError::last("ReadFile")); + } + + if ov.Internal as i32 == winapi::STATUS_PENDING { + let ok = kernel32::GetOverlappedResult(*self.handle, ov, &mut bytes_read, winapi::TRUE); + if ok == winapi::FALSE { + return Err(WinError::last("GetOverlappedResult")); + } + } else { + bytes_read = ov.InternalHigh as u32; + } + + let new_len = buf_len + bytes_read as usize; + buf.set_len(new_len); + } + } + + Ok(buf) + } +} + +#[derive(Debug)] +pub struct OsIpcReceiver { + // A MessageReader that implements most of the work of this + // MessageReader + reader: RefCell, +} + +unsafe impl Send for OsIpcReceiver { } + +impl PartialEq for OsIpcReceiver { + fn eq(&self, other: &OsIpcReceiver) -> bool { + self.reader.borrow().handle == other.reader.borrow().handle + } +} + +impl OsIpcReceiver { + unsafe fn from_handle(handle: HANDLE) -> OsIpcReceiver { + OsIpcReceiver { + reader: RefCell::new(MessageReader::new(WinHandle::new(handle))), + } + } + + fn new_named(pipe_name: &CString) -> Result { + unsafe { + // create the pipe server + let handle = + kernel32::CreateNamedPipeA(pipe_name.as_ptr(), + winapi::PIPE_ACCESS_INBOUND | winapi::FILE_FLAG_OVERLAPPED, + winapi::PIPE_TYPE_BYTE | winapi::PIPE_READMODE_BYTE | winapi::PIPE_REJECT_REMOTE_CLIENTS, + // 1 max instance of this pipe + 1, + // out/in buffer sizes + 0, PIPE_BUFFER_SIZE as u32, + 0, // default timeout for WaitNamedPipe (0 == 50ms as default) + ptr::null_mut()); + if handle == INVALID_HANDLE_VALUE { + return Err(WinError::last("CreateNamedPipeA")); + } + + Ok(OsIpcReceiver { + reader: RefCell::new(MessageReader::new(WinHandle::new(handle))), + }) + } + } + + fn prepare_for_transfer(&mut self) -> Result { + let mut reader = self.reader.borrow_mut(); + // cancel any outstanding IO request + reader.cancel_io(); + // this is only okay if we have nothing in the read buf + Ok(reader.read_buf.is_empty()) + } + + pub fn consume(&self) -> OsIpcReceiver { + let mut handle = dup_handle(&self.reader.borrow().handle).unwrap(); + unsafe { OsIpcReceiver::from_handle(handle.take()) } + } + + fn receive_message(&self, mut block: bool) + -> Result<(Vec, Vec, Vec),WinError> { + // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then + // the implementation in select() is used. It does much the same thing, but across multiple + // channels. + + // This function loops, because in the case of a blocking read, we may need to + // read multiple sets of bytes from the pipe to receive a complete message. + unsafe { + let mut reader = self.reader.borrow_mut(); + assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); + + loop { + // First, try to fetch a message, in case we have one pending + // in the reader's receive buffer + match try!(reader.get_message()) { + GetMessageResult::Message(data, channels, shmems) => + return Ok((data, channels, shmems)), + GetMessageResult::NoMessage => + {}, + } + + // If the pipe was already closed, we're done -- we've + // already drained all incoming bytes + if reader.closed { + return Err(WinError::ChannelClosed); + } + + // Then, issue a read if we don't have one already in flight. + // We must not issue a read if we have complete unconsumed + // messages, because getting a message modifies the read_buf. + try!(reader.start_read()); + + // If the last read flagged us closed we're done; we've already + // drained all incoming bytes earlier in the loop. + if reader.closed { + return Err(WinError::ChannelClosed); + } + + // Then, get the overlapped result, blocking if we need to. + let mut nbytes: u32 = 0; + let mut err = winapi::ERROR_SUCCESS; + let ok = kernel32::GetOverlappedResult(*reader.handle, reader.ov.deref_mut(), &mut nbytes, + if block { winapi::TRUE } else { winapi::FALSE }); + if ok == winapi::FALSE { + err = GetLastError(); + if !block && err == winapi::ERROR_IO_INCOMPLETE { + // Nonblocking read, no message, read's in flight, we're + // done. An error is expected in this case. + return Err(WinError::NoData); + } + // We pass err through to notify_completion so + // that it can handle other errors. + } + + // Notify that the read completed, which will update the + // read pointers + try!(reader.notify_completion(err)); + + // If we're not blocking, pretend that we are blocking, since we got part of + // a message already. Keep reading until we get a complete message. + block = true; + } + } + } + + pub fn recv(&self) + -> Result<(Vec, Vec, Vec),WinError> { + win32_trace!("recv"); + self.receive_message(true) + } + + pub fn try_recv(&self) + -> Result<(Vec, Vec, Vec),WinError> { + win32_trace!("try_recv"); + self.receive_message(false) + } + + // Do a pipe connect. Only used for one-shot servers + fn accept(&mut self) -> Result<(),WinError> { + unsafe { + let reader_borrow = self.reader.borrow(); + let handle = *reader_borrow.handle; + let mut ov = Box::new(mem::zeroed::()); + let ok = kernel32::ConnectNamedPipe(handle, ov.deref_mut()); + + // we should always get FALSE with async IO + assert!(ok == winapi::FALSE); + let err = GetLastError(); + + match err { + // did we successfully connect? (it's reported as an error [ok==false]) + winapi::ERROR_PIPE_CONNECTED => { + win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle); + Ok(()) + }, + + // This is a weird one -- if we create a named pipe (like we do + // in new(), the client connects, sends data, then drops its handle, + // a Connect here will get ERROR_NO_DATA -- but there may be data in + // the pipe that we'll be able to read. So we need to go do some reads + // like normal and wait until ReadFile gives us ERROR_NO_DATA. + winapi::ERROR_NO_DATA => { + win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle); + Ok(()) + }, + + // was it an actual error? + err if err != winapi::ERROR_IO_PENDING => { + win32_trace!("[$ {:?}] accept error -> {}", handle, err); + Err(WinError::last("ConnectNamedPipe")) + }, + + // the connect is pending; wait for it to complete + _ /* winapi::ERROR_IO_PENDING */ => { + let mut nbytes: u32 = 0; + let ok = kernel32::GetOverlappedResult(handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); + if ok == winapi::FALSE { + return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); + } + Ok(()) + }, + } + } + } + + // Does a single explicitly-sized recv from the handle, consuming + // the receiver in the process. This is used for receiving data + // from the out-of-band big data buffer. + fn recv_raw(self, size: usize) -> Result, WinError> { + self.reader.borrow_mut().read_raw_sized(size) + } +} + +#[derive(Debug, PartialEq)] +pub struct OsIpcSender { + // The client hande itself + handle: WinHandle, + // Make sure this is `!Sync`, to match `mpsc::Sender`; and to discourage sharing references. + // + // (Rather, senders should just be cloned, as they are shared internally anyway -- + // another layer of sharing only adds unnecessary overhead...) + nosync_marker: PhantomData>, +} + +unsafe impl Send for OsIpcSender { } + +impl Clone for OsIpcSender { + fn clone(&self) -> OsIpcSender { + unsafe { + let mut handle = dup_handle(&self.handle).unwrap(); + OsIpcSender::from_handle(handle.take()) + } + } +} + +// Write_msg, unlike write_buf, requires that bytes be sent +// in one operation. +fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { + if bytes.len() == 0 { + return Ok(()); + } + + let mut size: u32 = 0; + unsafe { + if kernel32::WriteFile(handle, + bytes.as_ptr() as LPVOID, + bytes.len() as u32, + &mut size, + ptr::null_mut()) + == winapi::FALSE + { + return Err(WinError::last("WriteFile")); + } + } + + if size != bytes.len() as u32 { + panic!("Windows IPC write_msg expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", size, bytes.len()); + } + + Ok(()) +} + +fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { + let total = bytes.len(); + if total == 0 { + return Ok(()); + } + + let mut written = 0; + while written < total { + let mut sz: u32 = 0; + unsafe { + let bytes_to_write = &bytes[written..]; + if kernel32::WriteFile(handle, + bytes_to_write.as_ptr() as LPVOID, + bytes_to_write.len() as u32, + &mut sz, + ptr::null_mut()) + == winapi::FALSE + { + return Err(WinError::last("WriteFile")); + } + } + written += sz as usize; + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", handle, sz, written, bytes.len(), GetLastError()); + } + + Ok(()) +} + +impl OsIpcSender { + pub fn connect(name: String) -> Result { + let pipe_name = make_pipe_name(&Uuid::parse_str(&name).unwrap()); + OsIpcSender::connect_named(&pipe_name) + } + + pub fn get_max_fragment_size() -> usize { + MAX_FRAGMENT_SIZE + } + + unsafe fn from_handle(handle: HANDLE) -> OsIpcSender { + OsIpcSender { + handle: WinHandle::new(handle), + nosync_marker: PhantomData, + } + } + + // Connect to a pipe server + fn connect_named(pipe_name: &CString) -> Result { + unsafe { + let handle = + kernel32::CreateFileA(pipe_name.as_ptr(), + winapi::GENERIC_WRITE, + 0, + ptr::null_mut(), // lpSecurityAttributes + winapi::OPEN_EXISTING, + winapi::FILE_ATTRIBUTE_NORMAL, + ptr::null_mut()); + if handle == INVALID_HANDLE_VALUE { + return Err(WinError::last("CreateFileA")); + } + + win32_trace!("[c {:?}] connect_to_server success", handle); + + Ok(OsIpcSender::from_handle(handle)) + } + } + + fn get_pipe_server_process_id(&self) -> Result { + unsafe { + let mut server_pid: winapi::ULONG = 0; + if kernel32::GetNamedPipeServerProcessId(*self.handle, &mut server_pid) == winapi::FALSE { + return Err(WinError::last("GetNamedPipeServerProcessId")); + } + Ok(server_pid) + } + } + + fn get_pipe_server_process_handle_and_pid(&self) -> Result<(WinHandle, winapi::ULONG),WinError> { + unsafe { + let server_pid = try!(self.get_pipe_server_process_id()); + if server_pid == *CURRENT_PROCESS_ID { + return Ok((WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE), server_pid)); + } + + let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, + winapi::FALSE, + server_pid as winapi::DWORD); + if raw_handle.is_null() { + return Err(WinError::last("OpenProcess")); + } + + Ok((WinHandle::new(raw_handle), server_pid)) + } + } + + fn needs_fragmentation(data_len: usize, oob: &OutOfBandMessage) -> bool { + let oob_size = if oob.needs_to_be_sent() { bincode::serialized_size(oob).unwrap() } else { 0 }; + + // make sure we don't have too much oob data to begin with + assert!((oob_size as usize) < (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); + + let bytes_left_for_data = (PIPE_BUFFER_SIZE-MessageHeader::size()) - (oob_size as usize); + data_len >= bytes_left_for_data + } + + // An internal-use-only send method that sends just raw data, with + // no header. + fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { + win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, + try!(self.get_pipe_server_process_id())); + + write_buf(*self.handle, data) + } + + pub fn send(&self, + data: &[u8], + ports: Vec, + shared_memory_regions: Vec) + -> Result<(),WinError> + { + // We limit the max size we can send here; we can fix this + // just by upping the header to be 2x u64 if we really want + // to. + assert!(data.len() < u32::max_value() as usize); + + let (server_h, server_pid) = if !shared_memory_regions.is_empty() || !ports.is_empty() { + try!(self.get_pipe_server_process_handle_and_pid()) + } else { + (WinHandle::invalid(), 0) + }; + + let mut oob = OutOfBandMessage::new(server_pid); + + for ref shmem in shared_memory_regions { + // shmem.handle, shmem.length + let mut remote_handle = try!(dup_handle_to_process(&shmem.handle, &server_h)); + oob.shmem_handles.push((remote_handle.take() as intptr_t, shmem.length as u64)); + } + + for port in ports { + match port { + OsIpcChannel::Sender(mut s) => { + let mut raw_remote_handle = try!(move_handle_to_process(&mut s.handle, &server_h)); + oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + }, + OsIpcChannel::Receiver(mut r) => { + if try!(r.prepare_for_transfer()) == false { + panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); + } + + let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.borrow_mut().handle, &server_h)); + oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + }, + } + } + + // Do we need to fragment? + let big_data_sender: Option = + if OsIpcSender::needs_fragmentation(data.len(), &oob) { + // We need to create a channel for the big data + let (sender, receiver) = try!(channel()); + + let (server_h, server_pid) = if server_h.is_valid() { + (server_h, server_pid) + } else { + try!(self.get_pipe_server_process_handle_and_pid()) + }; + + // Put the receiver in the OOB data + let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.borrow_mut().handle, &server_h)); + oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); + oob.target_process_id = server_pid; + + Some(sender) + } else { + None + }; + + // If we need to send OOB data, serialize it + let mut oob_data: Vec = vec![]; + if oob.needs_to_be_sent() { + oob_data = bincode::serialize(&oob).unwrap(); + } + + unsafe { + let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; + let full_in_band_len = MessageHeader::size() + in_band_data_len + oob_data.len(); + assert!(full_in_band_len <= PIPE_BUFFER_SIZE); + + let mut full_message = Vec::::with_capacity(full_in_band_len); + full_message.set_len(full_in_band_len); + + let header = full_message.as_mut_ptr() as *mut MessageHeader; + *header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); + + if big_data_sender.is_none() { + &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); + &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); + try!(write_msg(*self.handle, &full_message)); + } else { + &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); + try!(write_msg(*self.handle, &full_message)); + try!(big_data_sender.unwrap().send_raw(data)); + } + } + + Ok(()) + } +} + +pub enum OsIpcSelectionResult { + DataReceived(u64, Vec, Vec, Vec), + ChannelClosed(u64), +} + +pub struct OsIpcReceiverSet { + // Our incrementor, for unique handle IDs + incrementor: RangeFrom, + + // the IOCP that we select on + iocp: WinHandle, + + // The set of receivers, stored as MessageReaders + readers: Vec, +} + +impl OsIpcReceiverSet { + pub fn new() -> Result { + unsafe { + let iocp = kernel32::CreateIoCompletionPort(INVALID_HANDLE_VALUE, + ptr::null_mut(), + 0 as winapi::ULONG_PTR, + 0); + if iocp.is_null() { + return Err(WinError::last("CreateIoCompletionPort")); + } + + Ok(OsIpcReceiverSet { + incrementor: 0.., + iocp: WinHandle::new(iocp), + readers: vec![], + }) + } + } + + pub fn add(&mut self, receiver: OsIpcReceiver) -> Result { + // consume the receiver, and take the reader out + let mut reader = receiver.reader.into_inner(); + + let set_id = self.incrementor.next().unwrap(); + try!(reader.add_to_iocp(*self.iocp, set_id)); + + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); + + self.readers.push(reader); + + Ok(set_id) + } + + pub fn select(&mut self) -> Result,WinError> { + assert!(!self.readers.is_empty(), "selecting with no objects?"); + win32_trace!("[# {:?}] select() with {} receivers", *self.iocp, self.readers.len()); + + // the ultimate results + let mut selection_results = vec![]; + + // Make a quick first-run check for any closed receivers. + // This will only happen if we have a receiver that + // gets added to the Set after it was closed (the + // router_drops_callbacks_on_cloned_sender_shutdown test + // causes this.) + self.readers.retain(|ref r| { + if r.closed { + selection_results.push(OsIpcSelectionResult::ChannelClosed(r.set_id.unwrap())); + false + } else { + true + } + }); + + // if we had prematurely closed elements, just process them first + if !selection_results.is_empty() { + return Ok(selection_results); + } + + // Do this in a loop, because we may need to dequeue multiple packets to + // read a complete message. + loop { + let mut nbytes: u32 = 0; + let mut reader_index: Option = None; + let mut io_err = winapi::ERROR_SUCCESS; + + unsafe { + let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; + let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); + // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! + let ok = kernel32::GetQueuedCompletionStatus(*self.iocp, + &mut nbytes, + &mut completion_key as *mut _ as *mut winapi::ULONG_PTR, + &mut ov_ptr, + winapi::INFINITE); + win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", *self.iocp, ok, nbytes, completion_key); + if ok == winapi::FALSE { + // If the OVERLAPPED result is NULL, then the + // function call itself failed or timed out. + // Otherwise, the async IO operation failed, and + // we want to hand io_err to notify_completion below. + if ov_ptr.is_null() { + return Err(WinError::last("GetQueuedCompletionStatus")); + } + + io_err = GetLastError(); + } + + assert!(!ov_ptr.is_null()); + assert!(completion_key != INVALID_HANDLE_VALUE); + + // Find the matching receiver + for (index, ref mut reader) in self.readers.iter_mut().enumerate() { + if completion_key != *reader.handle { + continue; + } + + reader_index = Some(index); + break; + } + } + + if reader_index.is_none() { + panic!("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); + } + + let mut remove_index = None; + + // We need a scope here for the mutable borrow of self.readers; + // we need to (maybe) remove an element from it below. + { + let reader_index = reader_index.unwrap(); + let mut reader = &mut self.readers[reader_index]; + + win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); + + // tell it about the completed IO op + try!(reader.notify_completion(io_err)); + + // then drain as many messages as we can + loop { + match try!(reader.get_message()) { + GetMessageResult::Message(data, channels, shmems) => { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + }, + GetMessageResult::NoMessage => { + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + break; + }, + } + } + + // We may have already been closed, or the read resulted in us being closed. + // If so, add that to the result and remove the reader from our list. + if reader.closed { + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); + remove_index = Some(reader_index); + } else { + try!(reader.start_read()); + } + } + + if remove_index.is_some() { + self.readers.swap_remove(remove_index.unwrap()); + } + + // if we didn't dequeue at least one complete message -- we need to loop through GetQueuedCS again; + // otherwise we're done. + if !selection_results.is_empty() { + break; + } + } + + win32_trace!("select() -> {} results", selection_results.len()); + Ok(selection_results) + } +} + +impl OsIpcSelectionResult { + pub fn unwrap(self) -> (u64, Vec, Vec, Vec) { + match self { + OsIpcSelectionResult::DataReceived(id, data, channels, shared_memory_regions) => { + (id, data, channels, shared_memory_regions) + } + OsIpcSelectionResult::ChannelClosed(id) => { + panic!("OsIpcSelectionResult::unwrap(): receiver ID {} was closed!", id) + } + } + } +} + +#[derive(Debug)] +pub struct OsIpcSharedMemory { + handle: WinHandle, + ptr: *mut u8, + length: usize, +} + +unsafe impl Send for OsIpcSharedMemory {} +unsafe impl Sync for OsIpcSharedMemory {} + +impl Drop for OsIpcSharedMemory { + fn drop(&mut self) { + unsafe { + kernel32::UnmapViewOfFile(self.ptr as LPVOID); + } + } +} + +impl Clone for OsIpcSharedMemory { + fn clone(&self) -> OsIpcSharedMemory { + unsafe { + let mut handle = dup_handle(&self.handle).unwrap(); + OsIpcSharedMemory::from_handle(handle.take(), self.length).unwrap() + } + } +} + +impl PartialEq for OsIpcSharedMemory { + fn eq(&self, other: &OsIpcSharedMemory) -> bool { + self.handle == other.handle + } +} + +impl Deref for OsIpcSharedMemory { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + assert!(!self.ptr.is_null() && self.handle.is_valid()); + unsafe { + slice::from_raw_parts(self.ptr, self.length) + } + } +} + +impl OsIpcSharedMemory { + #[allow(exceeding_bitshifts)] + fn new(length: usize) -> Result { + unsafe { + assert!(length < u32::max_value() as usize); + let (lhigh, llow) = (0 as u32, (length & 0xffffffffusize) as u32); + let handle = + kernel32::CreateFileMappingA(INVALID_HANDLE_VALUE, + ptr::null_mut(), + winapi::PAGE_READWRITE | winapi::SEC_COMMIT, + lhigh, llow, + ptr::null_mut()); + if handle == INVALID_HANDLE_VALUE { + return Err(WinError::last("CreateFileMapping")); + } + + OsIpcSharedMemory::from_handle(handle, length) + } + } + + // There is no easy way to query the size of the mapping -- you + // can use NtQuerySection, but that's an undocumented NT kernel + // API. Instead we'll just always pass the length along. + // + // This function takes ownership of the handle, and will close it + // when finished. + unsafe fn from_handle(handle_raw: HANDLE, length: usize) -> Result { + // turn this into a WinHandle, because that will + // take care of closing it + let handle = WinHandle::new(handle_raw); + let address = kernel32::MapViewOfFile(handle_raw, + winapi::FILE_MAP_ALL_ACCESS, + 0, 0, 0); + if address.is_null() { + return Err(WinError::last("MapViewOfFile")); + } + + Ok(OsIpcSharedMemory { + handle: handle, + ptr: address as *mut u8, + length: length + }) + } + + pub fn from_byte(byte: u8, length: usize) -> OsIpcSharedMemory { + unsafe { + // panic if we can't create it + let mem = OsIpcSharedMemory::new(length).unwrap(); + for element in slice::from_raw_parts_mut(mem.ptr, mem.length) { + *element = byte; + } + mem + } + } + + pub fn from_bytes(bytes: &[u8]) -> OsIpcSharedMemory { + unsafe { + // panic if we can't create it + let mem = OsIpcSharedMemory::new(bytes.len()).unwrap(); + ptr::copy_nonoverlapping(bytes.as_ptr(), mem.ptr, bytes.len()); + mem + } + } +} + +pub struct OsIpcOneShotServer { + receiver: OsIpcReceiver, +} + +impl OsIpcOneShotServer { + pub fn new() -> Result<(OsIpcOneShotServer, String),WinError> { + let pipe_id = make_pipe_id(); + let pipe_name = make_pipe_name(&pipe_id); + let receiver = try!(OsIpcReceiver::new_named(&pipe_name)); + Ok(( + OsIpcOneShotServer { + receiver: receiver, + }, + pipe_id.to_string() + )) + } + + pub fn accept(self) -> Result<(OsIpcReceiver, + Vec, + Vec, + Vec),WinError> { + let mut receiver = self.receiver; + try!(receiver.accept()); + let (data, channels, shmems) = try!(receiver.recv()); + Ok((receiver, data, channels, shmems)) + } +} + +pub enum OsIpcChannel { + Sender(OsIpcSender), + Receiver(OsIpcReceiver), +} + +#[derive(PartialEq, Debug)] +pub struct OsOpaqueIpcChannel { + handle: HANDLE, +} + +impl OsOpaqueIpcChannel { + fn new(handle: HANDLE) -> OsOpaqueIpcChannel { + OsOpaqueIpcChannel { + handle: handle, + } + } + + pub fn to_receiver(&mut self) -> OsIpcReceiver { + unsafe { OsIpcReceiver::from_handle(self.handle) } + } + + pub fn to_sender(&mut self) -> OsIpcSender { + unsafe { OsIpcSender::from_handle(self.handle) } + } +} + +#[derive(Clone, Copy, Debug)] +pub enum WinError { + WindowsResult(u32), + ChannelClosed, + NoData, +} + +impl WinError { + pub fn error_string(errnum: u32) -> String { + // This value is calculated from the macro + // MAKELANGID(LANG_SYSTEM_DEFAULT, SUBLANG_SYS_DEFAULT) + let lang_id = 0x0800 as winapi::DWORD; + let mut buf = [0 as winapi::WCHAR; 2048]; + + unsafe { + let res = kernel32::FormatMessageW(winapi::FORMAT_MESSAGE_FROM_SYSTEM | + winapi::FORMAT_MESSAGE_IGNORE_INSERTS, + ptr::null_mut(), + errnum as winapi::DWORD, + lang_id, + buf.as_mut_ptr(), + buf.len() as winapi::DWORD, + ptr::null_mut()) as usize; + if res == 0 { + // Sometimes FormatMessageW can fail e.g. system doesn't like lang_id, + let fm_err = kernel32::GetLastError(); + return format!("OS Error {} (FormatMessageW() returned error {})", + errnum, fm_err); + } + + match String::from_utf16(&buf[..res]) { + Ok(msg) => { + // Trim trailing CRLF inserted by FormatMessageW + msg.trim().to_string() + }, + Err(..) => format!("OS Error {} (FormatMessageW() returned \ + invalid UTF-16)", errnum), + } + } + } + + fn from_system(err: u32, f: &str) -> WinError { + win32_trace!("WinError: {} ({}) from {}", WinError::error_string(err), err, f); + WinError::WindowsResult(err) + } + + fn last(f: &str) -> WinError { + WinError::from_system(GetLastError(), f) + } + + pub fn channel_is_closed(&self) -> bool { + match *self { + WinError::ChannelClosed => true, + _ => false, + } + } +} + +impl From for bincode::Error { + fn from(error: WinError) -> bincode::Error { + Error::from(error).into() + } +} + +impl From for Error { + fn from(error: WinError) -> Error { + match error { + WinError::ChannelClosed => { + Error::new(ErrorKind::BrokenPipe, "Win channel closed") + }, + WinError::NoData => { + Error::new(ErrorKind::WouldBlock, "Win channel has no data available") + }, + WinError::WindowsResult(err) => { + Error::from_raw_os_error(err as i32) + }, + } + } +} diff --git a/src/test.rs b/src/test.rs index dffed783f..c8ac5d1c5 100644 --- a/src/test.rs +++ b/src/test.rs @@ -8,17 +8,17 @@ // except according to those terms. use ipc::{self, IpcReceiverSet, IpcSender, IpcSharedMemory}; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use ipc::IpcReceiver; use router::ROUTER; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use libc; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::cell::RefCell; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use std::env; use std::iter; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use std::process::{self, Command, Stdio}; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] use std::ptr; @@ -26,7 +26,7 @@ use std::sync::Arc; use std::sync::mpsc::{self, Sender}; use std::thread; -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] use ipc::IpcOneShotServer; #[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] @@ -61,7 +61,7 @@ impl Wait for libc::pid_t { // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] pub fn get_channel_name_arg(which: &str) -> Option { for arg in env::args() { let arg_str = &*format!("channel_name-{}:", which); @@ -74,7 +74,7 @@ pub fn get_channel_name_arg(which: &str) -> Option { // Helper to get a channel_name argument passed in; used for the // cross-process spawn server tests. -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] pub fn spawn_server(test_name: &str, server_args: &[(&str, &str)]) -> process::Child { Command::new(env::current_exe().unwrap()) .arg(test_name) @@ -169,7 +169,7 @@ fn select() { } } -#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))] +#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))] #[test] fn cross_process_embedded_senders_spawn() { let person = ("Patrick Walton".to_owned(), 29); @@ -357,7 +357,14 @@ fn shared_memory() { let (tx, rx) = ipc::channel().unwrap(); tx.send(person_and_shared_memory.clone()).unwrap(); let received_person_and_shared_memory = rx.recv().unwrap(); - assert_eq!(received_person_and_shared_memory, person_and_shared_memory); + // On Windows, we don't have a way to check whether two handles + // refer to the same underlying object before Windows 10. It's questionable + // if this test *really* wants that anyway. + if cfg!(not(windows)) { + assert_eq!(received_person_and_shared_memory, person_and_shared_memory); + } else { + assert_eq!(received_person_and_shared_memory.0, person_and_shared_memory.0); + } assert!(person_and_shared_memory.1.iter().all(|byte| *byte == 0xba)); assert!(received_person_and_shared_memory.1.iter().all(|byte| *byte == 0xba)); } From 5ceb21610d741f1f1c8477177dad3b1a9652d00a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 5 Nov 2017 15:00:44 +0100 Subject: [PATCH 011/109] appveyor.yml: Restore testing `inprocess` back-end, not only native one --- appveyor.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2b25ba4f3..2af4d1c64 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,8 +3,17 @@ environment: RUST_BACKTRACE: 1 matrix: - TARGET: x86_64-pc-windows-msvc + FEATURES: "unstable" - TARGET: i686-pc-windows-msvc + FEATURES: "unstable" - TARGET: i686-pc-windows-gnu + FEATURES: "unstable" + - TARGET: x86_64-pc-windows-msvc + FEATURES: "unstable force-inprocess" + - TARGET: i686-pc-windows-msvc + FEATURES: "unstable force-inprocess" + - TARGET: i686-pc-windows-gnu + FEATURES: "unstable force-inprocess" install: - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" @@ -14,4 +23,4 @@ install: build: false test_script: - - 'cargo test --verbose --features "unstable"' + - cargo test --verbose --features "%FEATURES%" From 68f6b0c92d2083e934fce87d71b1dee01363e7f6 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 18:56:07 +0200 Subject: [PATCH 012/109] tests: Split out SHM object equality check Move the check for finding equality of cloned/received SHM objects -- known to fail on Windows -- into a separate test case, rather than using a conditional in the main SHM test case. This makes it more explicit that this is a know limitation, and distinct from other SHM functionality. Also, more explicitly document this limitation in the relevant part of the Windows back-end implementation itself, rather than elaborating on it in the test case. --- src/platform/windows/mod.rs | 19 ++++++++++++++++--- src/test.rs | 21 +++++++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 573390749..217eb431a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -296,10 +296,16 @@ impl Deref for WinHandle { impl PartialEq for WinHandle { fn eq(&self, other: &WinHandle) -> bool { - // FIXME this is not correct! We need to compare the object - // the handles refer to. On Windows 10, we have: + // FIXME This does not actually implement the desired behaviour: + // we want a way to compare the underlying objects the handles refer to, + // rather than just comparing the handles. + // + // On Windows 10, we could use: + // ``` // unsafe { kernel32::CompareObjectHandles(self.h, other.h) == winapi::TRUE } - // But that + // ``` + // + // This API call however is not available on older versions. self.h == other.h } } @@ -1269,6 +1275,13 @@ impl Clone for OsIpcSharedMemory { impl PartialEq for OsIpcSharedMemory { fn eq(&self, other: &OsIpcSharedMemory) -> bool { + // Due to the way `WinHandle.eq()` is currently implemented, + // this only finds equality when comparing the very same SHM structure -- + // it doesn't recognize cloned SHM structures as equal. + // (Neither when cloned explicitly, nor implicitly through an IPC transfer.) + // + // It's not clear though whether the inability to test this + // is really a meaningful limitation... self.handle == other.handle } } diff --git a/src/test.rs b/src/test.rs index c8ac5d1c5..247bc62e7 100644 --- a/src/test.rs +++ b/src/test.rs @@ -357,18 +357,23 @@ fn shared_memory() { let (tx, rx) = ipc::channel().unwrap(); tx.send(person_and_shared_memory.clone()).unwrap(); let received_person_and_shared_memory = rx.recv().unwrap(); - // On Windows, we don't have a way to check whether two handles - // refer to the same underlying object before Windows 10. It's questionable - // if this test *really* wants that anyway. - if cfg!(not(windows)) { - assert_eq!(received_person_and_shared_memory, person_and_shared_memory); - } else { - assert_eq!(received_person_and_shared_memory.0, person_and_shared_memory.0); - } + assert_eq!(received_person_and_shared_memory.0, person_and_shared_memory.0); assert!(person_and_shared_memory.1.iter().all(|byte| *byte == 0xba)); assert!(received_person_and_shared_memory.1.iter().all(|byte| *byte == 0xba)); } +#[test] +// The Windows implementation can't handle this case due to system API limitations. +#[cfg_attr(all(target_os = "windows", not(feature = "force-inprocess")), ignore)] +fn shared_memory_object_equality() { + let person = ("Patrick Walton".to_owned(), 29); + let person_and_shared_memory = (person, IpcSharedMemory::from_byte(0xba, 1024 * 1024)); + let (tx, rx) = ipc::channel().unwrap(); + tx.send(person_and_shared_memory.clone()).unwrap(); + let received_person_and_shared_memory = rx.recv().unwrap(); + assert_eq!(received_person_and_shared_memory, person_and_shared_memory); +} + #[test] fn opaque_sender() { let person = ("Patrick Walton".to_owned(), 29); From ca0eea6d2d2780ff0a993e30890497ba381c3169 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 19:13:03 +0200 Subject: [PATCH 013/109] windows: fix warning: `reader` doesn't need to be mutable --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 217eb431a..9da2710c9 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1185,7 +1185,7 @@ impl OsIpcReceiverSet { // we need to (maybe) remove an element from it below. { let reader_index = reader_index.unwrap(); - let mut reader = &mut self.readers[reader_index]; + let reader = &mut self.readers[reader_index]; win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); From 774f9d0be0b05af8b26f9a331cb0d8fd430d9e45 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 22:51:57 +0200 Subject: [PATCH 014/109] windows: cleanup: Change suitable plain comments into doc comments Turn comments describing the purpose of data types, functions etc. into proper doc comments. Also restructure them as needed to fit the expected form for doc comments; and in some cases, clarify/enhance the contents too. --- src/platform/windows/mod.rs | 160 +++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 67 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 9da2710c9..19e6d786b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -33,21 +33,22 @@ lazy_static! { static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } -// some debug bump macros to better track what's going on in case of errors +/// Debug macro to better track what's going on in case of errors. macro_rules! win32_trace { ($($rest:tt)*) => { if cfg!(feature = "win32-trace") { if *DEBUG_TRACE_ENABLED { println!($($rest)*); } } } } -// When we create the pipe, how big of a write buffer do we specify? -// This is reserved in the nonpaged pool. The fragment size is the -// max we can write to the pipe without fragmentation, and the -// buffer size is what we tell the pipe it is, so we have room -// for out of band data etc. +/// When we create the pipe, how big of a write buffer do we specify? +/// +/// This is reserved in the nonpaged pool. The fragment size is the +/// max we can write to the pipe without fragmentation, and the +/// buffer size is what we tell the pipe it is, so we have room +/// for out of band data etc. const MAX_FRAGMENT_SIZE: usize = 64 * 1024; -// Size of the pipe's write buffer, with excess room for the header. +/// Size of the pipe's write buffer, with excess room for the header. const PIPE_BUFFER_SIZE: usize = MAX_FRAGMENT_SIZE + 4 * 1024; #[allow(non_snake_case)] @@ -67,7 +68,7 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { Ok((sender, receiver)) } -// Holds data len and out-of-band data len +/// Holds data len and out-of-band data len. struct MessageHeader(u32, u32); impl MessageHeader { @@ -137,26 +138,26 @@ impl<'data> Message<'data> { } } -// If we have any channel handles or shmem segments, then we'll send an -// OutOfBandMessage after the data message. -// -// This includes the receiver's process ID, which the receiver checks to -// make sure that the message was originally sent to it, and was not sitting -// in another channel's buffer when that channel got transferred to another -// process. On Windows, we duplicate handles on the sender side to a specific -// reciever. If the wrong receiver gets it, those handles are not valid. -// -// TODO(vlad): We could attempt to recover from the above situation by -// duplicating from the intended target process to ourselves (the receiver). -// That would only work if the intended process a) still exists; b) can be -// opened by the receiver with handle dup privileges. Another approach -// could be to use a separate dedicated process intended purely for handle -// passing, though that process would need to be global to any processes -// amongst which you want to share channels or connect one-shot servers to. -// There may be a system process that we could use for this purpose, but -// I haven't foundone -- and in the system process case, we'd need to ensure -// that we don't leak the handles (e.g. dup a handle to the system process, -// and then everything dies -- we don't want those resources to be leaked). +/// If we have any channel handles or shmem segments, then we'll send an +/// OutOfBandMessage after the data message. +/// +/// This includes the receiver's process ID, which the receiver checks to +/// make sure that the message was originally sent to it, and was not sitting +/// in another channel's buffer when that channel got transferred to another +/// process. On Windows, we duplicate handles on the sender side to a specific +/// reciever. If the wrong receiver gets it, those handles are not valid. +/// +/// TODO(vlad): We could attempt to recover from the above situation by +/// duplicating from the intended target process to ourselves (the receiver). +/// That would only work if the intended process a) still exists; b) can be +/// opened by the receiver with handle dup privileges. Another approach +/// could be to use a separate dedicated process intended purely for handle +/// passing, though that process would need to be global to any processes +/// amongst which you want to share channels or connect one-shot servers to. +/// There may be a system process that we could use for this purpose, but +/// I haven't found one -- and in the system process case, we'd need to ensure +/// that we don't leak the handles (e.g. dup a handle to the system process, +/// and then everything dies -- we don't want those resources to be leaked). #[derive(Debug)] struct OutOfBandMessage { target_process_id: u32, @@ -216,11 +217,11 @@ fn make_pipe_name(pipe_id: &Uuid) -> CString { CString::new(format!("\\\\.\\pipe\\rust-ipc-{}", pipe_id.to_string())).unwrap() } -// Duplicate a given handle from this process to the target one, passing the -// given flags to DuplicateHandle. -// -// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is -// also the pseudohandle for the current process). +/// Duplicate a given handle from this process to the target one, passing the +/// given flags to DuplicateHandle. +/// +/// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is +/// also the pseudohandle for the current process). unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE, flags: winapi::DWORD) -> Result { @@ -239,12 +240,12 @@ unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE } } -// duplicate a handle in the current process +/// Duplicate a handle in the current process. fn dup_handle(handle: &WinHandle) -> Result { dup_handle_to_process(handle, &WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE)) } -// duplicate a handle to the target process +/// Duplicate a handle to the target process. fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( @@ -253,7 +254,7 @@ fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Resul } } -// duplicate a handle to the target process, closing the source handle +/// Duplicate a handle to the target process, closing the source handle. fn move_handle_to_process(handle: &mut WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( @@ -333,29 +334,34 @@ enum GetMessageResult { Message(Vec, Vec, Vec), } -// MessageReader implements blocking/nonblocking reads of messages -// from the handle +/// Main object keeping track of a receive handle and its associated state. +/// +/// Implements blocking/nonblocking reads of messages from the handle. #[derive(Debug)] struct MessageReader { - // The pipe read handle + /// The pipe read handle. handle: WinHandle, - // The OVERLAPPED struct for async IO on this receiver; we'll only - // ever have one in flight + /// The OVERLAPPED struct for async IO on this receiver. + /// + /// We'll only ever have one in flight. ov: Box, - // A read buffer for any pending reads + /// A read buffer for any pending reads. read_buf: Vec, - // If we have already issued an async read + /// Whether we have already issued an async read. read_in_progress: bool, - // If we received a BROKEN_PIPE or other error - // indicating that the remote end has closed the pipe + /// Whether we received a BROKEN_PIPE or other error + /// indicating that the remote end has closed the pipe. closed: bool, - // If this is part of a Set, then this is the ID that is used to identify - // this reader. If this is None, then this isn't part of a set. + /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. + /// + /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. + /// + /// `None` if this `MessageReader` is not part of any set. set_id: Option, } @@ -380,7 +386,7 @@ impl MessageReader { } } - // Called when we receive an IO Completion Packet for this handle. + /// Called when we receive an IO Completion Packet for this handle. fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -415,7 +421,7 @@ impl MessageReader { Ok(()) } - // kick off an asynchronous read + /// Kick off an asynchronous read. fn start_read(&mut self) -> Result<(),WinError> { if self.read_in_progress || self.closed { return Ok(()); @@ -557,9 +563,12 @@ impl MessageReader { } } - // This is a specialized read when the buffser size is known ahead of time, - // and without our typical message framing. It's only valid to call this - // as the one and only call after creating a MessageReader. + /// Specialized read for out-of-band data ports. + /// + /// Here the buffer size is known in advance, + /// and the transfer doesn't have our typical message framing. + /// + /// It's only valid to call this as the one and only call after creating a MessageReader. fn read_raw_sized(&mut self, size: usize) -> Result,WinError> { assert!(self.read_buf.len() == 0); @@ -609,8 +618,16 @@ impl MessageReader { #[derive(Debug)] pub struct OsIpcReceiver { - // A MessageReader that implements most of the work of this - // MessageReader + /// The receive handle and its associated state. + /// + /// We can't just deal with raw handles like in the other platform back-ends, + /// since this implementation -- using plain pipes with no native packet handling -- + /// requires keeping track of various bits of receiver state, + /// which must not be separated from the handle itself. + /// + /// Note: Inner mutability is necessary, + /// since the `consume()` method needs to move out the reader + /// despite only getting a shared reference to `self`. reader: RefCell, } @@ -743,7 +760,9 @@ impl OsIpcReceiver { self.receive_message(false) } - // Do a pipe connect. Only used for one-shot servers + /// Do a pipe connect. + /// + /// Only used for one-shot servers. fn accept(&mut self) -> Result<(),WinError> { unsafe { let reader_borrow = self.reader.borrow(); @@ -791,9 +810,10 @@ impl OsIpcReceiver { } } - // Does a single explicitly-sized recv from the handle, consuming - // the receiver in the process. This is used for receiving data - // from the out-of-band big data buffer. + /// Does a single explicitly-sized recv from the handle, + /// consuming the receiver in the process. + /// + /// This is used for receiving data from the out-of-band big data buffer. fn recv_raw(self, size: usize) -> Result, WinError> { self.reader.borrow_mut().read_raw_sized(size) } @@ -801,7 +821,6 @@ impl OsIpcReceiver { #[derive(Debug, PartialEq)] pub struct OsIpcSender { - // The client hande itself handle: WinHandle, // Make sure this is `!Sync`, to match `mpsc::Sender`; and to discourage sharing references. // @@ -821,8 +840,12 @@ impl Clone for OsIpcSender { } } -// Write_msg, unlike write_buf, requires that bytes be sent -// in one operation. +/// Atomic write to a handle. +/// +/// Fails if the data can't be written in a single system call. +/// This is important, since otherwise concurrent sending +/// could result in parts of different messages getting intermixed, +/// and we would not be able to extract the individual messages. fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { if bytes.len() == 0 { return Ok(()); @@ -848,6 +871,10 @@ fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { Ok(()) } +/// Non-atomic write to a handle. +/// +/// Can be used for writes to an exclusive pipe, +/// where the send being split up into several calls poses no danger. fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { let total = bytes.len(); if total == 0 { @@ -893,7 +920,7 @@ impl OsIpcSender { } } - // Connect to a pipe server + /// Connect to a pipe server. fn connect_named(pipe_name: &CString) -> Result { unsafe { let handle = @@ -952,8 +979,7 @@ impl OsIpcSender { data_len >= bytes_left_for_data } - // An internal-use-only send method that sends just raw data, with - // no header. + /// An internal-use-only send method that sends just raw data, with no header. fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); @@ -1063,13 +1089,13 @@ pub enum OsIpcSelectionResult { } pub struct OsIpcReceiverSet { - // Our incrementor, for unique handle IDs + /// Our incrementor, for unique handle IDs. incrementor: RangeFrom, - // the IOCP that we select on + /// The IOCP that we select on. iocp: WinHandle, - // The set of receivers, stored as MessageReaders + /// The set of receivers, stored as MessageReaders. readers: Vec, } From 7ee7690da8a398e8a5b4878a930bde4570b5f173 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 23 Sep 2017 22:57:08 +0200 Subject: [PATCH 015/109] windows: cleanup: Use more standard indentation for macro --- src/platform/windows/mod.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 19e6d786b..fc01d4081 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -34,11 +34,13 @@ lazy_static! { } /// Debug macro to better track what's going on in case of errors. -macro_rules! win32_trace { ($($rest:tt)*) => { - if cfg!(feature = "win32-trace") { - if *DEBUG_TRACE_ENABLED { println!($($rest)*); } +macro_rules! win32_trace { + ($($rest:tt)*) => { + if cfg!(feature = "win32-trace") { + if *DEBUG_TRACE_ENABLED { println!($($rest)*); } + } } -} } +} /// When we create the pipe, how big of a write buffer do we specify? /// From 05fedf696b4a4a044dcfb69b5bb510bb5a12937e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 14:19:00 +0200 Subject: [PATCH 016/109] windows: Move out original handle in `to_receiver()` and `to_sender()` Like in the other back-ends, remove (`mem::replae()`) the original handle while constructing the wrappers, to make sure we do not keep around a copy that might interfere if reused accidentally. --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index fc01d4081..1b5537b25 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1437,11 +1437,11 @@ impl OsOpaqueIpcChannel { } pub fn to_receiver(&mut self) -> OsIpcReceiver { - unsafe { OsIpcReceiver::from_handle(self.handle) } + unsafe { OsIpcReceiver::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } } pub fn to_sender(&mut self) -> OsIpcSender { - unsafe { OsIpcSender::from_handle(self.handle) } + unsafe { OsIpcSender::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } } } From 2df82b9eaaf7e9f6ea86a38b71d736a8c17c9fc2 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 14:46:17 +0200 Subject: [PATCH 017/109] windows: Drop unnecessary `&mut self` in a few places Use `&self` rather than `&mut self` in some private methods that do not actually modify the main object. (Mostly because they rely on inner mutability.) This also removes the need for `mut` in a few places in the callers. --- src/platform/windows/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 1b5537b25..a1352495c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -671,7 +671,7 @@ impl OsIpcReceiver { } } - fn prepare_for_transfer(&mut self) -> Result { + fn prepare_for_transfer(&self) -> Result { let mut reader = self.reader.borrow_mut(); // cancel any outstanding IO request reader.cancel_io(); @@ -765,7 +765,7 @@ impl OsIpcReceiver { /// Do a pipe connect. /// /// Only used for one-shot servers. - fn accept(&mut self) -> Result<(),WinError> { + fn accept(&self) -> Result<(),WinError> { unsafe { let reader_borrow = self.reader.borrow(); let handle = *reader_borrow.handle; @@ -1020,7 +1020,7 @@ impl OsIpcSender { let mut raw_remote_handle = try!(move_handle_to_process(&mut s.handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, - OsIpcChannel::Receiver(mut r) => { + OsIpcChannel::Receiver(r) => { if try!(r.prepare_for_transfer()) == false { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } @@ -1412,7 +1412,7 @@ impl OsIpcOneShotServer { Vec, Vec, Vec),WinError> { - let mut receiver = self.receiver; + let receiver = self.receiver; try!(receiver.accept()); let (data, channels, shmems) = try!(receiver.recv()); Ok((receiver, data, channels, shmems)) From 93fd37e9d42eed228eb89a746482a827478d0c85 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 15:31:10 +0200 Subject: [PATCH 018/109] windows: cleanup: Switch methods for a more logical order --- src/platform/windows/mod.rs | 70 ++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index a1352495c..cfaa25d55 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -388,41 +388,6 @@ impl MessageReader { } } - /// Called when we receive an IO Completion Packet for this handle. - fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { - win32_trace!("[$ {:?}] notify_completion", self.handle); - - // mark a read as no longer in progress even before we check errors - self.read_in_progress = false; - - if err == winapi::ERROR_BROKEN_PIPE { - assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); - self.closed = true; - return Ok(()); - } - - let nbytes = self.ov.InternalHigh as u32; - let offset = self.ov.Offset; - - assert!(offset == 0); - - // if the remote end closed... - if err != winapi::ERROR_SUCCESS { - // This should never happen - panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); - } - - unsafe { - let new_size = self.read_buf.len() + nbytes as usize; - win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", - nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); - assert!(new_size <= self.read_buf.capacity()); - self.read_buf.set_len(new_size); - } - - Ok(()) - } - /// Kick off an asynchronous read. fn start_read(&mut self) -> Result<(),WinError> { if self.read_in_progress || self.closed { @@ -484,6 +449,41 @@ impl MessageReader { } } + /// Called when we receive an IO Completion Packet for this handle. + fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + win32_trace!("[$ {:?}] notify_completion", self.handle); + + // mark a read as no longer in progress even before we check errors + self.read_in_progress = false; + + if err == winapi::ERROR_BROKEN_PIPE { + assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); + self.closed = true; + return Ok(()); + } + + let nbytes = self.ov.InternalHigh as u32; + let offset = self.ov.Offset; + + assert!(offset == 0); + + // if the remote end closed... + if err != winapi::ERROR_SUCCESS { + // This should never happen + panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); + } + + unsafe { + let new_size = self.read_buf.len() + nbytes as usize; + win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", + nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); + assert!(new_size <= self.read_buf.capacity()); + self.read_buf.set_len(new_size); + } + + Ok(()) + } + // This is split between get_message and get_message_inner, so that // this function can handle removing bytes from the buffer, since // get_message_inner borrows the buffer. From f9f1eb84d55153852035d507604c287dae83c25c Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 15:37:21 +0200 Subject: [PATCH 019/109] windows: Mark `notify_completion()` as unsafe This method could yield uninitialised data when invoked incorrectly. --- src/platform/windows/mod.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index cfaa25d55..42eaf48b2 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -450,7 +450,12 @@ impl MessageReader { } /// Called when we receive an IO Completion Packet for this handle. - fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + /// + /// Unsafe, since calling this with an invalid object or at the wrong time + /// could result in uninitialized data being passed off as valid. + /// While this may seem less critical than other memory errors, + /// it can also break type safety. + unsafe fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { win32_trace!("[$ {:?}] notify_completion", self.handle); // mark a read as no longer in progress even before we check errors @@ -473,13 +478,11 @@ impl MessageReader { panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); } - unsafe { - let new_size = self.read_buf.len() + nbytes as usize; - win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", - nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); - assert!(new_size <= self.read_buf.capacity()); - self.read_buf.set_len(new_size); - } + let new_size = self.read_buf.len() + nbytes as usize; + win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", + nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); + assert!(new_size <= self.read_buf.capacity()); + self.read_buf.set_len(new_size); Ok(()) } @@ -1218,7 +1221,7 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); // tell it about the completed IO op - try!(reader.notify_completion(io_err)); + unsafe { try!(reader.notify_completion(io_err)); } // then drain as many messages as we can loop { From c276d7f56aa5de68824ae34308e0950e6de36dcb Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 16:19:23 +0200 Subject: [PATCH 020/109] windows: Widen too narrow `unsafe` block Move some more calculations inside the unsafe block, since the other unsafe code relies upon them to be correct in order for soundness to be ensured. --- src/platform/windows/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 42eaf48b2..2635a4e39 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -396,17 +396,17 @@ impl MessageReader { win32_trace!("[$ {:?}] start_read", self.handle); - let buf_len = self.read_buf.len(); - let mut buf_cap = self.read_buf.capacity(); - let mut bytes_read: u32 = 0; + unsafe { + let buf_len = self.read_buf.len(); + let mut buf_cap = self.read_buf.capacity(); + let mut bytes_read: u32 = 0; - if buf_len == buf_cap { - self.read_buf.reserve(PIPE_BUFFER_SIZE); - buf_cap = self.read_buf.capacity(); - } + if buf_len == buf_cap { + self.read_buf.reserve(PIPE_BUFFER_SIZE); + buf_cap = self.read_buf.capacity(); + } - // issue the read to the buffer, at the current length offset - unsafe { + // issue the read to the buffer, at the current length offset *self.ov.deref_mut() = mem::zeroed(); let buf_ptr = self.read_buf.as_mut_ptr() as LPVOID; let max_read_bytes = buf_cap - buf_len; From 256e6b6445d78faa14503e52ae0123be81a54ed7 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 16:27:49 +0200 Subject: [PATCH 021/109] windows: Drop custom `GetMessageResult` type Just use a tuple in an `Option<>` instead. The named type didn't really add anything here -- it only made the code harder to follow. (The original introduction of this custom type resulted from a communication failure...) --- src/platform/windows/mod.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 2635a4e39..abb94846c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -331,11 +331,6 @@ impl WinHandle { } } -enum GetMessageResult { - NoMessage, - Message(Vec, Vec, Vec), -} - /// Main object keeping track of a receive handle and its associated state. /// /// Implements blocking/nonblocking reads of messages from the handle. @@ -490,7 +485,8 @@ impl MessageReader { // This is split between get_message and get_message_inner, so that // this function can handle removing bytes from the buffer, since // get_message_inner borrows the buffer. - fn get_message(&mut self) -> Result { + fn get_message(&mut self) -> Result, Vec, Vec)>, + WinError> { let drain_bytes; let result; if let Some(message) = Message::from_bytes(&self.read_buf) { @@ -525,10 +521,10 @@ impl MessageReader { win32_trace!("[$ {:?}] get_message success -> {} bytes, {} channels, {} shmems", self.handle, buf_data.len(), channels.len(), shmems.len()); drain_bytes = Some(message.size()); - result = GetMessageResult::Message(buf_data, channels, shmems); + result = Some((buf_data, channels, shmems)); } else { drain_bytes = None; - result = GetMessageResult::NoMessage; + result = None; } if let Some(size) = drain_bytes { @@ -703,9 +699,9 @@ impl OsIpcReceiver { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer match try!(reader.get_message()) { - GetMessageResult::Message(data, channels, shmems) => + Some((data, channels, shmems)) => return Ok((data, channels, shmems)), - GetMessageResult::NoMessage => + None => {}, } @@ -1226,11 +1222,11 @@ impl OsIpcReceiverSet { // then drain as many messages as we can loop { match try!(reader.get_message()) { - GetMessageResult::Message(data, channels, shmems) => { + Some((data, channels, shmems)) => { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); }, - GetMessageResult::NoMessage => { + None => { win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); break; }, From d65a9273790ae6fa956ce2ffa635be9d8b4ed069 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 20:03:57 +0200 Subject: [PATCH 022/109] windows: More defensive receive buffer handling Extend the buffer's exposed length to cover its entire allocated capacity before using it in receive calls, so we can use safe slice operations rather than manual pointer arithmetic for determining addresses and lengths to be passed to the system calls. To keep the effects localised, we reset the length to the actually filled part again after the system calls, rather than keeping it at the full allocated size permanently. I'm not entirely sure yet whether to consider that more or less defensive than the other option -- but at least I'm confident that it's more robust than the original approach. --- src/platform/windows/mod.rs | 69 ++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index abb94846c..5e0ac42b3 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -391,25 +391,43 @@ impl MessageReader { win32_trace!("[$ {:?}] start_read", self.handle); + if self.read_buf.len() == self.read_buf.capacity() { + self.read_buf.reserve(PIPE_BUFFER_SIZE); + } + unsafe { + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. let buf_len = self.read_buf.len(); - let mut buf_cap = self.read_buf.capacity(); - let mut bytes_read: u32 = 0; - - if buf_len == buf_cap { - self.read_buf.reserve(PIPE_BUFFER_SIZE); - buf_cap = self.read_buf.capacity(); - } + let buf_cap = self.read_buf.capacity(); + self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset *self.ov.deref_mut() = mem::zeroed(); - let buf_ptr = self.read_buf.as_mut_ptr() as LPVOID; - let max_read_bytes = buf_cap - buf_len; - let ok = kernel32::ReadFile(*self.handle, - buf_ptr.offset(buf_len as isize), - max_read_bytes as u32, - &mut bytes_read, - self.ov.deref_mut()); + let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut self.read_buf[buf_len..]; + kernel32::ReadFile(*self.handle, + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + self.ov.deref_mut()) + }; + + // Reset the vector to only expose the already filled part. + // + // This means that the async read + // will actually fill memory beyond the exposed part of the vector. + // While this use of a vector is officially sanctioned for such cases, + // it still feel rather icky to me... + // + // On the other hand, this way we make sure + // the buffer never appears to have more valid data + // than what is actually present, + // which could pose a potential danger in its own right. + // Also, it avoids the need to keep a separate state variable -- + // which would bear some risk of getting out of sync. + self.read_buf.set_len(buf_len); // ReadFile can return TRUE; if it does, an IO completion // packet is still posted to any port, and the OVERLAPPED @@ -584,17 +602,26 @@ impl MessageReader { let ov = self.ov.deref_mut(); *ov = mem::zeroed(); + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. let buf_len = buf.len(); - let dest_ptr = buf.as_mut_ptr().offset(buf_len as isize) as LPVOID; + let buf_cap = buf.capacity(); + buf.set_len(buf_cap); - let bytes_left = (size - buf_len) as u32; let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut buf[buf_len..]; + kernel32::ReadFile(*self.handle, + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + ov) + }; + + // Restore the original size before error handling, + // so we never leave the function with the buffer exposing uninitialized data. + buf.set_len(buf_len); - let ok = kernel32::ReadFile(*self.handle, - dest_ptr, - bytes_left, - &mut bytes_read, - ov); if ok == winapi::FALSE && GetLastError() != winapi::ERROR_IO_PENDING { return Err(WinError::last("ReadFile")); } From 524ef1034276efd410e8ec502ab5436f6d9bee11 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 20:50:59 +0200 Subject: [PATCH 023/109] [RemoveMe] Temporarily disable most CI targets No need to generate spare heat while I'm working only on the windows back-end... --- .travis.yml | 7 ------- appveyor.yml | 10 ---------- 2 files changed, 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index ccec31388..bc367972f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,19 +6,12 @@ rust: os: - linux - - osx env: global: - RUST_BACKTRACE=1 matrix: - FEATURES="unstable" - - FEATURES="unstable force-inprocess" - -matrix: - include: - - os: linux - env: FEATURES="unstable memfd" notifications: webhooks: http://build.servo.org:54856/travis diff --git a/appveyor.yml b/appveyor.yml index 2af4d1c64..445910295 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,16 +4,6 @@ environment: matrix: - TARGET: x86_64-pc-windows-msvc FEATURES: "unstable" - - TARGET: i686-pc-windows-msvc - FEATURES: "unstable" - - TARGET: i686-pc-windows-gnu - FEATURES: "unstable" - - TARGET: x86_64-pc-windows-msvc - FEATURES: "unstable force-inprocess" - - TARGET: i686-pc-windows-msvc - FEATURES: "unstable force-inprocess" - - TARGET: i686-pc-windows-gnu - FEATURES: "unstable force-inprocess" install: - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" From f3360f4a1cf5519406d7ff3171759e3fbc8f4269 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 27 Sep 2017 23:40:40 +0200 Subject: [PATCH 024/109] windows: Add explanation why the `ov` structure needs to be boxed --- src/platform/windows/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 5e0ac42b3..e9ba1a442 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -342,6 +342,10 @@ struct MessageReader { /// The OVERLAPPED struct for async IO on this receiver. /// /// We'll only ever have one in flight. + /// + /// This must be on the heap, so its memory location -- + /// which is registered in the kernel during an async read -- + /// remains stable even when the enclosing structure is passed around. ov: Box, /// A read buffer for any pending reads. From 34eb0c3121e30a7d6d62b837678bb9d2600ca6fc Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 27 Sep 2017 23:41:26 +0200 Subject: [PATCH 025/109] windows: Implement `Drop` for `MessageReader` Make sure any outstanding async I/O operation is cancelled before freeing the memory of the `ov` structure and receive buffer. This is an important safety fix: leaving the operation pending after the memory is freed might result in the kernel later writing to memory locations that are no longer valid. --- src/platform/windows/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e9ba1a442..b4fda9d89 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -366,6 +366,14 @@ struct MessageReader { set_id: Option, } +impl Drop for MessageReader { + fn drop(&mut self) { + // Before dropping the `ov` structure and read buffer, + // make sure the kernel won't do any more async updates to them! + self.cancel_io(); + } +} + impl MessageReader { fn new(handle: WinHandle) -> MessageReader { MessageReader { From c36e495768c23fa371370a8cab7dc78ce938152c Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Thu, 28 Sep 2017 23:16:01 +0200 Subject: [PATCH 026/109] windows: Consume reader immediately when moving out handle When moving the associated handle to another process, immediately consume the `MessageReader` containing it, so we do not leave the reader hanging around with an invalid handle. While this doesn't really change much in practice -- the reader was dropped along with the receiver shortly after anyway -- it's cleaner semantically, and should be a tick more robust. --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b4fda9d89..f4514212a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1063,7 +1063,7 @@ impl OsIpcSender { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.borrow_mut().handle, &server_h)); + let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.into_inner().handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, } @@ -1082,7 +1082,7 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.borrow_mut().handle, &server_h)); + let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.into_inner().handle, &server_h)); oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; From ad35a327ea8d97b2a07a9db6c6d4e90fc7177b60 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 15:18:16 +0200 Subject: [PATCH 027/109] windows: Take ownership of handle in `move_handle_to_process()` More explicitly reflect the fact that this function consumes (moves) the original handle. --- src/platform/windows/mod.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f4514212a..b9744c2a2 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -257,7 +257,7 @@ fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Resul } /// Duplicate a handle to the target process, closing the source handle. -fn move_handle_to_process(handle: &mut WinHandle, other_process: &WinHandle) -> Result { +fn move_handle_to_process(mut handle: WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( handle.take(), **other_process, @@ -1054,8 +1054,8 @@ impl OsIpcSender { for port in ports { match port { - OsIpcChannel::Sender(mut s) => { - let mut raw_remote_handle = try!(move_handle_to_process(&mut s.handle, &server_h)); + OsIpcChannel::Sender(s) => { + let mut raw_remote_handle = try!(move_handle_to_process(s.handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, OsIpcChannel::Receiver(r) => { @@ -1063,7 +1063,8 @@ impl OsIpcSender { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let mut raw_remote_handle = try!(move_handle_to_process(&mut r.reader.into_inner().handle, &server_h)); + let handle = WinHandle::new(r.reader.into_inner().handle.take()); + let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take() as intptr_t); }, } @@ -1082,7 +1083,8 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let mut raw_receiver_handle = try!(move_handle_to_process(&mut receiver.reader.into_inner().handle, &server_h)); + let handle = WinHandle::new(receiver.reader.into_inner().handle.take()); + let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; From 2776819c2c9f2dae1e5be43c897de854620ae01a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 14:55:51 +0200 Subject: [PATCH 028/109] windows: Assert valid state before consuming receiver --- src/platform/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b9744c2a2..d6bd22fed 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -718,7 +718,9 @@ impl OsIpcReceiver { } pub fn consume(&self) -> OsIpcReceiver { - let mut handle = dup_handle(&self.reader.borrow().handle).unwrap(); + let reader = self.reader.borrow(); + assert!(!reader.read_in_progress); + let mut handle = dup_handle(&reader.handle).unwrap(); unsafe { OsIpcReceiver::from_handle(handle.take()) } } From 592e43c130eda1b488cba9af3b6c98e58e59360d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 15:00:43 +0200 Subject: [PATCH 029/109] windows: Don't duplicate handle when consuming receiver The temporary duplicate was only necessary as a workaround, to prevent the new `OsIpcReceiver` copy from ending up with an invalid handle, when the original receiver gets dropped. (Specifically, the original receiver gets dropped after serialisation, before the actual transfer happens using the copy...) However, now we just can use inner mutability to actually unset the handle value in the original `OsIpcReceiver` struct (since the handle now lives inside a `RefCell` as part of `MessageReader`) -- so there is no longer a danger of the handle being dropped prematurely along with the original receiver. --- src/platform/windows/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d6bd22fed..9dec4f79e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -718,10 +718,9 @@ impl OsIpcReceiver { } pub fn consume(&self) -> OsIpcReceiver { - let reader = self.reader.borrow(); + let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - let mut handle = dup_handle(&reader.handle).unwrap(); - unsafe { OsIpcReceiver::from_handle(handle.take()) } + unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } } fn receive_message(&self, mut block: bool) From 96f9cc2bfeaa62be3902abfc6e2ae4197de72a27 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 24 Sep 2017 21:00:41 +0200 Subject: [PATCH 030/109] windows: Remove bogus explicit `impl Send` for `OsIpcSender` `OsIpcSender` is automatically `Sync`, since all its constituents are. We aren't doing anything on top of that to ensure it can indeed be shared -- so declaring `Sync` explicitly is wrong, and could obscure problems if the inherent `Sync` properties ever change for some reason. --- src/platform/windows/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 9dec4f79e..90623e676 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -870,8 +870,6 @@ pub struct OsIpcSender { nosync_marker: PhantomData>, } -unsafe impl Send for OsIpcSender { } - impl Clone for OsIpcSender { fn clone(&self) -> OsIpcSender { unsafe { From 6c0cd9c3fb8daf1fb383a277b416a9ca138a823e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 14:10:51 +0200 Subject: [PATCH 031/109] windows: Add a comment explaining the `impl Send` on `OsIpcReceiver` --- src/platform/windows/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 90623e676..06e6d2384 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -671,6 +671,19 @@ pub struct OsIpcReceiver { reader: RefCell, } +// We need to explicitly declare this, because of the raw pointer +// contained in the `OVERLAPPED` structure inside `MessageReader`. +// +// Note: the `Send` claim is only really fulfilled +// as long as nothing can ever alias the aforementioned raw pointer. +// While this seems to be true as far as I can tell, +// it's a rather fragile condition, which should be managed much more tightly +// (along with `OVERLAPPED` in general): at `MessageReader` level, at the very most. +// The current implementation doesn't follow such a strict encapsulation however, +// with `OsIpcReceiver` directly accessing `reader.ov` (in `receive_message()`), +// outside the `MessageReader` implementation -- +// so for now, `OsIpcReceiver` needs to be considered responsible as a whole +// for upholding the non-aliasing condition. unsafe impl Send for OsIpcReceiver { } impl PartialEq for OsIpcReceiver { From ccbd2eedeb3e95e2fabb46f27537bf59b3000b5f Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 30 Sep 2017 18:44:51 +0200 Subject: [PATCH 032/109] windows: cleanup: Use enum rather than anonymous bool for blocking mode parameter This is more in line with the other back-ends, as well as general Rust conventions. --- src/platform/windows/mod.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 06e6d2384..b08a3e8ca 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -656,6 +656,12 @@ impl MessageReader { } } +#[derive(Clone, Copy, Debug, PartialEq)] +enum BlockingMode { + Blocking, + Nonblocking, +} + #[derive(Debug)] pub struct OsIpcReceiver { /// The receive handle and its associated state. @@ -736,7 +742,7 @@ impl OsIpcReceiver { unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } } - fn receive_message(&self, mut block: bool) + fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then // the implementation in select() is used. It does much the same thing, but across multiple @@ -778,11 +784,17 @@ impl OsIpcReceiver { // Then, get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; let mut err = winapi::ERROR_SUCCESS; - let ok = kernel32::GetOverlappedResult(*reader.handle, reader.ov.deref_mut(), &mut nbytes, - if block { winapi::TRUE } else { winapi::FALSE }); + let block = match blocking_mode { + BlockingMode::Blocking => winapi::TRUE, + BlockingMode::Nonblocking => winapi::FALSE, + }; + let ok = kernel32::GetOverlappedResult(*reader.handle, + reader.ov.deref_mut(), + &mut nbytes, + block); if ok == winapi::FALSE { err = GetLastError(); - if !block && err == winapi::ERROR_IO_INCOMPLETE { + if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Nonblocking read, no message, read's in flight, we're // done. An error is expected in this case. return Err(WinError::NoData); @@ -797,7 +809,7 @@ impl OsIpcReceiver { // If we're not blocking, pretend that we are blocking, since we got part of // a message already. Keep reading until we get a complete message. - block = true; + blocking_mode = BlockingMode::Blocking; } } } @@ -805,13 +817,13 @@ impl OsIpcReceiver { pub fn recv(&self) -> Result<(Vec, Vec, Vec),WinError> { win32_trace!("recv"); - self.receive_message(true) + self.receive_message(BlockingMode::Blocking) } pub fn try_recv(&self) -> Result<(Vec, Vec, Vec),WinError> { win32_trace!("try_recv"); - self.receive_message(false) + self.receive_message(BlockingMode::Nonblocking) } /// Do a pipe connect. From 7a6f069c22d5364d9769df49a9a29e2a72b8b227 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 1 Oct 2017 18:18:17 +0200 Subject: [PATCH 033/109] windows: Improve placement of comments in `receive_message()` --- src/platform/windows/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b08a3e8ca..db7b4cf0c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -742,18 +742,17 @@ impl OsIpcReceiver { unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } } + // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then + // the implementation in select() is used. It does much the same thing, but across multiple + // channels. fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { - // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then - // the implementation in select() is used. It does much the same thing, but across multiple - // channels. - - // This function loops, because in the case of a blocking read, we may need to - // read multiple sets of bytes from the pipe to receive a complete message. unsafe { let mut reader = self.reader.borrow_mut(); assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); + // This function loops, because in the case of a blocking read, we may need to + // read multiple sets of bytes from the pipe to receive a complete message. loop { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer From 3fde2fc4765c9c8ee20a477efe8fd0af7ae330b3 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 21:24:40 +0200 Subject: [PATCH 034/109] windows: Add checks and warnings regarding dangers of `ov` and `read_buf` The whole thing is highly unsafe. To handle it *properly*, we would need to encapsulate these values such that nothing else can access them while there is an outstanding async read in progress with the kernel... However, that's quite tricky to achieve -- so for now, the best we can do is putting big fat warnings everywhere, and adding extra safety checks where possible. --- src/platform/windows/mod.rs | 207 ++++++++++++++++++++++++------------ 1 file changed, 137 insertions(+), 70 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index db7b4cf0c..5d729587d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -343,15 +343,39 @@ struct MessageReader { /// /// We'll only ever have one in flight. /// - /// This must be on the heap, so its memory location -- + /// This must be on the heap, in order for its memory location -- /// which is registered in the kernel during an async read -- - /// remains stable even when the enclosing structure is passed around. + /// to remain stable even when the enclosing structure is passed around. + /// + /// WARNING: As the kernel holds a mutable alias of this structure + /// while an async read is in progress, + /// it is crucial that this value is never accessed in user space + /// from the moment we issue an async read in `start_read()`, + /// until the moment we process the event + /// signalling completion of the async read in `notify_completion()`. + /// + /// Since Rust's type system is not aware of the kernel alias, + /// the compiler cannot guarantee exclusive access the way it normally would, + /// i.e. any access to this value is inherently unsafe! + /// The only way to avoid undefined behaviour + /// is to always make sure the `read_in_progress` indicator is not set, + /// before performing any access to this value. + /// + /// (Unfortunately, there is no way to express this condition in the type system, + /// without some fundamental change to how we handle these fields...) ov: Box, /// A read buffer for any pending reads. + /// + /// WARNING: This has the same safety problem as `ov` above. read_buf: Vec, - /// Whether we have already issued an async read. + /// Indicates whether the kernel currently has an async read in flight for this port. + /// + /// WARNING: Rather than just managing our internal state, + /// this flag plays a critical role in keeping track of kernel aliasing + /// of the `ov` and `read_buf` fields, as explained in the comment for `ov'. + /// Thus it is crucial that we always set this correctly! read_in_progress: bool, /// Whether we received a BROKEN_PIPE or other error @@ -396,7 +420,22 @@ impl MessageReader { } /// Kick off an asynchronous read. - fn start_read(&mut self) -> Result<(),WinError> { + /// + /// Note: This is *highly* unsafe, since upon successful return, + /// the `ov` and `read_buf` fields will be left mutably aliased by the kernel + /// (until we receive an event signalling completion of the async read) -- + /// and Rust's type system doesn't know about these aliases! + /// + /// This means that after invoking this method, + /// up to the point where we receive the completion notification, + /// nothing is allowed to access the `ov` and `read_buf` fields; + /// but the compiler cannot guarantee this for us. + /// It is our responsibility to make sure of it -- + /// i.e. all code on the path from invoking this method, + /// up to receiving the completion event, is unsafe. + /// + /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + unsafe fn start_read(&mut self) -> Result<(),WinError> { if self.read_in_progress || self.closed { return Ok(()); } @@ -407,83 +446,92 @@ impl MessageReader { self.read_buf.reserve(PIPE_BUFFER_SIZE); } - unsafe { - // Temporarily extend the vector to span its entire capacity, - // so we can safely sub-slice it for the actual read. - let buf_len = self.read_buf.len(); - let buf_cap = self.read_buf.capacity(); - self.read_buf.set_len(buf_cap); - - // issue the read to the buffer, at the current length offset - *self.ov.deref_mut() = mem::zeroed(); - let mut bytes_read: u32 = 0; - let ok = { - let remaining_buf = &mut self.read_buf[buf_len..]; - kernel32::ReadFile(*self.handle, - remaining_buf.as_mut_ptr() as LPVOID, - remaining_buf.len() as u32, - &mut bytes_read, - self.ov.deref_mut()) - }; - - // Reset the vector to only expose the already filled part. - // - // This means that the async read - // will actually fill memory beyond the exposed part of the vector. - // While this use of a vector is officially sanctioned for such cases, - // it still feel rather icky to me... - // - // On the other hand, this way we make sure - // the buffer never appears to have more valid data - // than what is actually present, - // which could pose a potential danger in its own right. - // Also, it avoids the need to keep a separate state variable -- - // which would bear some risk of getting out of sync. - self.read_buf.set_len(buf_len); - - // ReadFile can return TRUE; if it does, an IO completion - // packet is still posted to any port, and the OVERLAPPED - // structure has the IO operation flagged as complete. - // - // Normally, for an async operation, a call like - // `ReadFile` would return `FALSE`, and the error code - // would be `ERROR_IO_PENDING`. But in some situations, - // `ReadFile` can complete synchronously (returns `TRUE`). - // Even if it does, a notification that the IO completed - // is still sent to the IO completion port that this - // handle is part of, meaning that we don't have to do any - // special handling for sync-completed operations. - if ok == winapi::FALSE { - let err = GetLastError(); - if err == winapi::ERROR_BROKEN_PIPE { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - return Ok(()); - } + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. + let buf_len = self.read_buf.len(); + let buf_cap = self.read_buf.capacity(); + self.read_buf.set_len(buf_cap); + + // issue the read to the buffer, at the current length offset + *self.ov.deref_mut() = mem::zeroed(); + let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut self.read_buf[buf_len..]; + kernel32::ReadFile(*self.handle, + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + self.ov.deref_mut()) + }; - if err == winapi::ERROR_IO_PENDING { - self.read_in_progress = true; - return Ok(()); - } + // Reset the vector to only expose the already filled part. + // + // This means that the async read + // will actually fill memory beyond the exposed part of the vector. + // While this use of a vector is officially sanctioned for such cases, + // it still feel rather icky to me... + // + // On the other hand, this way we make sure + // the buffer never appears to have more valid data + // than what is actually present, + // which could pose a potential danger in its own right. + // Also, it avoids the need to keep a separate state variable -- + // which would bear some risk of getting out of sync. + self.read_buf.set_len(buf_len); + + // ReadFile can return TRUE; if it does, an IO completion + // packet is still posted to any port, and the OVERLAPPED + // structure has the IO operation flagged as complete. + // + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + if ok == winapi::FALSE { + let err = GetLastError(); + if err == winapi::ERROR_BROKEN_PIPE { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + return Ok(()); + } - Err(WinError::from_system(err, "ReadFile")) - } else { + if err == winapi::ERROR_IO_PENDING { self.read_in_progress = true; - Ok(()) + return Ok(()); } + + Err(WinError::from_system(err, "ReadFile")) + } else { + self.read_in_progress = true; + Ok(()) } } /// Called when we receive an IO Completion Packet for this handle. /// - /// Unsafe, since calling this with an invalid object or at the wrong time - /// could result in uninitialized data being passed off as valid. - /// While this may seem less critical than other memory errors, - /// it can also break type safety. + /// Unsafe, since calling this in error + /// while an async read is actually still in progress in the kernel + /// would have catastrophic effects, + /// as `ov` and `read_buf` are still mutably aliased by the kernel in that case! + /// + /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + /// + /// Also, this method relies on `ov` and `read_buf` actually having valid data, + /// i.e. nothing should modify these fields + /// between receiving the completion notification from the kernel + /// and invoking this method. unsafe fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + assert!(self.read_in_progress); + win32_trace!("[$ {:?}] notify_completion", self.handle); - // mark a read as no longer in progress even before we check errors + // Regardless whether the kernel reported success or error, + // it doesn't have an async read operation in flight at this point anymore. + // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; if err == winapi::ERROR_BROKEN_PIPE { @@ -517,6 +565,11 @@ impl MessageReader { // get_message_inner borrows the buffer. fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { + // Never touch the buffer while it's still mutably aliased by the kernel! + if self.read_in_progress { + return Ok(None); + } + let drain_bytes; let result; if let Some(message) = Message::from_bytes(&self.read_buf) { @@ -588,6 +641,10 @@ impl MessageReader { // Make sure that the reader has a read in flight, // otherwise a later select() will hang. + // + // Note: Just like in `OsIpcReceiver.receive_message()` below, + // this makes us vulnerable to invalid `ov` and `read_buf` modification + // from code not marked as unsafe... try!(self.start_read()); Ok(()) @@ -796,6 +853,16 @@ impl OsIpcReceiver { if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Nonblocking read, no message, read's in flight, we're // done. An error is expected in this case. + // + // Note: This leaks unsafety outside the `unsafe` block, + // since the method returns while an async read is still in progress; + // meaning the kernel still holds a mutable alias + // of the read buffer and `OVERLAPPED` structure + // that the Rust type system doesn't know about -- + // nothing prevents code that isn't marked as `unsafe` + // from performing invalid reads or writes to these fields! + // + // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) return Err(WinError::NoData); } // We pass err through to notify_completion so @@ -1304,7 +1371,7 @@ impl OsIpcReceiverSet { selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); } else { - try!(reader.start_read()); + unsafe { try!(reader.start_read()); } } } From ec7419a4ae230a130171163d8d0ad529cdcc1b95 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 21:32:55 +0200 Subject: [PATCH 035/109] windows: Shrink unnecessarily wide `unsafe` block Now that `get_message()` has a check to make sure it won't touch the buffer if an async read is in progress, the safety of the invocation is no longer affected by the correctness of the other code in the unsafe block, and vice versa. --- src/platform/windows/mod.rs | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 5d729587d..91bf0bd41 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -804,28 +804,28 @@ impl OsIpcReceiver { // channels. fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { - unsafe { - let mut reader = self.reader.borrow_mut(); - assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); - - // This function loops, because in the case of a blocking read, we may need to - // read multiple sets of bytes from the pipe to receive a complete message. - loop { - // First, try to fetch a message, in case we have one pending - // in the reader's receive buffer - match try!(reader.get_message()) { - Some((data, channels, shmems)) => - return Ok((data, channels, shmems)), - None => - {}, - } + let mut reader = self.reader.borrow_mut(); + assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); - // If the pipe was already closed, we're done -- we've - // already drained all incoming bytes - if reader.closed { - return Err(WinError::ChannelClosed); - } + // This function loops, because in the case of a blocking read, we may need to + // read multiple sets of bytes from the pipe to receive a complete message. + loop { + // First, try to fetch a message, in case we have one pending + // in the reader's receive buffer + match try!(reader.get_message()) { + Some((data, channels, shmems)) => + return Ok((data, channels, shmems)), + None => + {}, + } + + // If the pipe was already closed, we're done -- we've + // already drained all incoming bytes + if reader.closed { + return Err(WinError::ChannelClosed); + } + unsafe { // Then, issue a read if we don't have one already in flight. // We must not issue a read if we have complete unconsumed // messages, because getting a message modifies the read_buf. @@ -872,11 +872,11 @@ impl OsIpcReceiver { // Notify that the read completed, which will update the // read pointers try!(reader.notify_completion(err)); - - // If we're not blocking, pretend that we are blocking, since we got part of - // a message already. Keep reading until we get a complete message. - blocking_mode = BlockingMode::Blocking; } + + // If we're not blocking, pretend that we are blocking, since we got part of + // a message already. Keep reading until we get a complete message. + blocking_mode = BlockingMode::Blocking; } } From ce808627970826bbc812219ecdd672ea05e37b9f Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 22:12:37 +0200 Subject: [PATCH 036/109] windows: Minor comment fix --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 91bf0bd41..138e37a24 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -914,7 +914,7 @@ impl OsIpcReceiver { }, // This is a weird one -- if we create a named pipe (like we do - // in new(), the client connects, sends data, then drops its handle, + // in new() ), the client connects, sends data, then drops its handle, // a Connect here will get ERROR_NO_DATA -- but there may be data in // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. From 5bebf0684b7816431c95eb991b891254b8321de0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 22:18:04 +0200 Subject: [PATCH 037/109] windows: cleanup: More straightforward use of default match case --- src/platform/windows/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 138e37a24..91138477a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -923,14 +923,8 @@ impl OsIpcReceiver { Ok(()) }, - // was it an actual error? - err if err != winapi::ERROR_IO_PENDING => { - win32_trace!("[$ {:?}] accept error -> {}", handle, err); - Err(WinError::last("ConnectNamedPipe")) - }, - // the connect is pending; wait for it to complete - _ /* winapi::ERROR_IO_PENDING */ => { + winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; let ok = kernel32::GetOverlappedResult(handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { @@ -938,6 +932,12 @@ impl OsIpcReceiver { } Ok(()) }, + + // Anything else signifies some actual I/O error. + err => { + win32_trace!("[$ {:?}] accept error -> {}", handle, err); + Err(WinError::last("ConnectNamedPipe")) + }, } } } From 40a58bbbbe4786dccdd8bbbbb1ef2f1636dbd923 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 6 Oct 2017 22:33:47 +0200 Subject: [PATCH 038/109] windows: Take `WinHandle` rather than `HANDLE` in `write_msg()` and `write_buf()` I see no reason to use the raw handle here -- so let's stick with the safer abstraction... --- src/platform/windows/mod.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 91138477a..924a2b02f 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -976,14 +976,14 @@ impl Clone for OsIpcSender { /// This is important, since otherwise concurrent sending /// could result in parts of different messages getting intermixed, /// and we would not be able to extract the individual messages. -fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { +fn write_msg(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { if bytes.len() == 0 { return Ok(()); } let mut size: u32 = 0; unsafe { - if kernel32::WriteFile(handle, + if kernel32::WriteFile(**handle, bytes.as_ptr() as LPVOID, bytes.len() as u32, &mut size, @@ -1005,7 +1005,7 @@ fn write_msg(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { /// /// Can be used for writes to an exclusive pipe, /// where the send being split up into several calls poses no danger. -fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { +fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { let total = bytes.len(); if total == 0 { return Ok(()); @@ -1016,7 +1016,7 @@ fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { let mut sz: u32 = 0; unsafe { let bytes_to_write = &bytes[written..]; - if kernel32::WriteFile(handle, + if kernel32::WriteFile(**handle, bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, &mut sz, @@ -1027,7 +1027,7 @@ fn write_buf(handle: HANDLE, bytes: &[u8]) -> Result<(),WinError> { } } written += sz as usize; - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", handle, sz, written, bytes.len(), GetLastError()); + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, bytes.len(), GetLastError()); } Ok(()) @@ -1114,7 +1114,7 @@ impl OsIpcSender { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); - write_buf(*self.handle, data) + write_buf(&self.handle, data) } pub fn send(&self, @@ -1203,10 +1203,10 @@ impl OsIpcSender { if big_data_sender.is_none() { &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); - try!(write_msg(*self.handle, &full_message)); + try!(write_msg(&self.handle, &full_message)); } else { &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); - try!(write_msg(*self.handle, &full_message)); + try!(write_msg(&self.handle, &full_message)); try!(big_data_sender.unwrap().send_raw(data)); } } From 76334d3544ff040b5fd64d906690c2142002bf27 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 16:49:54 +0200 Subject: [PATCH 039/109] windows: cleanup: Use helper variable consistently --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 924a2b02f..488585fb7 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1027,7 +1027,7 @@ fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { } } written += sz as usize; - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, bytes.len(), GetLastError()); + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); } Ok(()) From 85f739684060e88cb426306cd5031527070ac8bb Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:08:41 +0200 Subject: [PATCH 040/109] windows: cleanup: Merge `write_msg()` functionality into `write_buf()` Use a single function with a mode flag, rather than maintaining two separate, nearly identical functions. --- src/platform/windows/mod.rs | 63 +++++++++++++++---------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 488585fb7..971e9a6c8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -970,42 +970,16 @@ impl Clone for OsIpcSender { } } -/// Atomic write to a handle. -/// -/// Fails if the data can't be written in a single system call. -/// This is important, since otherwise concurrent sending -/// could result in parts of different messages getting intermixed, -/// and we would not be able to extract the individual messages. -fn write_msg(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { - if bytes.len() == 0 { - return Ok(()); - } - - let mut size: u32 = 0; - unsafe { - if kernel32::WriteFile(**handle, - bytes.as_ptr() as LPVOID, - bytes.len() as u32, - &mut size, - ptr::null_mut()) - == winapi::FALSE - { - return Err(WinError::last("WriteFile")); - } - } - - if size != bytes.len() as u32 { - panic!("Windows IPC write_msg expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", size, bytes.len()); - } - - Ok(()) +#[derive(Clone, Copy, Debug)] +enum AtomicMode { + Atomic, + Nonatomic, } -/// Non-atomic write to a handle. +/// Write data to a handle. /// -/// Can be used for writes to an exclusive pipe, -/// where the send being split up into several calls poses no danger. -fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { +/// In `Atomic` mode, this panics if the data can't be written in a single system call. +fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(),WinError> { let total = bytes.len(); if total == 0 { return Ok(()); @@ -1027,7 +1001,16 @@ fn write_buf(handle: &WinHandle, bytes: &[u8]) -> Result<(),WinError> { } } written += sz as usize; - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + match atomic { + AtomicMode::Atomic => { + if written != total { + panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total); + } + }, + AtomicMode::Nonatomic => { + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + }, + } } Ok(()) @@ -1114,7 +1097,10 @@ impl OsIpcSender { win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); - write_buf(&self.handle, data) + // Write doesn't need to be atomic, + // since the pipe is exclusive for this message, + // so we don't have to fear intermixing with parts of other messages. + write_buf(&self.handle, data, AtomicMode::Nonatomic) } pub fn send(&self, @@ -1203,10 +1189,13 @@ impl OsIpcSender { if big_data_sender.is_none() { &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); - try!(write_msg(&self.handle, &full_message)); + // Write needs to be atomic, since otherwise concurrent sending + // could result in parts of different messages getting intermixed, + // and the receiver would not be able to extract the individual messages. + try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); } else { &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); - try!(write_msg(&self.handle, &full_message)); + try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); try!(big_data_sender.unwrap().send_raw(data)); } } From 9962dc8f45b371b7177f032672d570b05428002e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:16:28 +0200 Subject: [PATCH 041/109] windows: Shrink an unnecessarily wide `unsafe` block Sub-slicing is a safe operation (always yielding a valid slice) -- so it shouldn't affect the soundness of the actual unsafe code. --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 971e9a6c8..150f9587e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -988,8 +988,8 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), let mut written = 0; while written < total { let mut sz: u32 = 0; + let bytes_to_write = &bytes[written..]; unsafe { - let bytes_to_write = &bytes[written..]; if kernel32::WriteFile(**handle, bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, From f90bc92a8f63c7672955b02a545d8fdb5daf5068 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:28:15 +0200 Subject: [PATCH 042/109] windows: Fix overly eager size check While it's an unlikely case, I don't see any problem with the buffer being used up to the last byte... --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 150f9587e..f4f9cc685 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1086,7 +1086,7 @@ impl OsIpcSender { let oob_size = if oob.needs_to_be_sent() { bincode::serialized_size(oob).unwrap() } else { 0 }; // make sure we don't have too much oob data to begin with - assert!((oob_size as usize) < (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); + assert!((oob_size as usize) <= (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); let bytes_left_for_data = (PIPE_BUFFER_SIZE-MessageHeader::size()) - (oob_size as usize); data_len >= bytes_left_for_data From a43c96c644c2344c43ea9a0bef5cff26a5ac8e47 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 17:29:47 +0200 Subject: [PATCH 043/109] windows: Fix another overly eager size check Again, while it's an unlikely case, I see no problem with fully using the available value range. --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f4f9cc685..e245431c0 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1112,7 +1112,7 @@ impl OsIpcSender { // We limit the max size we can send here; we can fix this // just by upping the header to be 2x u64 if we really want // to. - assert!(data.len() < u32::max_value() as usize); + assert!(data.len() <= u32::max_value() as usize); let (server_h, server_pid) = if !shared_memory_regions.is_empty() || !ports.is_empty() { try!(self.get_pipe_server_process_handle_and_pid()) From a51b7c330a482a97ad5b208be960ef856197efff Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 18:45:25 +0200 Subject: [PATCH 044/109] windows: Revamp send buffer construction to minimise `unsafe` The only really unsafe part here is turning the `MessageHeader` struct into a series of bytes. All the actual buffer manipulations can be done just as efficiently using safe operations. This includes a major revamp of the way we deal with the header construction: making it more straightforward, and reusing existing functionality. --- src/platform/windows/mod.rs | 50 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e245431c0..6a2ffef5b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1175,29 +1175,33 @@ impl OsIpcSender { oob_data = bincode::serialize(&oob).unwrap(); } - unsafe { - let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; - let full_in_band_len = MessageHeader::size() + in_band_data_len + oob_data.len(); - assert!(full_in_band_len <= PIPE_BUFFER_SIZE); - - let mut full_message = Vec::::with_capacity(full_in_band_len); - full_message.set_len(full_in_band_len); - - let header = full_message.as_mut_ptr() as *mut MessageHeader; - *header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); - - if big_data_sender.is_none() { - &mut full_message[MessageHeader::size()..MessageHeader::size()+data.len()].clone_from_slice(data); - &mut full_message[MessageHeader::size()+data.len()..].clone_from_slice(&oob_data); - // Write needs to be atomic, since otherwise concurrent sending - // could result in parts of different messages getting intermixed, - // and the receiver would not be able to extract the individual messages. - try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); - } else { - &mut full_message[MessageHeader::size()..].clone_from_slice(&oob_data); - try!(write_buf(&self.handle, &full_message, AtomicMode::Atomic)); - try!(big_data_sender.unwrap().send_raw(data)); - } + let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; + let header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); + let full_in_band_len = header.total_message_bytes_needed(); + assert!(full_in_band_len <= PIPE_BUFFER_SIZE); + let mut full_message = Vec::::with_capacity(full_in_band_len); + + { + let header_bytes = unsafe { slice::from_raw_parts(&header as *const _ as *const u8, + mem::size_of_val(&header)) }; + full_message.extend_from_slice(header_bytes); + } + + if big_data_sender.is_none() { + full_message.extend_from_slice(&*data); + full_message.extend_from_slice(&*oob_data); + assert!(full_message.len() == full_in_band_len); + + // Write needs to be atomic, since otherwise concurrent sending + // could result in parts of different messages getting intermixed, + // and the receiver would not be able to extract the individual messages. + try!(write_buf(&self.handle, &*full_message, AtomicMode::Atomic)); + } else { + full_message.extend_from_slice(&*oob_data); + assert!(full_message.len() == full_in_band_len); + + try!(write_buf(&self.handle, &*full_message, AtomicMode::Atomic)); + try!(big_data_sender.unwrap().send_raw(data)); } Ok(()) From d02db851cc02febae67853c1e0c711c75009f330 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:09:38 +0200 Subject: [PATCH 045/109] windows: Remove trivial `MessageHeader::size()` helper Abstracting this trivial functionality only makes it more obscure. --- src/platform/windows/mod.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 6a2ffef5b..dd65ac08c 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -74,12 +74,8 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { struct MessageHeader(u32, u32); impl MessageHeader { - fn size() -> usize { - mem::size_of::() - } - fn total_message_bytes_needed(&self) -> usize { - MessageHeader::size() + self.0 as usize + self.1 as usize + mem::size_of::() + self.0 as usize + self.1 as usize } } @@ -91,7 +87,7 @@ struct Message<'data> { impl<'data> Message<'data> { fn from_bytes(bytes: &'data [u8]) -> Option { - if bytes.len() < MessageHeader::size() { + if bytes.len() < mem::size_of::() { return None; } @@ -110,11 +106,11 @@ impl<'data> Message<'data> { } fn data(&self) -> &[u8] { - &self.bytes[MessageHeader::size()..(MessageHeader::size() + self.data_len)] + &self.bytes[mem::size_of::()..(mem::size_of::() + self.data_len)] } fn oob_bytes(&self) -> &[u8] { - &self.bytes[(MessageHeader::size() + self.data_len)..] + &self.bytes[(mem::size_of::() + self.data_len)..] } fn oob_data(&self) -> Option { @@ -136,7 +132,7 @@ impl<'data> Message<'data> { } fn size(&self) -> usize { - MessageHeader::size() + self.data_len + self.oob_len + mem::size_of::() + self.data_len + self.oob_len } } @@ -1086,9 +1082,9 @@ impl OsIpcSender { let oob_size = if oob.needs_to_be_sent() { bincode::serialized_size(oob).unwrap() } else { 0 }; // make sure we don't have too much oob data to begin with - assert!((oob_size as usize) <= (PIPE_BUFFER_SIZE-MessageHeader::size()), "too much oob data"); + assert!((oob_size as usize) <= (PIPE_BUFFER_SIZE - mem::size_of::()), "too much oob data"); - let bytes_left_for_data = (PIPE_BUFFER_SIZE-MessageHeader::size()) - (oob_size as usize); + let bytes_left_for_data = (PIPE_BUFFER_SIZE - mem::size_of::()) - (oob_size as usize); data_len >= bytes_left_for_data } From 9c2b75819d51a8111032ab0c1bfc302a220b71cb Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:20:10 +0200 Subject: [PATCH 046/109] windows: Turn `MessageHeader` into normal struct Tuple structs are obscure, and are used pretty much only for the "newtype" pattern -- a struct with named fields is generally more readable. --- src/platform/windows/mod.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index dd65ac08c..d59fdbd8a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -70,12 +70,14 @@ pub fn channel() -> Result<(OsIpcSender, OsIpcReceiver),WinError> { Ok((sender, receiver)) } -/// Holds data len and out-of-band data len. -struct MessageHeader(u32, u32); +struct MessageHeader { + data_len: u32, + oob_len: u32, +} impl MessageHeader { fn total_message_bytes_needed(&self) -> usize { - mem::size_of::() + self.0 as usize + self.1 as usize + mem::size_of::() + self.data_len as usize + self.oob_len as usize } } @@ -98,8 +100,8 @@ impl<'data> Message<'data> { } Some(Message { - data_len: header.0 as usize, - oob_len: header.1 as usize, + data_len: header.data_len as usize, + oob_len: header.oob_len as usize, bytes: &bytes[0..header.total_message_bytes_needed()], }) } @@ -1172,7 +1174,10 @@ impl OsIpcSender { } let in_band_data_len = if big_data_sender.is_none() { data.len() } else { 0 }; - let header = MessageHeader(in_band_data_len as u32, oob_data.len() as u32); + let header = MessageHeader { + data_len: in_band_data_len as u32, + oob_len: oob_data.len() as u32 + }; let full_in_band_len = header.total_message_bytes_needed(); assert!(full_in_band_len <= PIPE_BUFFER_SIZE); let mut full_message = Vec::::with_capacity(full_in_band_len); From 033cdd8c3fbcd0c669bea0b7780f6155fbd1c2dc Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:40:37 +0200 Subject: [PATCH 047/109] windows: Check for errors on SHM unmapping --- src/platform/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d59fdbd8a..1e74a4c02 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -22,6 +22,7 @@ use std::mem; use std::ops::{Deref, DerefMut, RangeFrom}; use std::ptr; use std::slice; +use std::thread; use uuid::Uuid; use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; use winapi; @@ -1411,7 +1412,8 @@ unsafe impl Sync for OsIpcSharedMemory {} impl Drop for OsIpcSharedMemory { fn drop(&mut self) { unsafe { - kernel32::UnmapViewOfFile(self.ptr as LPVOID); + let result = kernel32::UnmapViewOfFile(self.ptr as LPVOID); + assert!(thread::panicking() || result != 0); } } } From faee0c389668992a47932c0653a5fa9640eebe11 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 19:40:37 +0200 Subject: [PATCH 048/109] windows: Check for errors on file handle closing --- src/platform/windows/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 1e74a4c02..8f83c8764 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -276,7 +276,10 @@ unsafe impl Sync for WinHandle { } impl Drop for WinHandle { fn drop(&mut self) { unsafe { - kernel32::CloseHandle(self.h); + if self.is_valid() { + let result = kernel32::CloseHandle(self.h); + assert!(thread::panicking() || result != 0); + } } } } From cf2bdac5db9f8a46126402867ad55304bf68c641 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 20:48:41 +0200 Subject: [PATCH 049/109] windows: Add check to make sure we don't leak `OsOpaqueIpcChannel` Adaptation of same mechanism in unix back-end. --- src/platform/windows/mod.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8f83c8764..7eb6550ee 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1558,6 +1558,17 @@ pub struct OsOpaqueIpcChannel { handle: HANDLE, } +impl Drop for OsOpaqueIpcChannel { + fn drop(&mut self) { + // Make sure we don't leak! + // + // The `OsOpaqueIpcChannel` objects should always be used, + // i.e. converted with `to_sender()` or `to_receiver()` -- + // so the value should already be unset before the object gets dropped. + debug_assert!(self.handle == INVALID_HANDLE_VALUE); + } +} + impl OsOpaqueIpcChannel { fn new(handle: HANDLE) -> OsOpaqueIpcChannel { OsOpaqueIpcChannel { From d71a85626e9ffb3162c1190a4eb5cb780fa70e85 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 20:56:23 +0200 Subject: [PATCH 050/109] windows: Fix SHM mapping for areas larger than 2^32 That code resulted from some kind of misunderstanding... --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 7eb6550ee..bb9e1d238 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1456,11 +1456,11 @@ impl Deref for OsIpcSharedMemory { } impl OsIpcSharedMemory { - #[allow(exceeding_bitshifts)] fn new(length: usize) -> Result { unsafe { assert!(length < u32::max_value() as usize); - let (lhigh, llow) = (0 as u32, (length & 0xffffffffusize) as u32); + let (lhigh, llow) = (length.checked_shr(32).unwrap_or(0) as u32, + (length & 0xffffffff) as u32); let handle = kernel32::CreateFileMappingA(INVALID_HANDLE_VALUE, ptr::null_mut(), From 17d87bfa21669f1dadba1e81210c8e510ed9c237 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 21:58:41 +0200 Subject: [PATCH 051/109] windows: Derive `PartialEq` on `WinError` This way we can do simple comparision, rather than needing one-arm match statements. --- src/platform/windows/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index bb9e1d238..8c8c6c454 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1585,7 +1585,7 @@ impl OsOpaqueIpcChannel { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum WinError { WindowsResult(u32), ChannelClosed, @@ -1636,10 +1636,7 @@ impl WinError { } pub fn channel_is_closed(&self) -> bool { - match *self { - WinError::ChannelClosed => true, - _ => false, - } + *self == WinError::ChannelClosed } } From 628a724bbda4b8106b7350a872029e1e72a60bc9 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 7 Oct 2017 22:09:29 +0200 Subject: [PATCH 052/109] windows: cleanup: Don't intersperse method impls with free-standing function The convention is to keep all `impl`s of a type together in one place. --- src/platform/windows/mod.rs | 92 ++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8c8c6c454..ae8e582f9 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -715,6 +715,52 @@ impl MessageReader { } } +#[derive(Clone, Copy, Debug)] +enum AtomicMode { + Atomic, + Nonatomic, +} + +/// Write data to a handle. +/// +/// In `Atomic` mode, this panics if the data can't be written in a single system call. +fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(),WinError> { + let total = bytes.len(); + if total == 0 { + return Ok(()); + } + + let mut written = 0; + while written < total { + let mut sz: u32 = 0; + let bytes_to_write = &bytes[written..]; + unsafe { + if kernel32::WriteFile(**handle, + bytes_to_write.as_ptr() as LPVOID, + bytes_to_write.len() as u32, + &mut sz, + ptr::null_mut()) + == winapi::FALSE + { + return Err(WinError::last("WriteFile")); + } + } + written += sz as usize; + match atomic { + AtomicMode::Atomic => { + if written != total { + panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total); + } + }, + AtomicMode::Nonatomic => { + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + }, + } + } + + Ok(()) +} + #[derive(Clone, Copy, Debug, PartialEq)] enum BlockingMode { Blocking, @@ -972,52 +1018,6 @@ impl Clone for OsIpcSender { } } -#[derive(Clone, Copy, Debug)] -enum AtomicMode { - Atomic, - Nonatomic, -} - -/// Write data to a handle. -/// -/// In `Atomic` mode, this panics if the data can't be written in a single system call. -fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(),WinError> { - let total = bytes.len(); - if total == 0 { - return Ok(()); - } - - let mut written = 0; - while written < total { - let mut sz: u32 = 0; - let bytes_to_write = &bytes[written..]; - unsafe { - if kernel32::WriteFile(**handle, - bytes_to_write.as_ptr() as LPVOID, - bytes_to_write.len() as u32, - &mut sz, - ptr::null_mut()) - == winapi::FALSE - { - return Err(WinError::last("WriteFile")); - } - } - written += sz as usize; - match atomic { - AtomicMode::Atomic => { - if written != total { - panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total); - } - }, - AtomicMode::Nonatomic => { - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); - }, - } - } - - Ok(()) -} - impl OsIpcSender { pub fn connect(name: String) -> Result { let pipe_name = make_pipe_name(&Uuid::parse_str(&name).unwrap()); From f59a916c7e986505b039b00a3a59585293b1a18e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 8 Oct 2017 16:21:26 +0200 Subject: [PATCH 053/109] windows: cleanup: `start_read()`: Use `match` for error handling --- src/platform/windows/mod.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ae8e582f9..34565ef31 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -494,19 +494,20 @@ impl MessageReader { // handle is part of, meaning that we don't have to do any // special handling for sync-completed operations. if ok == winapi::FALSE { - let err = GetLastError(); - if err == winapi::ERROR_BROKEN_PIPE { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - return Ok(()); - } - - if err == winapi::ERROR_IO_PENDING { - self.read_in_progress = true; - return Ok(()); + match GetLastError() { + winapi::ERROR_BROKEN_PIPE => { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + return Ok(()); + }, + winapi::ERROR_IO_PENDING => { + self.read_in_progress = true; + return Ok(()); + }, + err => { + Err(WinError::from_system(err, "ReadFile")) + }, } - - Err(WinError::from_system(err, "ReadFile")) } else { self.read_in_progress = true; Ok(()) From 9bd559b6485faa113f11eaacfeca0484608803be Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:10:26 +0200 Subject: [PATCH 054/109] windows: cleanup: `start_read()`: Consistenly use explicit `return` Should be easier to read than mixing explicit and implicit return for no obvious reason... --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 34565ef31..002cffe2d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -505,12 +505,12 @@ impl MessageReader { return Ok(()); }, err => { - Err(WinError::from_system(err, "ReadFile")) + return Err(WinError::from_system(err, "ReadFile")); }, } } else { self.read_in_progress = true; - Ok(()) + return Ok(()); } } From a46741e8ca5cbc83954ba07f602e9ff9d20640c2 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:11:14 +0200 Subject: [PATCH 055/109] windows: cleanup: `start_read()`: Merge handling of identical cases Since the `Ok` and `ERROR_IO_PENDING` cases are identical (as explained in the comment), just fall through to a common default handler. --- src/platform/windows/mod.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 002cffe2d..8a42728f3 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -501,17 +501,15 @@ impl MessageReader { return Ok(()); }, winapi::ERROR_IO_PENDING => { - self.read_in_progress = true; - return Ok(()); }, err => { return Err(WinError::from_system(err, "ReadFile")); }, } - } else { - self.read_in_progress = true; - return Ok(()); } + + self.read_in_progress = true; + Ok(()) } /// Called when we receive an IO Completion Packet for this handle. From 949393216d14bf72d18863975e75487a6ea14b86 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:14:06 +0200 Subject: [PATCH 056/109] windows: cleanup: `start_read()`: Switch cases for more logical order Put the success case before the error cases, rather than in between... --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8a42728f3..bc8196481 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -495,13 +495,13 @@ impl MessageReader { // special handling for sync-completed operations. if ok == winapi::FALSE { match GetLastError() { + winapi::ERROR_IO_PENDING => { + }, winapi::ERROR_BROKEN_PIPE => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); self.closed = true; return Ok(()); }, - winapi::ERROR_IO_PENDING => { - }, err => { return Err(WinError::from_system(err, "ReadFile")); }, From 36f16e704f7d24a0e11f09319065c9d3d22386b6 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 14 Oct 2017 00:17:38 +0200 Subject: [PATCH 057/109] windows: cleanup: `start_read()`: Construct a temporary `Result<>` Further streamline error handling by turning the FFI result into a `Result<>` value, so we can handle it in one uniform `match` expression. While it could be argue that this adds unnecessary extra code, I believe it makes the error handling easier to follow -- especially the common handling of the `Ok` and `ERROR_IO_PENDING` cases. It also allows moving the comment closer to the actual case it explains... --- src/platform/windows/mod.rs | 60 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index bc8196481..a2010eea5 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -481,35 +481,39 @@ impl MessageReader { // which would bear some risk of getting out of sync. self.read_buf.set_len(buf_len); - // ReadFile can return TRUE; if it does, an IO completion - // packet is still posted to any port, and the OVERLAPPED - // structure has the IO operation flagged as complete. - // - // Normally, for an async operation, a call like - // `ReadFile` would return `FALSE`, and the error code - // would be `ERROR_IO_PENDING`. But in some situations, - // `ReadFile` can complete synchronously (returns `TRUE`). - // Even if it does, a notification that the IO completed - // is still sent to the IO completion port that this - // handle is part of, meaning that we don't have to do any - // special handling for sync-completed operations. - if ok == winapi::FALSE { - match GetLastError() { - winapi::ERROR_IO_PENDING => { - }, - winapi::ERROR_BROKEN_PIPE => { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - return Ok(()); - }, - err => { - return Err(WinError::from_system(err, "ReadFile")); - }, - } - } + let result = if ok == winapi::FALSE { + Err(GetLastError()) + } else { + Ok(()) + }; - self.read_in_progress = true; - Ok(()) + match result { + // ReadFile can return TRUE; if it does, an IO completion + // packet is still posted to any port, and the OVERLAPPED + // structure has the IO operation flagged as complete. + // + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + Ok(()) | + Err(winapi::ERROR_IO_PENDING) => { + self.read_in_progress = true; + Ok(()) + }, + Err(winapi::ERROR_BROKEN_PIPE) => { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.closed = true; + Ok(()) + }, + Err(err) => { + Err(WinError::from_system(err, "ReadFile")) + }, + } } /// Called when we receive an IO Completion Packet for this handle. From 37e67df19a94e9957c3c9873da547aaba5e28075 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 8 Oct 2017 16:35:49 +0200 Subject: [PATCH 058/109] windows: cleanup: Drop redundant comment part The second paragraph explains the same thing as the first one, only clearer... So we can just get rid of the first one entirely. --- src/platform/windows/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index a2010eea5..f16ce2c68 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -488,10 +488,6 @@ impl MessageReader { }; match result { - // ReadFile can return TRUE; if it does, an IO completion - // packet is still posted to any port, and the OVERLAPPED - // structure has the IO operation flagged as complete. - // // Normally, for an async operation, a call like // `ReadFile` would return `FALSE`, and the error code // would be `ERROR_IO_PENDING`. But in some situations, From c9b9ad23ee550c392ddc651392d9edba9826fc80 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 8 Oct 2017 23:34:06 +0200 Subject: [PATCH 059/109] windows: Fix misplaced comment Apparently this ended up in the wrong place during some edit... While at it, also improve wording. --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f16ce2c68..e5d94280b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -535,6 +535,7 @@ impl MessageReader { // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; + // Remote end closed the channel. if err == winapi::ERROR_BROKEN_PIPE { assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); self.closed = true; @@ -546,7 +547,6 @@ impl MessageReader { assert!(offset == 0); - // if the remote end closed... if err != winapi::ERROR_SUCCESS { // This should never happen panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); From 897f7a2c5015f029d881844623b183f91c4d6acb Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 9 Oct 2017 00:11:37 +0200 Subject: [PATCH 060/109] windows: Drop bogus `Result<>` from `notify_completion()` Don't know whether there was a point to it in some earlier iteration -- but in the current code, this method never returns anything else than `Ok(())`... --- src/platform/windows/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e5d94280b..8d9cc17cc 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -525,7 +525,7 @@ impl MessageReader { /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, err: u32) -> Result<(),WinError> { + unsafe fn notify_completion(&mut self, err: u32) { assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -539,7 +539,7 @@ impl MessageReader { if err == winapi::ERROR_BROKEN_PIPE { assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); self.closed = true; - return Ok(()); + return; } let nbytes = self.ov.InternalHigh as u32; @@ -557,8 +557,6 @@ impl MessageReader { nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); assert!(new_size <= self.read_buf.capacity()); self.read_buf.set_len(new_size); - - Ok(()) } // This is split between get_message and get_message_inner, so that @@ -918,7 +916,7 @@ impl OsIpcReceiver { // Notify that the read completed, which will update the // read pointers - try!(reader.notify_completion(err)); + reader.notify_completion(err); } // If we're not blocking, pretend that we are blocking, since we got part of @@ -1345,7 +1343,7 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); // tell it about the completed IO op - unsafe { try!(reader.notify_completion(io_err)); } + unsafe { reader.notify_completion(io_err); } // then drain as many messages as we can loop { From 421bafdb5089a209e14432207ecc3e92b2027c05 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 02:21:28 +0200 Subject: [PATCH 061/109] windows: Drop bogus comment This seems to be a leftover from some earlier version of the code... --- src/platform/windows/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 8d9cc17cc..ef4470fbe 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -559,9 +559,6 @@ impl MessageReader { self.read_buf.set_len(new_size); } - // This is split between get_message and get_message_inner, so that - // this function can handle removing bytes from the buffer, since - // get_message_inner borrows the buffer. fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! From 629cb6d122b99e0f0911ad5f3c2383cd7c88c9de Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 02:49:25 +0200 Subject: [PATCH 062/109] windows: Introduce `MessageReader.fetch_async_result()` helper method Move the raw FFI call from `OsIpcReceiver.get_message()` to a new helper method in `MessageReader`. This fixes a major layering violation, and makes the functionality more reusable. Note: this doesn't address the related raw FFI call in `OsIpcReceiverSet.select()` -- that one is a whole other can of worms, which will require a larger refactoring to fix... --- src/platform/windows/mod.rs | 107 ++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ef4470fbe..feb3ad338 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -559,6 +559,57 @@ impl MessageReader { self.read_buf.set_len(new_size); } + /// Attempt to conclude an already issued async read operation. + /// + /// If successful, the result will be ready for picking up by `get_message()`. + /// + /// (`get_message()` might still yield nothing though, + /// in case only part of the message was received in this read, + /// and further read operations are necessary to get the rest.) + /// + /// In non-blocking mode, this may return with `WinError:NoData`, + /// while the async operation remains in flight. + /// + /// Note: Upon successful return, + /// the internal `ov` and `read_buf` fields + /// won't be aliased by the kernel anymore. + /// When getting `NoData` however, + /// access to these fields remains invalid, + /// i.e. we are still in unsafe mode in that case! + fn fetch_async_result(&mut self, blocking_mode: BlockingMode) -> Result<(), WinError> { + unsafe { + assert!(self.read_in_progress); + + // Get the overlapped result, blocking if we need to. + let mut nbytes: u32 = 0; + let mut err = winapi::ERROR_SUCCESS; + let block = match blocking_mode { + BlockingMode::Blocking => winapi::TRUE, + BlockingMode::Nonblocking => winapi::FALSE, + }; + let ok = kernel32::GetOverlappedResult(*self.handle, + self.ov.deref_mut(), + &mut nbytes, + block); + if ok == winapi::FALSE { + err = GetLastError(); + if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { + // Async read hasn't completed yet. + // Inform the caller, while keeping the read in flight. + return Err(WinError::NoData); + } + // We pass err through to notify_completion so + // that it can handle other errors. + } + + // Notify that the read completed, which will update the + // read pointers + self.notify_completion(err); + } + + Ok(()) + } + fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! @@ -784,11 +835,6 @@ pub struct OsIpcReceiver { // While this seems to be true as far as I can tell, // it's a rather fragile condition, which should be managed much more tightly // (along with `OVERLAPPED` in general): at `MessageReader` level, at the very most. -// The current implementation doesn't follow such a strict encapsulation however, -// with `OsIpcReceiver` directly accessing `reader.ov` (in `receive_message()`), -// outside the `MessageReader` implementation -- -// so for now, `OsIpcReceiver` needs to be considered responsible as a whole -// for upholding the non-aliasing condition. unsafe impl Send for OsIpcReceiver { } impl PartialEq for OsIpcReceiver { @@ -879,41 +925,22 @@ impl OsIpcReceiver { return Err(WinError::ChannelClosed); } - // Then, get the overlapped result, blocking if we need to. - let mut nbytes: u32 = 0; - let mut err = winapi::ERROR_SUCCESS; - let block = match blocking_mode { - BlockingMode::Blocking => winapi::TRUE, - BlockingMode::Nonblocking => winapi::FALSE, - }; - let ok = kernel32::GetOverlappedResult(*reader.handle, - reader.ov.deref_mut(), - &mut nbytes, - block); - if ok == winapi::FALSE { - err = GetLastError(); - if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { - // Nonblocking read, no message, read's in flight, we're - // done. An error is expected in this case. - // - // Note: This leaks unsafety outside the `unsafe` block, - // since the method returns while an async read is still in progress; - // meaning the kernel still holds a mutable alias - // of the read buffer and `OVERLAPPED` structure - // that the Rust type system doesn't know about -- - // nothing prevents code that isn't marked as `unsafe` - // from performing invalid reads or writes to these fields! - // - // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) - return Err(WinError::NoData); - } - // We pass err through to notify_completion so - // that it can handle other errors. - } - - // Notify that the read completed, which will update the - // read pointers - reader.notify_completion(err); + // May return `WinError::NoData` in non-blocking mode. + // + // The async read remains in flight in that case; + // and another attempt at getting a result + // can be done the next time we are called. + // + // Note: This leaks unsafety outside the `unsafe` block, + // since the method returns while an async read is still in progress; + // meaning the kernel still holds a mutable alias + // of the read buffer and `OVERLAPPED` structure + // that the Rust type system doesn't know about -- + // nothing prevents code that isn't marked as `unsafe` + // from performing invalid reads or writes to these fields! + // + // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) + try!(reader.fetch_async_result(blocking_mode)); } // If we're not blocking, pretend that we are blocking, since we got part of From dee8c983dfbc389537fdc5faee20cf3dc3142ce8 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Thu, 10 May 2018 18:55:17 +0200 Subject: [PATCH 063/109] windows: Move `Send` declaration from `OsIpcReceiver` to `MessageReader` Now that nothing touches the `ov` field outside the implementation of `MessageReader`, it makes much more sense to make `MessageReader` responsible for upholding the `Send` property. --- src/platform/windows/mod.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index feb3ad338..facc4af5e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -392,6 +392,17 @@ struct MessageReader { set_id: Option, } +// We need to explicitly declare this, because of the raw pointer +// contained in the `OVERLAPPED` structure. +// +// Note: the `Send` claim is only really fulfilled +// as long as nothing can ever alias the aforementioned raw pointer. +// As explained in the documentation of the `ov` field, +// this is a tricky condition (because of kernel aliasing), +// which we however need to uphold regardless of the `Send` property -- +// so claiming `Send` should not introduce any additional issues. +unsafe impl Send for OsIpcReceiver { } + impl Drop for MessageReader { fn drop(&mut self) { // Before dropping the `ov` structure and read buffer, @@ -827,16 +838,6 @@ pub struct OsIpcReceiver { reader: RefCell, } -// We need to explicitly declare this, because of the raw pointer -// contained in the `OVERLAPPED` structure inside `MessageReader`. -// -// Note: the `Send` claim is only really fulfilled -// as long as nothing can ever alias the aforementioned raw pointer. -// While this seems to be true as far as I can tell, -// it's a rather fragile condition, which should be managed much more tightly -// (along with `OVERLAPPED` in general): at `MessageReader` level, at the very most. -unsafe impl Send for OsIpcReceiver { } - impl PartialEq for OsIpcReceiver { fn eq(&self, other: &OsIpcReceiver) -> bool { self.reader.borrow().handle == other.reader.borrow().handle From d2d0e687bbede3f9c4c3d02f2c9a7fe606641a92 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 03:44:11 +0200 Subject: [PATCH 064/109] windows: cleanup: Consume reader in `read_raw_sized()` The reader is not supposed to be used for anything else; and the caller drops it immediately afterwards anyway. Taking it by value (and dropping it ourself) makes this more explicit. --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index facc4af5e..cd93f2b10 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -715,7 +715,7 @@ impl MessageReader { /// and the transfer doesn't have our typical message framing. /// /// It's only valid to call this as the one and only call after creating a MessageReader. - fn read_raw_sized(&mut self, size: usize) -> Result,WinError> { + fn read_raw_sized(mut self, size: usize) -> Result,WinError> { assert!(self.read_buf.len() == 0); // We use with_capacity() to allocate an uninitialized buffer, @@ -1017,7 +1017,7 @@ impl OsIpcReceiver { /// /// This is used for receiving data from the out-of-band big data buffer. fn recv_raw(self, size: usize) -> Result, WinError> { - self.reader.borrow_mut().read_raw_sized(size) + self.reader.into_inner().read_raw_sized(size) } } From 447be23ee77c043fdb0e93c3f97b406ebe61b90f Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 16 Oct 2017 03:19:15 +0200 Subject: [PATCH 065/109] windows: Avoid code duplication in `read_raw_sized()` As far as I can tell, this method only differs from the regular receive pattern in that it allocates the entire buffer for the message of known size in advance, and then reads into it repeatedly until the expected size is filled; at which point it returns the entire buffer -- rather than trying to extract individual messages from the buffer with `get_message()` between reads. Since the actual async read initiation and completion is pretty much the same -- apart from some shortcuts, which should be insignificant I believe -- we can just implement this functionality in terms of the regular methods, rather than keeping separate code paths that do pretty much the same. --- src/platform/windows/mod.rs | 53 ++++++++----------------------------- 1 file changed, 11 insertions(+), 42 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index cd93f2b10..adeb66908 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -718,56 +718,25 @@ impl MessageReader { fn read_raw_sized(mut self, size: usize) -> Result,WinError> { assert!(self.read_buf.len() == 0); - // We use with_capacity() to allocate an uninitialized buffer, - // since we're going to read into it and don't need to - // zero it. - let mut buf = Vec::with_capacity(size); - while buf.len() < size { + self.read_buf.reserve(size); + while self.read_buf.len() < size { // Because our handle is asynchronous, we have to do a two-part read -- // first issue the operation, then wait for its completion. unsafe { - let ov = self.ov.deref_mut(); - *ov = mem::zeroed(); - - // Temporarily extend the vector to span its entire capacity, - // so we can safely sub-slice it for the actual read. - let buf_len = buf.len(); - let buf_cap = buf.capacity(); - buf.set_len(buf_cap); - - let mut bytes_read: u32 = 0; - let ok = { - let remaining_buf = &mut buf[buf_len..]; - kernel32::ReadFile(*self.handle, - remaining_buf.as_mut_ptr() as LPVOID, - remaining_buf.len() as u32, - &mut bytes_read, - ov) - }; - - // Restore the original size before error handling, - // so we never leave the function with the buffer exposing uninitialized data. - buf.set_len(buf_len); - - if ok == winapi::FALSE && GetLastError() != winapi::ERROR_IO_PENDING { - return Err(WinError::last("ReadFile")); + try!(self.start_read()); + // Sender should not close until it sent as much data as we expect... + if self.closed { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); } - - if ov.Internal as i32 == winapi::STATUS_PENDING { - let ok = kernel32::GetOverlappedResult(*self.handle, ov, &mut bytes_read, winapi::TRUE); - if ok == winapi::FALSE { - return Err(WinError::last("GetOverlappedResult")); - } - } else { - bytes_read = ov.InternalHigh as u32; + // In blocking mode, this should never fail... + self.fetch_async_result(BlockingMode::Blocking).unwrap(); + if self.closed { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); } - - let new_len = buf_len + bytes_read as usize; - buf.set_len(new_len); } } - Ok(buf) + Ok(mem::replace(&mut self.read_buf, vec![])) } } From 6b3064eeef524ac77a3cdbe048cab3721f0ac397 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 20 Oct 2017 00:19:25 +0200 Subject: [PATCH 066/109] windows: Take `WinHandle` rather than `HANDLE` in `add_to_iocp()` Just as with `write_buf()`, I see no reason to use the raw handle -- so let's stick with the safer abstraction here as well... --- src/platform/windows/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index adeb66908..f4e81ccea 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -683,12 +683,12 @@ impl MessageReader { Ok(result) } - fn add_to_iocp(&mut self, iocp: HANDLE, set_id: u64) -> Result<(),WinError> { + fn add_to_iocp(&mut self, iocp: &WinHandle, set_id: u64) -> Result<(),WinError> { unsafe { assert!(self.set_id.is_none()); let ret = kernel32::CreateIoCompletionPort(*self.handle, - iocp, + **iocp, *self.handle as winapi::ULONG_PTR, 0); if ret.is_null() { @@ -1244,7 +1244,7 @@ impl OsIpcReceiverSet { let mut reader = receiver.reader.into_inner(); let set_id = self.incrementor.next().unwrap(); - try!(reader.add_to_iocp(*self.iocp, set_id)); + try!(reader.add_to_iocp(&self.iocp, set_id)); win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); From d378a941d4530f3f2bf73a548fd6ac9bd974c0ee Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 21 Oct 2017 02:28:58 +0200 Subject: [PATCH 067/109] windows: Comment on boxing `OVERLAPPED` inside `OsIpcReceiver.accept()` --- src/platform/windows/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f4e81ccea..26e969807 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -938,6 +938,8 @@ impl OsIpcReceiver { unsafe { let reader_borrow = self.reader.borrow(); let handle = *reader_borrow.handle; + // Boxing this to get a stable address is not strictly necesssary here, + // since we are not moving the local variable around -- but better safe than sorry... let mut ov = Box::new(mem::zeroed::()); let ok = kernel32::ConnectNamedPipe(handle, ov.deref_mut()); From 66aded86e9e952f7938f249fd5867c3971cb1e84 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 28 Oct 2017 19:51:45 +0200 Subject: [PATCH 068/109] windows: cleanup: `OsIpcReceiver.accept()`: Unwrap handle at actual use sites Extract the raw handle from the `WinHandle` structure individually wherever it's actually needed for the Windows API calls, rather than doing it up front at the beginning of the method. Dealing with the raw handles as locally as possible seems more robust; and it's also more in line with what we do elsewhere. --- src/platform/windows/mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 26e969807..ac897e75f 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -937,11 +937,11 @@ impl OsIpcReceiver { fn accept(&self) -> Result<(),WinError> { unsafe { let reader_borrow = self.reader.borrow(); - let handle = *reader_borrow.handle; + let handle = &reader_borrow.handle; // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... let mut ov = Box::new(mem::zeroed::()); - let ok = kernel32::ConnectNamedPipe(handle, ov.deref_mut()); + let ok = kernel32::ConnectNamedPipe(**handle, ov.deref_mut()); // we should always get FALSE with async IO assert!(ok == winapi::FALSE); @@ -950,7 +950,7 @@ impl OsIpcReceiver { match err { // did we successfully connect? (it's reported as an error [ok==false]) winapi::ERROR_PIPE_CONNECTED => { - win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle); + win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", **handle); Ok(()) }, @@ -960,14 +960,14 @@ impl OsIpcReceiver { // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. winapi::ERROR_NO_DATA => { - win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle); + win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", **handle); Ok(()) }, // the connect is pending; wait for it to complete winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); + let ok = kernel32::GetOverlappedResult(**handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } @@ -976,7 +976,7 @@ impl OsIpcReceiver { // Anything else signifies some actual I/O error. err => { - win32_trace!("[$ {:?}] accept error -> {}", handle, err); + win32_trace!("[$ {:?}] accept error -> {}", **handle, err); Err(WinError::last("ConnectNamedPipe")) }, } From e9bca002499dc3e643a3a932bf699870c30363c2 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Oct 2017 20:46:32 +0100 Subject: [PATCH 069/109] windows: Fix a misleading (outdated?) comment --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ac897e75f..47139e5aa 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1355,7 +1355,7 @@ impl OsIpcReceiverSet { } } - // We may have already been closed, or the read resulted in us being closed. + // Instead of new data, we might have received a broken pipe notification. // If so, add that to the result and remove the reader from our list. if reader.closed { win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); From 6f29e860b7ba22902673a6a20dc6cbf7525408a8 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Oct 2017 20:56:01 +0100 Subject: [PATCH 070/109] windows: cleanup: Use `if let` rather than `is_some()` + `unwrap()` --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 47139e5aa..92e92b9f7 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1366,8 +1366,8 @@ impl OsIpcReceiverSet { } } - if remove_index.is_some() { - self.readers.swap_remove(remove_index.unwrap()); + if let Some(index) = remove_index { + self.readers.swap_remove(index); } // if we didn't dequeue at least one complete message -- we need to loop through GetQueuedCS again; From 0a52f80f97a29d408c1ea34f47b6c77d8b916a5d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Oct 2017 21:25:02 +0100 Subject: [PATCH 071/109] windows: cleanup: More idiomatic search code --- src/platform/windows/mod.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 92e92b9f7..34aecc1b1 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1285,10 +1285,9 @@ impl OsIpcReceiverSet { // read a complete message. loop { let mut nbytes: u32 = 0; - let mut reader_index: Option = None; let mut io_err = winapi::ERROR_SUCCESS; - unsafe { + let reader_index = unsafe { let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! @@ -1314,26 +1313,17 @@ impl OsIpcReceiverSet { assert!(completion_key != INVALID_HANDLE_VALUE); // Find the matching receiver - for (index, ref mut reader) in self.readers.iter_mut().enumerate() { - if completion_key != *reader.handle { - continue; - } - - reader_index = Some(index); - break; - } - } - - if reader_index.is_none() { - panic!("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - } + let (index, _) = self.readers.iter().enumerate() + .find(|&(_, ref reader)| *reader.handle == completion_key) + .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); + index + }; let mut remove_index = None; // We need a scope here for the mutable borrow of self.readers; // we need to (maybe) remove an element from it below. { - let reader_index = reader_index.unwrap(); let reader = &mut self.readers[reader_index]; win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); From 62a775452d32b45cf5af2120b88acca666778e2b Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 31 Oct 2017 17:54:11 +0100 Subject: [PATCH 072/109] windows: cleanup: Use `if let` rather than one-arm `match` The other arm is empty; and since this is on an `Option<>`, we do not benefit from the exhaustiveness check either -- so the more compact variant seems preferable. --- src/platform/windows/mod.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 34aecc1b1..2205a7073 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -870,11 +870,8 @@ impl OsIpcReceiver { loop { // First, try to fetch a message, in case we have one pending // in the reader's receive buffer - match try!(reader.get_message()) { - Some((data, channels, shmems)) => - return Ok((data, channels, shmems)), - None => - {}, + if let Some((data, channels, shmems)) = try!(reader.get_message()) { + return Ok((data, channels, shmems)); } // If the pipe was already closed, we're done -- we've From 86c0891c02a20e0e438ab24832b38d6bfae9746e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 5 Nov 2017 18:37:37 +0100 Subject: [PATCH 073/109] windows: Don't explicitly set `ErrorKind::BrokenPipe` on closed sender This is related to the fix for the `inprocess` back-end in 750d9a12512727e31c9e9bc0616d271a106d7a57 : according to the documentation of `ErrorKind`, just like the related Unix error, `ErrorKind::BrokenPipe` is supposed to signal only a "receiver closed" condition -- and not "sender closed". Note that the situation is quite confusing here, since the Windows API actually returns a `winapi::ERROR_BROKEN_PIPE` for the "sender closed" condition; so it would seem an obvious choice to turn it into `ErrorKind::BrokenPipe`... Except that this conflicts with the stated purpose according to the documentation. Rather than explicitly deciding on the right `ErrorKind` to use here, we just punt this back to the Windows API, letting it assign whatever `ErrorKind` it deems appropriate for this situation -- thus avoiding a potential confusing discrepancy with other `winapi` users, at the cost of a potential confusing discrepancy with other `ipc-channel` back-ends... The situation here differs from that in the `inprocess` back-end also in that we were already using a distinct `WinError::ChannelClosed` internal state for closed senders, rather than throwing it in with the handling of closed receivers -- so it didn't affect the behaviour of the public `channel_is_closed()` method here, but only the converted `Error` value. (And thus the message printed on `unwrap()`.) --- src/platform/windows/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 2205a7073..46249f5d6 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1630,7 +1630,10 @@ impl From for Error { fn from(error: WinError) -> Error { match error { WinError::ChannelClosed => { - Error::new(ErrorKind::BrokenPipe, "Win channel closed") + // This is the error code we originally got from the Windows API + // to signal the "channel closed" (no sender) condition -- + // so hand it back to the Windows API to create an appropriate `Error` value. + Error::from_raw_os_error(winapi::ERROR_BROKEN_PIPE as i32) }, WinError::NoData => { Error::new(ErrorKind::WouldBlock, "Win channel has no data available") From d13aa7cb5f8062593abc3f4c6c6d86e6471a67d6 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 12 Nov 2017 17:33:06 +0100 Subject: [PATCH 074/109] windows: cleanup: Use `while let` rather than `loop` + `match` More idiomatic code is much shorter, and probably easier to follow too. --- src/platform/windows/mod.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 46249f5d6..d7c86f7d0 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1329,18 +1329,11 @@ impl OsIpcReceiverSet { unsafe { reader.notify_completion(io_err); } // then drain as many messages as we can - loop { - match try!(reader.get_message()) { - Some((data, channels, shmems)) => { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); - }, - None => { - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - break; - }, - } + while let Some((data, channels, shmems)) = try!(reader.get_message()) { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); } + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); // Instead of new data, we might have received a broken pipe notification. // If so, add that to the result and remove the reader from our list. From 4fb4e18742edd7a8133d9908b5c299b93f9da41a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 12 Nov 2017 21:24:39 +0100 Subject: [PATCH 075/109] windows: refactor: More straightforward "closed" status handling Check for the "closed" status right after issuing operations that can set it, rather than only after draining pending messages. This makes the control flow much clearer in general; and it also avoids suggesting that we could have both pending messages and closed status at the same time -- which is not the case. The more straightforward handling flow will also facilitate further refactoring. Note that there is one place where we still do not handle the closed status immediately: if we get a "closed" notification while starting a read in the course of adding a receiver to a set, we actually have to delay reporting the status until the next `select()` call. (Though the check there still happens before trying to drain messages -- even before trying to fetch the completion status, in fact.) We also do not immediately handle the "closed" status when re-initiating the async read after receiving data inside `select()`; keeping the original approach instead for now, where it gets handled as a side effect of the code for the case described above. We aren't changing this bit for now, since returning the "closed" event immediately (rather than delaying to the next `select()` call) will actually be a slight behaviour change, as opposed to just pure refactoring. --- src/platform/windows/mod.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d7c86f7d0..12166e050 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -449,7 +449,10 @@ impl MessageReader { /// /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) unsafe fn start_read(&mut self) -> Result<(),WinError> { - if self.read_in_progress || self.closed { + // There is no valid reason to call this again after getting a channel closed notification. + assert!(!self.closed); + + if self.read_in_progress { return Ok(()); } @@ -623,6 +626,9 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { + // We should never expect having pending data after receiving a channel closed notification. + assert!(!self.closed); + // Never touch the buffer while it's still mutably aliased by the kernel! if self.read_in_progress { return Ok(None); @@ -874,12 +880,6 @@ impl OsIpcReceiver { return Ok((data, channels, shmems)); } - // If the pipe was already closed, we're done -- we've - // already drained all incoming bytes - if reader.closed { - return Err(WinError::ChannelClosed); - } - unsafe { // Then, issue a read if we don't have one already in flight. // We must not issue a read if we have complete unconsumed @@ -908,6 +908,10 @@ impl OsIpcReceiver { // // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) try!(reader.fetch_async_result(blocking_mode)); + + if reader.closed { + return Err(WinError::ChannelClosed); + } } // If we're not blocking, pretend that we are blocking, since we got part of @@ -1328,13 +1332,6 @@ impl OsIpcReceiverSet { // tell it about the completed IO op unsafe { reader.notify_completion(io_err); } - // then drain as many messages as we can - while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); - } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); - // Instead of new data, we might have received a broken pipe notification. // If so, add that to the result and remove the reader from our list. if reader.closed { @@ -1342,6 +1339,18 @@ impl OsIpcReceiverSet { selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); } else { + // Otherwise, drain as many messages as we can. + while let Some((data, channels, shmems)) = try!(reader.get_message()) { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + } + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + + // Now that we are done frobbing the buffer, + // we can safely initiate the next async read operation. + // + // Note: if this operation sets the `closed` status for this reader, + // it will be handled at the beginning of the next `select()` call. unsafe { try!(reader.start_read()); } } } From 9a563445d48257866429d42760d215af0eba99fb Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 13 Nov 2017 01:24:52 +0100 Subject: [PATCH 076/109] windows: Don't delay "sender closed" notification from `select()` Immediately handle the `closed` status -- rather than postponing handling to the next `select()` call -- even if it gets set in `start_read()`. (It already was being reported immediately in case the status got set in `notify_completion()`.) This is undoubtedly the more obvious and desirable behaviour. (I wonder whether we should actually enforce it with a test case?...) Also, the code should be easier to follow this way. --- src/platform/windows/mod.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 12166e050..fce529155 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1332,14 +1332,8 @@ impl OsIpcReceiverSet { // tell it about the completed IO op unsafe { reader.notify_completion(io_err); } - // Instead of new data, we might have received a broken pipe notification. - // If so, add that to the result and remove the reader from our list. - if reader.closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); - remove_index = Some(reader_index); - } else { - // Otherwise, drain as many messages as we can. + if !reader.closed { + // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); @@ -1348,11 +1342,19 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - // - // Note: if this operation sets the `closed` status for this reader, - // it will be handled at the beginning of the next `select()` call. unsafe { try!(reader.start_read()); } } + + // If we got a "sender closed" notification -- + // either instead of new data, + // or while trying to re-initiate an async read after receiving data -- + // add an event to this effect to the result list, + // and remove the reader in question from our set. + if reader.closed { + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); + remove_index = Some(reader_index); + } } if let Some(index) = remove_index { From 5d164cd4342d210af50b95bb1c3a5fe34bd3e0c7 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 13 Nov 2017 21:45:41 +0100 Subject: [PATCH 077/109] windows: Handle channel closed events directly where possible Only use the indirection with the `MessageReader.closed` flag where it is really necessary, i.e. where we aren't directly passing the notification to the caller, but rather need to delay it. Specifically, this only happens when getting a "closed" notification while adding a receiver to a set. In all other cases, just work with a regular return status. This simplifies the code in some places, and complicated it in others -- but in *all* cases, the more direct handling should make the code easier to follow... --- src/platform/windows/mod.rs | 93 ++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index fce529155..53e6d6b85 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -380,8 +380,10 @@ struct MessageReader { /// Thus it is crucial that we always set this correctly! read_in_progress: bool, - /// Whether we received a BROKEN_PIPE or other error - /// indicating that the remote end has closed the pipe. + /// We got added to a receiver set + /// after the the sender end of the channel got closed -- + /// so we need to report a "closed" event in the next `select()` call, + /// rather than actually waiting for data from this channel. closed: bool, /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. @@ -517,8 +519,7 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.closed = true; - Ok(()) + Err(WinError::ChannelClosed) }, Err(err) => { Err(WinError::from_system(err, "ReadFile")) @@ -539,7 +540,8 @@ impl MessageReader { /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, err: u32) { + unsafe fn notify_completion(&mut self, err: u32) -> Result<(), WinError> { + assert!(!self.closed); assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -551,9 +553,7 @@ impl MessageReader { // Remote end closed the channel. if err == winapi::ERROR_BROKEN_PIPE { - assert!(!self.closed, "we shouldn't get an async BROKEN_PIPE after we already got one"); - self.closed = true; - return; + return Err(WinError::ChannelClosed); } let nbytes = self.ov.InternalHigh as u32; @@ -571,6 +571,8 @@ impl MessageReader { nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); assert!(new_size <= self.read_buf.capacity()); self.read_buf.set_len(new_size); + + Ok(()) } /// Attempt to conclude an already issued async read operation. @@ -618,10 +620,8 @@ impl MessageReader { // Notify that the read completed, which will update the // read pointers - self.notify_completion(err); + self.notify_completion(err) } - - Ok(()) } fn get_message(&mut self) -> Result, Vec, Vec)>, @@ -709,9 +709,15 @@ impl MessageReader { // Note: Just like in `OsIpcReceiver.receive_message()` below, // this makes us vulnerable to invalid `ov` and `read_buf` modification // from code not marked as unsafe... - try!(self.start_read()); - - Ok(()) + match self.start_read() { + Err(WinError::ChannelClosed) => { + // If the sender has already been closed, we need to stash this information, + // so we can report the corresponding event in the next `select()` call. + self.closed = true; + Ok(()) + } + result => result, + } } } @@ -729,16 +735,25 @@ impl MessageReader { // Because our handle is asynchronous, we have to do a two-part read -- // first issue the operation, then wait for its completion. unsafe { - try!(self.start_read()); - // Sender should not close until it sent as much data as we expect... - if self.closed { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); - } - // In blocking mode, this should never fail... - self.fetch_async_result(BlockingMode::Blocking).unwrap(); - if self.closed { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); - } + match self.start_read() { + Err(WinError::ChannelClosed) => { + // If the helper channel closes unexpectedly + // (i.e. before supplying the expected amount of data), + // don't report that as a "sender closed" condition on the main channel: + // rather, fail with the actual raw error code. + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); + } + Err(err) => return Err(err), + Ok(()) => {} + }; + match self.fetch_async_result(BlockingMode::Blocking) { + Err(WinError::ChannelClosed) => { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) + } + // In blocking mode, `fetch_async_result()` has no other expected failure modes. + Err(_) => unreachable!(), + Ok(()) => {} + }; } } @@ -886,12 +901,6 @@ impl OsIpcReceiver { // messages, because getting a message modifies the read_buf. try!(reader.start_read()); - // If the last read flagged us closed we're done; we've already - // drained all incoming bytes earlier in the loop. - if reader.closed { - return Err(WinError::ChannelClosed); - } - // May return `WinError::NoData` in non-blocking mode. // // The async read remains in flight in that case; @@ -908,10 +917,6 @@ impl OsIpcReceiver { // // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) try!(reader.fetch_async_result(blocking_mode)); - - if reader.closed { - return Err(WinError::ChannelClosed); - } } // If we're not blocking, pretend that we are blocking, since we got part of @@ -1330,9 +1335,15 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); // tell it about the completed IO op - unsafe { reader.notify_completion(io_err); } + let mut closed = unsafe { + match reader.notify_completion(io_err) { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), + } + }; - if !reader.closed { + if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); @@ -1342,7 +1353,13 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - unsafe { try!(reader.start_read()); } + closed = unsafe { + match reader.start_read() { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), + } + }; } // If we got a "sender closed" notification -- @@ -1350,7 +1367,7 @@ impl OsIpcReceiverSet { // or while trying to re-initiate an async read after receiving data -- // add an event to this effect to the result list, // and remove the reader in question from our set. - if reader.closed { + if closed { win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); From 7d6873df15009645d023bab696860bc1fc8390ae Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 14:05:32 +0200 Subject: [PATCH 078/109] windows: cleanup: Simplify control flow --- src/platform/windows/mod.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 53e6d6b85..0d0587359 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1282,14 +1282,9 @@ impl OsIpcReceiverSet { } }); - // if we had prematurely closed elements, just process them first - if !selection_results.is_empty() { - return Ok(selection_results); - } - // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. - loop { + while selection_results.is_empty() { let mut nbytes: u32 = 0; let mut io_err = winapi::ERROR_SUCCESS; @@ -1377,12 +1372,6 @@ impl OsIpcReceiverSet { if let Some(index) = remove_index { self.readers.swap_remove(index); } - - // if we didn't dequeue at least one complete message -- we need to loop through GetQueuedCS again; - // otherwise we're done. - if !selection_results.is_empty() { - break; - } } win32_trace!("select() -> {} results", selection_results.len()); From 5ca5a0ddf81f506c191c8111ecfae9fb090ab03c Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 10:20:58 +0200 Subject: [PATCH 079/109] windows: Introduce `closed_readers` vector instead of `MessageReader.closed` flag Since the `closed` flag was now only being used to signal a reader that has received a "closed" notification while being added to a set, the set can just keep such closed readers in a dedicated vector instead, thus avoiding the need for the `closed` flag altogether. This makes the code simpler, clearer, and less error prone. There is a slight behaviour change resulting from this: the `MessageReader` object of the closed reader is now dropped already while the reader is being added to the set, rather than only on the next `select()` call. This shouldn't have any observable effect, though. --- src/platform/windows/mod.rs | 71 +++++++++++++++---------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 0d0587359..f1f917e8e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -380,12 +380,6 @@ struct MessageReader { /// Thus it is crucial that we always set this correctly! read_in_progress: bool, - /// We got added to a receiver set - /// after the the sender end of the channel got closed -- - /// so we need to report a "closed" event in the next `select()` call, - /// rather than actually waiting for data from this channel. - closed: bool, - /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. @@ -420,7 +414,6 @@ impl MessageReader { ov: Box::new(unsafe { mem::zeroed::() }), read_buf: Vec::new(), read_in_progress: false, - closed: false, set_id: None, } } @@ -451,9 +444,6 @@ impl MessageReader { /// /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) unsafe fn start_read(&mut self) -> Result<(),WinError> { - // There is no valid reason to call this again after getting a channel closed notification. - assert!(!self.closed); - if self.read_in_progress { return Ok(()); } @@ -541,7 +531,6 @@ impl MessageReader { /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, err: u32) -> Result<(), WinError> { - assert!(!self.closed); assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -626,9 +615,6 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { - // We should never expect having pending data after receiving a channel closed notification. - assert!(!self.closed); - // Never touch the buffer while it's still mutably aliased by the kernel! if self.read_in_progress { return Ok(None); @@ -709,15 +695,7 @@ impl MessageReader { // Note: Just like in `OsIpcReceiver.receive_message()` below, // this makes us vulnerable to invalid `ov` and `read_buf` modification // from code not marked as unsafe... - match self.start_read() { - Err(WinError::ChannelClosed) => { - // If the sender has already been closed, we need to stash this information, - // so we can report the corresponding event in the next `select()` call. - self.closed = true; - Ok(()) - } - result => result, - } + self.start_read() } } @@ -1226,6 +1204,13 @@ pub struct OsIpcReceiverSet { /// The set of receivers, stored as MessageReaders. readers: Vec, + + /// Readers that got closed before adding them to the set. + /// + /// These need to report a "closed" event on the next `select()` call. + /// + /// Only the `set_id` is necessary for that. + closed_readers: Vec, } impl OsIpcReceiverSet { @@ -1243,6 +1228,7 @@ impl OsIpcReceiverSet { incrementor: 0.., iocp: WinHandle::new(iocp), readers: vec![], + closed_readers: vec![], }) } } @@ -1252,35 +1238,36 @@ impl OsIpcReceiverSet { let mut reader = receiver.reader.into_inner(); let set_id = self.incrementor.next().unwrap(); - try!(reader.add_to_iocp(&self.iocp, set_id)); - - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); - self.readers.push(reader); + match reader.add_to_iocp(&self.iocp, set_id) { + Ok(()) => { + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); + self.readers.push(reader); + } + Err(WinError::ChannelClosed) => { + // If the sender has already been closed, we need to stash this information, + // so we can report the corresponding event in the next `select()` call. + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", *self.iocp, *reader.handle, set_id); + self.closed_readers.push(set_id); + } + Err(err) => return Err(err), + }; Ok(set_id) } pub fn select(&mut self) -> Result,WinError> { - assert!(!self.readers.is_empty(), "selecting with no objects?"); - win32_trace!("[# {:?}] select() with {} receivers", *self.iocp, self.readers.len()); + assert!(self.readers.len() + self.closed_readers.len() > 0, "selecting with no objects?"); + win32_trace!("[# {:?}] select() with {} active and {} closed receivers", *self.iocp, self.readers.len(), self.closed_readers.len()); // the ultimate results let mut selection_results = vec![]; - // Make a quick first-run check for any closed receivers. - // This will only happen if we have a receiver that - // gets added to the Set after it was closed (the - // router_drops_callbacks_on_cloned_sender_shutdown test - // causes this.) - self.readers.retain(|ref r| { - if r.closed { - selection_results.push(OsIpcSelectionResult::ChannelClosed(r.set_id.unwrap())); - false - } else { - true - } - }); + // Process any pending "closed" events + // from channels that got closed before being added to the set, + // and thus received "closed" notifications while being added. + self.closed_readers.drain(..) + .for_each(|set_id| selection_results.push(OsIpcSelectionResult::ChannelClosed(set_id))); // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. From 15dd4f204331642f7cedf9526dd568cf2a2b4f3a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 21:00:39 +0200 Subject: [PATCH 080/109] windows: cleanup: Rename `WinHandle.take()` to `take_raw()` This makes it more explicit that we are getting a raw handle back rather than another `WinHandle`; and avoids confusion with the ordinary meaning of `take()`, which preserves the type. --- src/platform/windows/mod.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index f1f917e8e..001a2b8de 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -259,7 +259,7 @@ fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Resul fn move_handle_to_process(mut handle: WinHandle, other_process: &WinHandle) -> Result { unsafe { let h = try!(dup_handle_to_process_with_flags( - handle.take(), **other_process, + handle.take_raw(), **other_process, winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS)); Ok(WinHandle::new(h)) } @@ -328,7 +328,7 @@ impl WinHandle { self.h != INVALID_HANDLE_VALUE } - fn take(&mut self) -> HANDLE { + fn take_raw(&mut self) -> HANDLE { mem::replace(&mut self.h, INVALID_HANDLE_VALUE) } } @@ -853,7 +853,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - unsafe { OsIpcReceiver::from_handle(reader.handle.take()) } + unsafe { OsIpcReceiver::from_handle(reader.handle.take_raw()) } } // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then @@ -990,7 +990,7 @@ impl Clone for OsIpcSender { fn clone(&self) -> OsIpcSender { unsafe { let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSender::from_handle(handle.take()) + OsIpcSender::from_handle(handle.take_raw()) } } } @@ -1104,23 +1104,23 @@ impl OsIpcSender { for ref shmem in shared_memory_regions { // shmem.handle, shmem.length let mut remote_handle = try!(dup_handle_to_process(&shmem.handle, &server_h)); - oob.shmem_handles.push((remote_handle.take() as intptr_t, shmem.length as u64)); + oob.shmem_handles.push((remote_handle.take_raw() as intptr_t, shmem.length as u64)); } for port in ports { match port { OsIpcChannel::Sender(s) => { let mut raw_remote_handle = try!(move_handle_to_process(s.handle, &server_h)); - oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, OsIpcChannel::Receiver(r) => { if try!(r.prepare_for_transfer()) == false { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let handle = WinHandle::new(r.reader.into_inner().handle.take()); + let handle = WinHandle::new(r.reader.into_inner().handle.take_raw()); let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); - oob.channel_handles.push(raw_remote_handle.take() as intptr_t); + oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, } } @@ -1138,9 +1138,9 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let handle = WinHandle::new(receiver.reader.into_inner().handle.take()); + let handle = WinHandle::new(receiver.reader.into_inner().handle.take_raw()); let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); - oob.big_data_receiver_handle = Some((raw_receiver_handle.take() as intptr_t, data.len() as u64)); + oob.big_data_receiver_handle = Some((raw_receiver_handle.take_raw() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; Some(sender) @@ -1402,7 +1402,7 @@ impl Clone for OsIpcSharedMemory { fn clone(&self) -> OsIpcSharedMemory { unsafe { let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSharedMemory::from_handle(handle.take(), self.length).unwrap() + OsIpcSharedMemory::from_handle(handle.take_raw(), self.length).unwrap() } } } From eab846020a7296c4d12a58db256549b625ca032e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 21:19:10 +0200 Subject: [PATCH 081/109] windows: cleanup: More idiomatic `for` loops Drop unnecessary explicit `iter()` calls; and don't use reference iterators when we can (and indeed should) consume the values. --- src/platform/windows/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 001a2b8de..0cc4596c8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -633,11 +633,11 @@ impl MessageReader { oob.big_data_receiver_handle); unsafe { - for handle in oob.channel_handles.iter() { - channels.push(OsOpaqueIpcChannel::new(*handle as HANDLE)); + for handle in oob.channel_handles { + channels.push(OsOpaqueIpcChannel::new(handle as HANDLE)); } - for sh in oob.shmem_handles.iter() { + for sh in oob.shmem_handles { shmems.push(OsIpcSharedMemory::from_handle(sh.0 as HANDLE, sh.1 as usize).unwrap()); } From 9925e58ac6b0544293df003ec9424218f40b4ce5 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 21:24:41 +0200 Subject: [PATCH 082/109] windows: cleanup: More idiomatic use of tuples --- src/platform/windows/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 0cc4596c8..4047f1441 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -637,8 +637,8 @@ impl MessageReader { channels.push(OsOpaqueIpcChannel::new(handle as HANDLE)); } - for sh in oob.shmem_handles { - shmems.push(OsIpcSharedMemory::from_handle(sh.0 as HANDLE, sh.1 as usize).unwrap()); + for (handle, size) in oob.shmem_handles { + shmems.push(OsIpcSharedMemory::from_handle(handle as HANDLE, size as usize).unwrap()); } if oob.big_data_receiver_handle.is_some() { From 41e3d29ff5274c89f24d245953bf8b1a0e306017 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 22:08:38 +0200 Subject: [PATCH 083/109] windows: Use `WinHandle` in more places There was a bunch of places that used raw handles, although using the `WinHandle` wrapper works perfectly fine, and there is no reason to forsake the additional guarantees provided by the type system. This slightly complicates the code in some places, while somewhat simplifying it in others -- it pretty much feels cleaner across the board, though. As a side effect, a bunch of functions are not marked `unsafe` anymore, since they were only marked as such (somewhat controversially) for their use of raw handles. --- src/platform/windows/mod.rs | 139 +++++++++++++++++------------------- 1 file changed, 65 insertions(+), 74 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 4047f1441..d7b002ad2 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -29,7 +29,7 @@ use winapi; lazy_static! { static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; - static ref CURRENT_PROCESS_HANDLE: intptr_t = unsafe { kernel32::GetCurrentProcess() as intptr_t }; + static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } @@ -223,46 +223,44 @@ fn make_pipe_name(pipe_id: &Uuid) -> CString { /// /// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is /// also the pseudohandle for the current process). -unsafe fn dup_handle_to_process_with_flags(handle: HANDLE, other_process: HANDLE, flags: winapi::DWORD) - -> Result +fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandle, flags: winapi::DWORD) + -> Result { - if handle == INVALID_HANDLE_VALUE { - return Ok(INVALID_HANDLE_VALUE); + if !handle.is_valid() { + return Ok(WinHandle::invalid()); } - let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let ok = kernel32::DuplicateHandle(*CURRENT_PROCESS_HANDLE as HANDLE, handle, - other_process, &mut new_handle, - 0, winapi::FALSE, flags); - if ok == winapi::FALSE { - Err(WinError::last("DuplicateHandle")) - } else { - Ok(new_handle) + unsafe { + let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; + let ok = kernel32::DuplicateHandle(**CURRENT_PROCESS_HANDLE, **handle, + **other_process, &mut new_handle, + 0, winapi::FALSE, flags); + if ok == winapi::FALSE { + Err(WinError::last("DuplicateHandle")) + } else { + Ok(WinHandle::new(new_handle)) + } } } /// Duplicate a handle in the current process. fn dup_handle(handle: &WinHandle) -> Result { - dup_handle_to_process(handle, &WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE)) + dup_handle_to_process(handle, &WinHandle::new(**CURRENT_PROCESS_HANDLE)) } /// Duplicate a handle to the target process. fn dup_handle_to_process(handle: &WinHandle, other_process: &WinHandle) -> Result { - unsafe { - let h = try!(dup_handle_to_process_with_flags( - **handle, **other_process, winapi::DUPLICATE_SAME_ACCESS)); - Ok(WinHandle::new(h)) - } + dup_handle_to_process_with_flags(handle, other_process, winapi::DUPLICATE_SAME_ACCESS) } /// Duplicate a handle to the target process, closing the source handle. -fn move_handle_to_process(mut handle: WinHandle, other_process: &WinHandle) -> Result { - unsafe { - let h = try!(dup_handle_to_process_with_flags( - handle.take_raw(), **other_process, - winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS)); - Ok(WinHandle::new(h)) - } +fn move_handle_to_process(handle: WinHandle, other_process: &WinHandle) -> Result { + let result = dup_handle_to_process_with_flags(&handle, other_process, + winapi::DUPLICATE_CLOSE_SOURCE | winapi::DUPLICATE_SAME_ACCESS); + // Since the handle was moved to another process, the original is no longer valid; + // so we probably shouldn't try to close it explicitly? + mem::forget(handle); + result } #[derive(Debug)] @@ -632,20 +630,20 @@ impl MessageReader { self.handle, message.data_len, oob.channel_handles.len(), oob.shmem_handles.len(), oob.big_data_receiver_handle); - unsafe { - for handle in oob.channel_handles { - channels.push(OsOpaqueIpcChannel::new(handle as HANDLE)); - } + for handle in oob.channel_handles { + channels.push(OsOpaqueIpcChannel::new(WinHandle::new(handle as HANDLE))); + } - for (handle, size) in oob.shmem_handles { - shmems.push(OsIpcSharedMemory::from_handle(handle as HANDLE, size as usize).unwrap()); - } + for (handle, size) in oob.shmem_handles { + shmems.push(OsIpcSharedMemory::from_handle(WinHandle::new(handle as HANDLE), + size as usize, + ).unwrap()); + } - if oob.big_data_receiver_handle.is_some() { - let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); - let receiver = OsIpcReceiver::from_handle(handle as HANDLE); - big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); - } + if oob.big_data_receiver_handle.is_some() { + let (handle, big_data_size) = oob.big_data_receiver_handle.unwrap(); + let receiver = OsIpcReceiver::from_handle(WinHandle::new(handle as HANDLE)); + big_data = Some(try!(receiver.recv_raw(big_data_size as usize))); } } @@ -813,9 +811,9 @@ impl PartialEq for OsIpcReceiver { } impl OsIpcReceiver { - unsafe fn from_handle(handle: HANDLE) -> OsIpcReceiver { + fn from_handle(handle: WinHandle) -> OsIpcReceiver { OsIpcReceiver { - reader: RefCell::new(MessageReader::new(WinHandle::new(handle))), + reader: RefCell::new(MessageReader::new(handle)), } } @@ -853,7 +851,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - unsafe { OsIpcReceiver::from_handle(reader.handle.take_raw()) } + OsIpcReceiver::from_handle(WinHandle::new(reader.handle.take_raw())) } // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then @@ -988,10 +986,7 @@ pub struct OsIpcSender { impl Clone for OsIpcSender { fn clone(&self) -> OsIpcSender { - unsafe { - let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSender::from_handle(handle.take_raw()) - } + OsIpcSender::from_handle(dup_handle(&self.handle).unwrap()) } } @@ -1005,9 +1000,9 @@ impl OsIpcSender { MAX_FRAGMENT_SIZE } - unsafe fn from_handle(handle: HANDLE) -> OsIpcSender { + fn from_handle(handle: WinHandle) -> OsIpcSender { OsIpcSender { - handle: WinHandle::new(handle), + handle: handle, nosync_marker: PhantomData, } } @@ -1029,7 +1024,7 @@ impl OsIpcSender { win32_trace!("[c {:?}] connect_to_server success", handle); - Ok(OsIpcSender::from_handle(handle)) + Ok(OsIpcSender::from_handle(WinHandle::new(handle))) } } @@ -1047,7 +1042,7 @@ impl OsIpcSender { unsafe { let server_pid = try!(self.get_pipe_server_process_id()); if server_pid == *CURRENT_PROCESS_ID { - return Ok((WinHandle::new(*CURRENT_PROCESS_HANDLE as HANDLE), server_pid)); + return Ok((WinHandle::new(**CURRENT_PROCESS_HANDLE), server_pid)); } let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, @@ -1400,10 +1395,7 @@ impl Drop for OsIpcSharedMemory { impl Clone for OsIpcSharedMemory { fn clone(&self) -> OsIpcSharedMemory { - unsafe { - let mut handle = dup_handle(&self.handle).unwrap(); - OsIpcSharedMemory::from_handle(handle.take_raw(), self.length).unwrap() - } + OsIpcSharedMemory::from_handle(dup_handle(&self.handle).unwrap(), self.length).unwrap() } } @@ -1448,7 +1440,7 @@ impl OsIpcSharedMemory { return Err(WinError::last("CreateFileMapping")); } - OsIpcSharedMemory::from_handle(handle, length) + OsIpcSharedMemory::from_handle(WinHandle::new(handle), length) } } @@ -1458,22 +1450,21 @@ impl OsIpcSharedMemory { // // This function takes ownership of the handle, and will close it // when finished. - unsafe fn from_handle(handle_raw: HANDLE, length: usize) -> Result { - // turn this into a WinHandle, because that will - // take care of closing it - let handle = WinHandle::new(handle_raw); - let address = kernel32::MapViewOfFile(handle_raw, - winapi::FILE_MAP_ALL_ACCESS, - 0, 0, 0); - if address.is_null() { - return Err(WinError::last("MapViewOfFile")); - } + fn from_handle(handle: WinHandle, length: usize) -> Result { + unsafe { + let address = kernel32::MapViewOfFile(*handle, + winapi::FILE_MAP_ALL_ACCESS, + 0, 0, 0); + if address.is_null() { + return Err(WinError::last("MapViewOfFile")); + } - Ok(OsIpcSharedMemory { - handle: handle, - ptr: address as *mut u8, - length: length - }) + Ok(OsIpcSharedMemory { + handle: handle, + ptr: address as *mut u8, + length: length + }) + } } pub fn from_byte(byte: u8, length: usize) -> OsIpcSharedMemory { @@ -1532,7 +1523,7 @@ pub enum OsIpcChannel { #[derive(PartialEq, Debug)] pub struct OsOpaqueIpcChannel { - handle: HANDLE, + handle: WinHandle, } impl Drop for OsOpaqueIpcChannel { @@ -1542,23 +1533,23 @@ impl Drop for OsOpaqueIpcChannel { // The `OsOpaqueIpcChannel` objects should always be used, // i.e. converted with `to_sender()` or `to_receiver()` -- // so the value should already be unset before the object gets dropped. - debug_assert!(self.handle == INVALID_HANDLE_VALUE); + debug_assert!(!self.handle.is_valid()); } } impl OsOpaqueIpcChannel { - fn new(handle: HANDLE) -> OsOpaqueIpcChannel { + fn new(handle: WinHandle) -> OsOpaqueIpcChannel { OsOpaqueIpcChannel { handle: handle, } } pub fn to_receiver(&mut self) -> OsIpcReceiver { - unsafe { OsIpcReceiver::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } + OsIpcReceiver::from_handle(WinHandle::new(self.handle.take_raw())) } pub fn to_sender(&mut self) -> OsIpcSender { - unsafe { OsIpcSender::from_handle(mem::replace(&mut self.handle, INVALID_HANDLE_VALUE)) } + OsIpcSender::from_handle(WinHandle::new(self.handle.take_raw())) } } From 85f1eaafa575639e7f0ea4233c820090a118ace1 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 29 Apr 2018 23:19:45 +0200 Subject: [PATCH 084/109] windows: refactor: (Re-)introduce `WinHandle.take()` with expected meaning Now that `WinHandle` is used in more places, many previous uses of `take_raw()` now actually benefit from a proper `take()` method, returning another `WinHandle` while invalidating the original one. --- src/platform/windows/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index d7b002ad2..e29457064 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -329,6 +329,10 @@ impl WinHandle { fn take_raw(&mut self) -> HANDLE { mem::replace(&mut self.h, INVALID_HANDLE_VALUE) } + + fn take(&mut self) -> WinHandle { + WinHandle::new(self.take_raw()) + } } /// Main object keeping track of a receive handle and its associated state. @@ -851,7 +855,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); assert!(!reader.read_in_progress); - OsIpcReceiver::from_handle(WinHandle::new(reader.handle.take_raw())) + OsIpcReceiver::from_handle(reader.handle.take()) } // This is only used for recv/try_recv. When this is added to an IpcReceiverSet, then @@ -1113,7 +1117,7 @@ impl OsIpcSender { panic!("Sending receiver with outstanding partial read buffer, noooooo! What should even happen?"); } - let handle = WinHandle::new(r.reader.into_inner().handle.take_raw()); + let handle = r.reader.into_inner().handle.take(); let mut raw_remote_handle = try!(move_handle_to_process(handle, &server_h)); oob.channel_handles.push(raw_remote_handle.take_raw() as intptr_t); }, @@ -1133,7 +1137,7 @@ impl OsIpcSender { }; // Put the receiver in the OOB data - let handle = WinHandle::new(receiver.reader.into_inner().handle.take_raw()); + let handle = receiver.reader.into_inner().handle.take(); let mut raw_receiver_handle = try!(move_handle_to_process(handle, &server_h)); oob.big_data_receiver_handle = Some((raw_receiver_handle.take_raw() as intptr_t, data.len() as u64)); oob.target_process_id = server_pid; @@ -1545,11 +1549,11 @@ impl OsOpaqueIpcChannel { } pub fn to_receiver(&mut self) -> OsIpcReceiver { - OsIpcReceiver::from_handle(WinHandle::new(self.handle.take_raw())) + OsIpcReceiver::from_handle(self.handle.take()) } pub fn to_sender(&mut self) -> OsIpcSender { - OsIpcSender::from_handle(WinHandle::new(self.handle.take_raw())) + OsIpcSender::from_handle(self.handle.take()) } } From ddb4515488eb8cfff3d00fab84057ed3ad66d788 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 07:09:28 +0200 Subject: [PATCH 085/109] windows: Use explicit `as_raw()` on WinHandle instead of `deref()` The use of dereferencing to get at the raw handle always felt unintuitive to me, and hard to keep track of. Using a more explicit `as_raw()` method instead seems clearer. (It should also be more flexible I think -- though we aren't actually making use of any of the added possibilities...) --- src/platform/windows/mod.rs | 73 +++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e29457064..9599c03c5 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -232,8 +232,8 @@ fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandl unsafe { let mut new_handle: HANDLE = INVALID_HANDLE_VALUE; - let ok = kernel32::DuplicateHandle(**CURRENT_PROCESS_HANDLE, **handle, - **other_process, &mut new_handle, + let ok = kernel32::DuplicateHandle(CURRENT_PROCESS_HANDLE.as_raw(), handle.as_raw(), + other_process.as_raw(), &mut new_handle, 0, winapi::FALSE, flags); if ok == winapi::FALSE { Err(WinError::last("DuplicateHandle")) @@ -245,7 +245,7 @@ fn dup_handle_to_process_with_flags(handle: &WinHandle, other_process: &WinHandl /// Duplicate a handle in the current process. fn dup_handle(handle: &WinHandle) -> Result { - dup_handle_to_process(handle, &WinHandle::new(**CURRENT_PROCESS_HANDLE)) + dup_handle_to_process(handle, &WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw())) } /// Duplicate a handle to the target process. @@ -288,15 +288,6 @@ impl Default for WinHandle { } } -impl Deref for WinHandle { - type Target = HANDLE; - - #[inline] - fn deref(&self) -> &HANDLE { - &self.h - } -} - impl PartialEq for WinHandle { fn eq(&self, other: &WinHandle) -> bool { // FIXME This does not actually implement the desired behaviour: @@ -326,6 +317,10 @@ impl WinHandle { self.h != INVALID_HANDLE_VALUE } + fn as_raw(&self) -> HANDLE { + self.h + } + fn take_raw(&mut self) -> HANDLE { mem::replace(&mut self.h, INVALID_HANDLE_VALUE) } @@ -423,7 +418,7 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.read_in_progress { - kernel32::CancelIoEx(*self.handle, self.ov.deref_mut()); + kernel32::CancelIoEx(self.handle.as_raw(), self.ov.deref_mut()); self.read_in_progress = false; } } @@ -467,7 +462,7 @@ impl MessageReader { let mut bytes_read: u32 = 0; let ok = { let remaining_buf = &mut self.read_buf[buf_len..]; - kernel32::ReadFile(*self.handle, + kernel32::ReadFile(self.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, @@ -594,7 +589,7 @@ impl MessageReader { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, }; - let ok = kernel32::GetOverlappedResult(*self.handle, + let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), self.ov.deref_mut(), &mut nbytes, block); @@ -681,9 +676,9 @@ impl MessageReader { unsafe { assert!(self.set_id.is_none()); - let ret = kernel32::CreateIoCompletionPort(*self.handle, - **iocp, - *self.handle as winapi::ULONG_PTR, + let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), + iocp.as_raw(), + self.handle.as_raw() as winapi::ULONG_PTR, 0); if ret.is_null() { return Err(WinError::last("CreateIoCompletionPort")); @@ -761,7 +756,7 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), let mut sz: u32 = 0; let bytes_to_write = &bytes[written..]; unsafe { - if kernel32::WriteFile(**handle, + if kernel32::WriteFile(handle.as_raw(), bytes_to_write.as_ptr() as LPVOID, bytes_to_write.len() as u32, &mut sz, @@ -779,7 +774,7 @@ fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), } }, AtomicMode::Nonatomic => { - win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", **handle, sz, written, total, GetLastError()); + win32_trace!("[c {:?}] ... wrote {} bytes, total {}/{} err {}", handle.as_raw(), sz, written, total, GetLastError()); }, } } @@ -927,7 +922,7 @@ impl OsIpcReceiver { // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... let mut ov = Box::new(mem::zeroed::()); - let ok = kernel32::ConnectNamedPipe(**handle, ov.deref_mut()); + let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.deref_mut()); // we should always get FALSE with async IO assert!(ok == winapi::FALSE); @@ -936,7 +931,7 @@ impl OsIpcReceiver { match err { // did we successfully connect? (it's reported as an error [ok==false]) winapi::ERROR_PIPE_CONNECTED => { - win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", **handle); + win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle.as_raw()); Ok(()) }, @@ -946,14 +941,14 @@ impl OsIpcReceiver { // the pipe that we'll be able to read. So we need to go do some reads // like normal and wait until ReadFile gives us ERROR_NO_DATA. winapi::ERROR_NO_DATA => { - win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", **handle); + win32_trace!("[$ {:?}] accept (ERROR_NO_DATA)", handle.as_raw()); Ok(()) }, // the connect is pending; wait for it to complete winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(**handle, ov.deref_mut(), &mut nbytes, winapi::TRUE); + let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } @@ -962,7 +957,7 @@ impl OsIpcReceiver { // Anything else signifies some actual I/O error. err => { - win32_trace!("[$ {:?}] accept error -> {}", **handle, err); + win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), err); Err(WinError::last("ConnectNamedPipe")) }, } @@ -1035,7 +1030,7 @@ impl OsIpcSender { fn get_pipe_server_process_id(&self) -> Result { unsafe { let mut server_pid: winapi::ULONG = 0; - if kernel32::GetNamedPipeServerProcessId(*self.handle, &mut server_pid) == winapi::FALSE { + if kernel32::GetNamedPipeServerProcessId(self.handle.as_raw(), &mut server_pid) == winapi::FALSE { return Err(WinError::last("GetNamedPipeServerProcessId")); } Ok(server_pid) @@ -1046,7 +1041,7 @@ impl OsIpcSender { unsafe { let server_pid = try!(self.get_pipe_server_process_id()); if server_pid == *CURRENT_PROCESS_ID { - return Ok((WinHandle::new(**CURRENT_PROCESS_HANDLE), server_pid)); + return Ok((WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw()), server_pid)); } let raw_handle = kernel32::OpenProcess(winapi::PROCESS_DUP_HANDLE, @@ -1072,7 +1067,7 @@ impl OsIpcSender { /// An internal-use-only send method that sends just raw data, with no header. fn send_raw(&self, data: &[u8]) -> Result<(),WinError> { - win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", *self.handle, data.len(), *CURRENT_PROCESS_ID, + win32_trace!("[c {:?}] writing {} bytes raw to (pid {}->{})", self.handle.as_raw(), data.len(), *CURRENT_PROCESS_ID, try!(self.get_pipe_server_process_id())); // Write doesn't need to be atomic, @@ -1240,13 +1235,13 @@ impl OsIpcReceiverSet { match reader.add_to_iocp(&self.iocp, set_id) { Ok(()) => { - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", *self.iocp, *reader.handle, set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); self.readers.push(reader); } Err(WinError::ChannelClosed) => { // If the sender has already been closed, we need to stash this information, // so we can report the corresponding event in the next `select()` call. - win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", *self.iocp, *reader.handle, set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); self.closed_readers.push(set_id); } Err(err) => return Err(err), @@ -1257,7 +1252,7 @@ impl OsIpcReceiverSet { pub fn select(&mut self) -> Result,WinError> { assert!(self.readers.len() + self.closed_readers.len() > 0, "selecting with no objects?"); - win32_trace!("[# {:?}] select() with {} active and {} closed receivers", *self.iocp, self.readers.len(), self.closed_readers.len()); + win32_trace!("[# {:?}] select() with {} active and {} closed receivers", self.iocp.as_raw(), self.readers.len(), self.closed_readers.len()); // the ultimate results let mut selection_results = vec![]; @@ -1278,12 +1273,12 @@ impl OsIpcReceiverSet { let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! - let ok = kernel32::GetQueuedCompletionStatus(*self.iocp, + let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), &mut nbytes, &mut completion_key as *mut _ as *mut winapi::ULONG_PTR, &mut ov_ptr, winapi::INFINITE); - win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", *self.iocp, ok, nbytes, completion_key); + win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. @@ -1301,7 +1296,7 @@ impl OsIpcReceiverSet { // Find the matching receiver let (index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| *reader.handle == completion_key) + .find(|&(_, ref reader)| reader.handle.as_raw() == completion_key) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); index }; @@ -1313,7 +1308,7 @@ impl OsIpcReceiverSet { { let reader = &mut self.readers[reader_index]; - win32_trace!("[# {:?}] result for receiver {:?}", *self.iocp, *reader.handle); + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); // tell it about the completed IO op let mut closed = unsafe { @@ -1327,10 +1322,10 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", *self.iocp, *reader.handle, reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. @@ -1349,7 +1344,7 @@ impl OsIpcReceiverSet { // add an event to this effect to the result list, // and remove the reader in question from our set. if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", *self.iocp, *reader.handle, reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); remove_index = Some(reader_index); } @@ -1456,7 +1451,7 @@ impl OsIpcSharedMemory { // when finished. fn from_handle(handle: WinHandle, length: usize) -> Result { unsafe { - let address = kernel32::MapViewOfFile(*handle, + let address = kernel32::MapViewOfFile(handle.as_raw(), winapi::FILE_MAP_ALL_ACCESS, 0, 0, 0); if address.is_null() { From ec94c030a8e7d755d49d97f9a10119b82ae5bbdd Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 07:43:32 +0200 Subject: [PATCH 086/109] windows: cleanup: Simpler casting for `completion_key` Do the type casting in a different place, which makes it quite trivial compared to the somewhat icky pointer conversion needed before. --- src/platform/windows/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 9599c03c5..505084d7a 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1270,12 +1270,12 @@ impl OsIpcReceiverSet { let mut io_err = winapi::ERROR_SUCCESS; let reader_index = unsafe { - let mut completion_key: HANDLE = INVALID_HANDLE_VALUE; + let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), &mut nbytes, - &mut completion_key as *mut _ as *mut winapi::ULONG_PTR, + &mut completion_key, &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); @@ -1292,11 +1292,11 @@ impl OsIpcReceiverSet { } assert!(!ov_ptr.is_null()); - assert!(completion_key != INVALID_HANDLE_VALUE); + assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); // Find the matching receiver let (index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() == completion_key) + .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); index }; From c6c504823155949762a8ec8496596f7832e20ed1 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 1 May 2018 13:16:12 +0200 Subject: [PATCH 087/109] windows: cleanup: Tighten scope of `nbytes` local variable --- src/platform/windows/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 505084d7a..448cb5ace 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1266,10 +1266,10 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let mut nbytes: u32 = 0; let mut io_err = winapi::ERROR_SUCCESS; let reader_index = unsafe { + let mut nbytes: u32 = 0; let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! From 27c77eca8c032e2bd5c9baaf93a8c6c03b283f62 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 1 May 2018 13:18:00 +0200 Subject: [PATCH 088/109] windows: cleanup: Streamline handling of reader list in `select()` When receiving an event for a reader, instead of keeping it in the list, and only removing it at the end if it got closed, we now remove it immediately, only to add it back later when a new read is started successfully. This simplifies the code a bit; and it should be more robust too, since a reader's presence in the list is now tied more closely to it having a read in flight, i.e. being able to receive events. --- src/platform/windows/mod.rs | 90 ++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 448cb5ace..42d887d7e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -415,6 +415,14 @@ impl MessageReader { } } + fn take(&mut self) -> MessageReader { + // This is currently somewhat inefficient, + // because of the initialisation of things that won't be used. + // Moving the data items of `MessageReader` into an enum will fix this, + // as that way we will be able to just define a data-less `Invalid` case. + mem::replace(self, MessageReader::new(WinHandle::invalid())) + } + fn cancel_io(&mut self) { unsafe { if self.read_in_progress { @@ -1301,57 +1309,55 @@ impl OsIpcReceiverSet { index }; - let mut remove_index = None; + // Remove the entry from the set for now -- we will re-add it later, + // if we can successfully initiate another async read operation. + let mut reader = self.readers.swap_remove(reader_index); - // We need a scope here for the mutable borrow of self.readers; - // we need to (maybe) remove an element from it below. - { - let reader = &mut self.readers[reader_index]; + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + // tell it about the completed IO op + let mut closed = unsafe { + match reader.notify_completion(io_err) { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), + } + }; - // tell it about the completed IO op - let mut closed = unsafe { - match reader.notify_completion(io_err) { - Ok(()) => false, + if !closed { + // Drain as many messages as we can. + while let Some((data, channels, shmems)) = try!(reader.get_message()) { + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + } + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + + // Now that we are done frobbing the buffer, + // we can safely initiate the next async read operation. + closed = unsafe { + match reader.start_read() { + Ok(()) => { + // We just successfully reinstated it as an active reader -- + // so add it back to the list. + // + // Note: `take()` is a workaround for the compiler not seeing + // that we won't actually be using it anymore after this... + self.readers.push(reader.take()); + false + } Err(WinError::ChannelClosed) => true, Err(err) => return Err(err), } }; - - if !closed { - // Drain as many messages as we can. - while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); - } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - - // Now that we are done frobbing the buffer, - // we can safely initiate the next async read operation. - closed = unsafe { - match reader.start_read() { - Ok(()) => false, - Err(WinError::ChannelClosed) => true, - Err(err) => return Err(err), - } - }; - } - - // If we got a "sender closed" notification -- - // either instead of new data, - // or while trying to re-initiate an async read after receiving data -- - // add an event to this effect to the result list, - // and remove the reader in question from our set. - if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); - remove_index = Some(reader_index); - } } - if let Some(index) = remove_index { - self.readers.swap_remove(index); + // If we got a "sender closed" notification -- + // either instead of new data, + // or while trying to re-initiate an async read after receiving data -- + // add an event to this effect to the result list. + if closed { + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); } } From 4a27fe4da0eb7d8ad664ccd5a6f89d90bd99bbcc Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 1 May 2018 14:46:15 +0200 Subject: [PATCH 089/109] windows: Fix `unsafe` coverage in `OsIpcReceiverSet.select()` Correct IOCP event handling is critical to the soundness of the `select()` code: mixing it up might result in trying to access a buffer and `OVERLAPPED` structure that the kernel is not actually done with yet... This means that all code on the path from receiving IOCP events, to changing the status of the corresponding readers in `notify_completion()`, needs to be considered unsafe. Handling the results of `notify_completion()` and `start_read()` on the other hand isn't unsafe at all, since neither affects the individual readers' `read_in_progress` flags -- which have sole responsibility for keeping track of kernel aliasing. The worst that could happen here is trying to fetch messages from the a reader which didn't actually receive (which is begnign), and/or losing track of which readers are still open. --- src/platform/windows/mod.rs | 62 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 42d887d7e..058010abc 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1274,9 +1274,7 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let mut io_err = winapi::ERROR_SUCCESS; - - let reader_index = unsafe { + let (mut reader, result) = unsafe { let mut nbytes: u32 = 0; let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); @@ -1287,6 +1285,8 @@ impl OsIpcReceiverSet { &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); + + let mut io_err = winapi::ERROR_SUCCESS; if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. @@ -1303,25 +1303,25 @@ impl OsIpcReceiverSet { assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); // Find the matching receiver - let (index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) - .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - index - }; + let (reader_index, _) = self.readers.iter().enumerate() + .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) + .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - // Remove the entry from the set for now -- we will re-add it later, - // if we can successfully initiate another async read operation. - let mut reader = self.readers.swap_remove(reader_index); + // Remove the entry from the set for now -- we will re-add it later, + // if we can successfully initiate another async read operation. + let mut reader = self.readers.swap_remove(reader_index); - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); - // tell it about the completed IO op - let mut closed = unsafe { - match reader.notify_completion(io_err) { - Ok(()) => false, - Err(WinError::ChannelClosed) => true, - Err(err) => return Err(err), - } + // tell it about the completed IO op + let result = reader.notify_completion(io_err); + (reader, result) + }; + + let mut closed = match result { + Ok(()) => false, + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), }; if !closed { @@ -1334,20 +1334,18 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - closed = unsafe { - match reader.start_read() { - Ok(()) => { - // We just successfully reinstated it as an active reader -- - // so add it back to the list. - // - // Note: `take()` is a workaround for the compiler not seeing - // that we won't actually be using it anymore after this... - self.readers.push(reader.take()); - false - } - Err(WinError::ChannelClosed) => true, - Err(err) => return Err(err), + closed = match unsafe { reader.start_read() } { + Ok(()) => { + // We just successfully reinstated it as an active reader -- + // so add it back to the list. + // + // Note: `take()` is a workaround for the compiler not seeing + // that we won't actually be using it anymore after this... + self.readers.push(reader.take()); + false } + Err(WinError::ChannelClosed) => true, + Err(err) => return Err(err), }; } From a46804d7899a585b9552d43048d639f5c638e3a5 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 08:30:31 +0200 Subject: [PATCH 090/109] windows: cleanup: Be more explicit about completion key creation --- src/platform/windows/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 058010abc..b9d161d13 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -684,9 +684,10 @@ impl MessageReader { unsafe { assert!(self.set_id.is_none()); + let completion_key = self.handle.as_raw() as winapi::ULONG_PTR; let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), iocp.as_raw(), - self.handle.as_raw() as winapi::ULONG_PTR, + completion_key, 0); if ret.is_null() { return Err(WinError::last("CreateIoCompletionPort")); From 5732e83901ca9d5f3ecfb13c3bbc8ac0f48ff4ed Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 30 Apr 2018 09:38:06 +0200 Subject: [PATCH 091/109] windows: Rename `set_id` to `entry_id` `set_id` kept misleading me, sounding like it's the ID of the set the receiver is part of, rather than the ID of the receiver within the set... --- src/platform/windows/mod.rs | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b9d161d13..1f3faa479 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -382,7 +382,7 @@ struct MessageReader { /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. /// /// `None` if this `MessageReader` is not part of any set. - set_id: Option, + entry_id: Option, } // We need to explicitly declare this, because of the raw pointer @@ -411,7 +411,7 @@ impl MessageReader { ov: Box::new(unsafe { mem::zeroed::() }), read_buf: Vec::new(), read_in_progress: false, - set_id: None, + entry_id: None, } } @@ -680,9 +680,9 @@ impl MessageReader { Ok(result) } - fn add_to_iocp(&mut self, iocp: &WinHandle, set_id: u64) -> Result<(),WinError> { + fn add_to_iocp(&mut self, iocp: &WinHandle, entry_id: u64) -> Result<(),WinError> { unsafe { - assert!(self.set_id.is_none()); + assert!(self.entry_id.is_none()); let completion_key = self.handle.as_raw() as winapi::ULONG_PTR; let ret = kernel32::CreateIoCompletionPort(self.handle.as_raw(), @@ -693,7 +693,7 @@ impl MessageReader { return Err(WinError::last("CreateIoCompletionPort")); } - self.set_id = Some(set_id); + self.entry_id = Some(entry_id); // Make sure that the reader has a read in flight, // otherwise a later select() will hang. @@ -868,7 +868,7 @@ impl OsIpcReceiver { fn receive_message(&self, mut blocking_mode: BlockingMode) -> Result<(Vec, Vec, Vec),WinError> { let mut reader = self.reader.borrow_mut(); - assert!(reader.set_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); + assert!(reader.entry_id.is_none(), "receive_message is only valid before this OsIpcReceiver was added to a Set"); // This function loops, because in the case of a blocking read, we may need to // read multiple sets of bytes from the pipe to receive a complete message. @@ -1212,7 +1212,7 @@ pub struct OsIpcReceiverSet { /// /// These need to report a "closed" event on the next `select()` call. /// - /// Only the `set_id` is necessary for that. + /// Only the `entry_id` is necessary for that. closed_readers: Vec, } @@ -1240,23 +1240,23 @@ impl OsIpcReceiverSet { // consume the receiver, and take the reader out let mut reader = receiver.reader.into_inner(); - let set_id = self.incrementor.next().unwrap(); + let entry_id = self.incrementor.next().unwrap(); - match reader.add_to_iocp(&self.iocp, set_id) { + match reader.add_to_iocp(&self.iocp, entry_id) { Ok(()) => { - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); self.readers.push(reader); } Err(WinError::ChannelClosed) => { // If the sender has already been closed, we need to stash this information, // so we can report the corresponding event in the next `select()` call. - win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), set_id); - self.closed_readers.push(set_id); + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); + self.closed_readers.push(entry_id); } Err(err) => return Err(err), }; - Ok(set_id) + Ok(entry_id) } pub fn select(&mut self) -> Result,WinError> { @@ -1270,7 +1270,7 @@ impl OsIpcReceiverSet { // from channels that got closed before being added to the set, // and thus received "closed" notifications while being added. self.closed_readers.drain(..) - .for_each(|set_id| selection_results.push(OsIpcSelectionResult::ChannelClosed(set_id))); + .for_each(|entry_id| selection_results.push(OsIpcSelectionResult::ChannelClosed(entry_id))); // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. @@ -1328,10 +1328,10 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::DataReceived(reader.set_id.unwrap(), data, channels, shmems)); + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + selection_results.push(OsIpcSelectionResult::DataReceived(reader.entry_id.unwrap(), data, channels, shmems)); } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. @@ -1355,8 +1355,8 @@ impl OsIpcReceiverSet { // or while trying to re-initiate an async read after receiving data -- // add an event to this effect to the result list. if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.set_id.unwrap()); - selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.set_id.unwrap())); + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.entry_id.unwrap())); } } From 4b6f122894376657f879cfc1a766a6c5f6e48692 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 5 May 2018 17:52:17 +0200 Subject: [PATCH 092/109] windows: cleanup: More idiomatic handling of `io_err` Initialise using exhaustive conditionals, avoiding mutation. --- src/platform/windows/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 1f3faa479..ec88d9f2e 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -592,7 +592,6 @@ impl MessageReader { // Get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; - let mut err = winapi::ERROR_SUCCESS; let block = match blocking_mode { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, @@ -601,8 +600,8 @@ impl MessageReader { self.ov.deref_mut(), &mut nbytes, block); - if ok == winapi::FALSE { - err = GetLastError(); + let io_err = if ok == winapi::FALSE { + let err = GetLastError(); if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Async read hasn't completed yet. // Inform the caller, while keeping the read in flight. @@ -610,11 +609,14 @@ impl MessageReader { } // We pass err through to notify_completion so // that it can handle other errors. - } + err + } else { + winapi::ERROR_SUCCESS + }; // Notify that the read completed, which will update the // read pointers - self.notify_completion(err) + self.notify_completion(io_err) } } @@ -1286,9 +1288,7 @@ impl OsIpcReceiverSet { &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - - let mut io_err = winapi::ERROR_SUCCESS; - if ok == winapi::FALSE { + let io_err = if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. // Otherwise, the async IO operation failed, and @@ -1297,8 +1297,10 @@ impl OsIpcReceiverSet { return Err(WinError::last("GetQueuedCompletionStatus")); } - io_err = GetLastError(); - } + GetLastError() + } else { + winapi::ERROR_SUCCESS + }; assert!(!ov_ptr.is_null()); assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); From b05b8a62cb40f79c7f0f7ef90453170a14ad39c5 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 5 May 2018 18:37:58 +0200 Subject: [PATCH 093/109] windows: cleanup: Pass status as a proper `Result<>` Rather than passing a naked error code (with a magic value denoting success), use a standard `Result<>` enum. --- src/platform/windows/mod.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ec88d9f2e..152da9054 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -535,7 +535,7 @@ impl MessageReader { /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, err: u32) -> Result<(), WinError> { + unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { assert!(self.read_in_progress); win32_trace!("[$ {:?}] notify_completion", self.handle); @@ -546,7 +546,7 @@ impl MessageReader { self.read_in_progress = false; // Remote end closed the channel. - if err == winapi::ERROR_BROKEN_PIPE { + if io_result == Err(winapi::ERROR_BROKEN_PIPE) { return Err(WinError::ChannelClosed); } @@ -555,8 +555,10 @@ impl MessageReader { assert!(offset == 0); - if err != winapi::ERROR_SUCCESS { - // This should never happen + if let Err(err) = io_result { + // Other errors shouldn't come up here... + // If they do, we don't really understand the situation -- + // so we can't handle this gracefully. panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); } @@ -600,7 +602,7 @@ impl MessageReader { self.ov.deref_mut(), &mut nbytes, block); - let io_err = if ok == winapi::FALSE { + let io_result = if ok == winapi::FALSE { let err = GetLastError(); if blocking_mode == BlockingMode::Nonblocking && err == winapi::ERROR_IO_INCOMPLETE { // Async read hasn't completed yet. @@ -609,14 +611,14 @@ impl MessageReader { } // We pass err through to notify_completion so // that it can handle other errors. - err + Err(err) } else { - winapi::ERROR_SUCCESS + Ok(()) }; // Notify that the read completed, which will update the // read pointers - self.notify_completion(io_err) + self.notify_completion(io_result) } } @@ -1288,18 +1290,18 @@ impl OsIpcReceiverSet { &mut ov_ptr, winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - let io_err = if ok == winapi::FALSE { + let io_result = if ok == winapi::FALSE { // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. // Otherwise, the async IO operation failed, and - // we want to hand io_err to notify_completion below. + // we want to hand the error to notify_completion below. if ov_ptr.is_null() { return Err(WinError::last("GetQueuedCompletionStatus")); } - GetLastError() + Err(GetLastError()) } else { - winapi::ERROR_SUCCESS + Ok(()) }; assert!(!ov_ptr.is_null()); @@ -1317,7 +1319,7 @@ impl OsIpcReceiverSet { win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); // tell it about the completed IO op - let result = reader.notify_completion(io_err); + let result = reader.notify_completion(io_result); (reader, result) }; From 464f3a9fca43c7db93aaa0965919323ae337a750 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 5 May 2018 18:47:12 +0200 Subject: [PATCH 094/109] windows: cleanup: `match` on errors in `notify_completion()` Use exhaustive match in one place rather than scattered conditionals. This should be much easier to follow. --- src/platform/windows/mod.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 152da9054..be05fc5ae 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -545,9 +545,18 @@ impl MessageReader { // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; - // Remote end closed the channel. - if io_result == Err(winapi::ERROR_BROKEN_PIPE) { - return Err(WinError::ChannelClosed); + match io_result { + Ok(()) => {} + Err(winapi::ERROR_BROKEN_PIPE) => { + // Remote end closed the channel. + return Err(WinError::ChannelClosed); + } + Err(err) => { + // Other errors shouldn't come up here... + // If they do, we don't really understand the situation -- + // so we can't handle this gracefully. + panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); + } } let nbytes = self.ov.InternalHigh as u32; @@ -555,13 +564,6 @@ impl MessageReader { assert!(offset == 0); - if let Err(err) = io_result { - // Other errors shouldn't come up here... - // If they do, we don't really understand the situation -- - // so we can't handle this gracefully. - panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); - } - let new_size = self.read_buf.len() + nbytes as usize; win32_trace!("nbytes: {}, offset {}, buf len {}->{}, capacity {}", nbytes, offset, self.read_buf.len(), new_size, self.read_buf.capacity()); From 7ebae596a61a251cb62ccc90ae8ee1cfd74fda8d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 6 May 2018 19:51:00 +0200 Subject: [PATCH 095/109] windows: More defensive `read_in_progress` handling Set the flag before issuing the system call, and only reset it later if it turns out no read was actually started. This way it's less likely that setting the flag gets ommited by mistake, which could have catastrophic effects, since setting this is crucial for tracking the kernel aliasing of `ov` and `read_buf`. --- src/platform/windows/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index be05fc5ae..584393baa 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -466,6 +466,7 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset + self.read_in_progress = true; *self.ov.deref_mut() = mem::zeroed(); let mut bytes_read: u32 = 0; let ok = { @@ -509,14 +510,15 @@ impl MessageReader { // special handling for sync-completed operations. Ok(()) | Err(winapi::ERROR_IO_PENDING) => { - self.read_in_progress = true; Ok(()) }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.read_in_progress = false; Err(WinError::ChannelClosed) }, Err(err) => { + self.read_in_progress = false; Err(WinError::from_system(err, "ReadFile")) }, } From 61c21dbc871e0b9d8737cfe390b26c799c6ea1f8 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 6 May 2018 20:14:09 +0200 Subject: [PATCH 096/109] windows: Make `MessageReader.ov` optional This field is only meaningful while an async read is in progress with the kernel; and we never reuse it between reads. Making this more explicit and robust by only giving it a value while it's in use. --- src/platform/windows/mod.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 584393baa..356201ac1 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -362,7 +362,7 @@ struct MessageReader { /// /// (Unfortunately, there is no way to express this condition in the type system, /// without some fundamental change to how we handle these fields...) - ov: Box, + ov: Option>, /// A read buffer for any pending reads. /// @@ -408,7 +408,7 @@ impl MessageReader { fn new(handle: WinHandle) -> MessageReader { MessageReader { handle: handle, - ov: Box::new(unsafe { mem::zeroed::() }), + ov: None, read_buf: Vec::new(), read_in_progress: false, entry_id: None, @@ -426,7 +426,7 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.read_in_progress { - kernel32::CancelIoEx(self.handle.as_raw(), self.ov.deref_mut()); + kernel32::CancelIoEx(self.handle.as_raw(), self.ov.take().unwrap().deref_mut()); self.read_in_progress = false; } } @@ -467,7 +467,7 @@ impl MessageReader { // issue the read to the buffer, at the current length offset self.read_in_progress = true; - *self.ov.deref_mut() = mem::zeroed(); + self.ov = Some(Box::new(mem::zeroed())); let mut bytes_read: u32 = 0; let ok = { let remaining_buf = &mut self.read_buf[buf_len..]; @@ -475,7 +475,7 @@ impl MessageReader { remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, - self.ov.deref_mut()) + self.ov.as_mut().unwrap().deref_mut()) }; // Reset the vector to only expose the already filled part. @@ -515,10 +515,12 @@ impl MessageReader { Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); self.read_in_progress = false; + self.ov = None; Err(WinError::ChannelClosed) }, Err(err) => { self.read_in_progress = false; + self.ov = None; Err(WinError::from_system(err, "ReadFile")) }, } @@ -546,6 +548,7 @@ impl MessageReader { // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `ov` and `read_buf` fields.) self.read_in_progress = false; + let ov = self.ov.take().unwrap(); match io_result { Ok(()) => {} @@ -561,8 +564,8 @@ impl MessageReader { } } - let nbytes = self.ov.InternalHigh as u32; - let offset = self.ov.Offset; + let nbytes = ov.InternalHigh as u32; + let offset = ov.Offset; assert!(offset == 0); @@ -603,7 +606,7 @@ impl MessageReader { BlockingMode::Nonblocking => winapi::FALSE, }; let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), - self.ov.deref_mut(), + self.ov.as_mut().unwrap().deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { From 2963ea572078d379ce398ad1001003f35a7416a9 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sun, 6 May 2018 23:15:48 +0200 Subject: [PATCH 097/109] windows: Drop explicit `read_in_progress` flag Since the `ov` field is now only set when we have a read in progress, and thus effectively serves as an indicator of that state, the explicit flag became redundant: we can just check the presence of `ov` instead. (And since a check is performed implicitly when unwrapping the `ov` value, some of the explicit asserts on `read_in_progress` can be dropped entirely.) --- src/platform/windows/mod.rs | 48 ++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 356201ac1..b68a49f44 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -346,6 +346,11 @@ struct MessageReader { /// which is registered in the kernel during an async read -- /// to remain stable even when the enclosing structure is passed around. /// + /// Note: Since this field only has a value + /// when an async read operation is in progress + /// (i.e. has been issued to the system, and not completed yet), + /// this also serves as an indicator of the latter. + /// /// WARNING: As the kernel holds a mutable alias of this structure /// while an async read is in progress, /// it is crucial that this value is never accessed in user space @@ -357,8 +362,9 @@ struct MessageReader { /// the compiler cannot guarantee exclusive access the way it normally would, /// i.e. any access to this value is inherently unsafe! /// The only way to avoid undefined behaviour - /// is to always make sure the `read_in_progress` indicator is not set, - /// before performing any access to this value. + /// is to never access this field from user space, + /// except initialising it before issuing the async read to the kernel, + /// and taking out the result value after the kernel signals completion. /// /// (Unfortunately, there is no way to express this condition in the type system, /// without some fundamental change to how we handle these fields...) @@ -366,17 +372,12 @@ struct MessageReader { /// A read buffer for any pending reads. /// - /// WARNING: This has the same safety problem as `ov` above. + /// WARNING: This has the same aliasing problem as `ov` above, + /// meaning it never should be accessed from user space + /// while an async read operation is in progress with the kernel, + /// i.e. while the `ov` field has a value. read_buf: Vec, - /// Indicates whether the kernel currently has an async read in flight for this port. - /// - /// WARNING: Rather than just managing our internal state, - /// this flag plays a critical role in keeping track of kernel aliasing - /// of the `ov` and `read_buf` fields, as explained in the comment for `ov'. - /// Thus it is crucial that we always set this correctly! - read_in_progress: bool, - /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. @@ -410,7 +411,6 @@ impl MessageReader { handle: handle, ov: None, read_buf: Vec::new(), - read_in_progress: false, entry_id: None, } } @@ -425,9 +425,8 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { - if self.read_in_progress { + if self.ov.is_some() { kernel32::CancelIoEx(self.handle.as_raw(), self.ov.take().unwrap().deref_mut()); - self.read_in_progress = false; } } } @@ -447,9 +446,10 @@ impl MessageReader { /// i.e. all code on the path from invoking this method, /// up to receiving the completion event, is unsafe. /// - /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + /// (See documentation of the `ov` and `read_buf` fields.) unsafe fn start_read(&mut self) -> Result<(),WinError> { - if self.read_in_progress { + // Nothing needs to be done if an async read operation is already in progress. + if self.ov.is_some() { return Ok(()); } @@ -466,7 +466,6 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset - self.read_in_progress = true; self.ov = Some(Box::new(mem::zeroed())); let mut bytes_read: u32 = 0; let ok = { @@ -514,12 +513,10 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.read_in_progress = false; self.ov = None; Err(WinError::ChannelClosed) }, Err(err) => { - self.read_in_progress = false; self.ov = None; Err(WinError::from_system(err, "ReadFile")) }, @@ -533,21 +530,18 @@ impl MessageReader { /// would have catastrophic effects, /// as `ov` and `read_buf` are still mutably aliased by the kernel in that case! /// - /// (See documentation of the `ov`, `read_buf` and `read_in_progress` fields.) + /// (See documentation of the `ov` and `read_buf` fields.) /// /// Also, this method relies on `ov` and `read_buf` actually having valid data, /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { - assert!(self.read_in_progress); - win32_trace!("[$ {:?}] notify_completion", self.handle); // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `ov` and `read_buf` fields.) - self.read_in_progress = false; let ov = self.ov.take().unwrap(); match io_result { @@ -597,8 +591,6 @@ impl MessageReader { /// i.e. we are still in unsafe mode in that case! fn fetch_async_result(&mut self, blocking_mode: BlockingMode) -> Result<(), WinError> { unsafe { - assert!(self.read_in_progress); - // Get the overlapped result, blocking if we need to. let mut nbytes: u32 = 0; let block = match blocking_mode { @@ -632,7 +624,7 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! - if self.read_in_progress { + if self.ov.is_some() { return Ok(None); } @@ -869,7 +861,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); - assert!(!reader.read_in_progress); + assert!(reader.ov.is_none()); OsIpcReceiver::from_handle(reader.handle.take()) } @@ -910,7 +902,7 @@ impl OsIpcReceiver { // nothing prevents code that isn't marked as `unsafe` // from performing invalid reads or writes to these fields! // - // (See documentation of `ov`, `read_buf`, and `read_in_progress` fields.) + // (See documentation of `ov` and `read_buf` fields.) try!(reader.fetch_async_result(blocking_mode)); } From 7c1ccfa101407f50d9f962ad5e58fdf778a0fad2 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 7 May 2018 18:02:18 +0200 Subject: [PATCH 098/109] windows: Avoid leaking unsafety outside `unsafe` blocks Introduce an `AliasedCell` wrapper type for the fields that get aliased by the kernel during async read operations, making sure that these values can only be accessed through special unsafe methods, i.e. only inside `unsafe` blocks, even if the wrappers get passed around through safe code. This makes invoking `MessageReader.start_read()` safe; and consequently removes all unsafety from `OsIpcReceiver`. (`OsIpcReceiverSet.select()` retains some unsafe code though, since it does raw I/O itself... This can only be fixed by factoring out that code.) --- src/platform/windows/aliased_cell.rs | 363 +++++++++++++++++++++++++++ src/platform/windows/mod.rs | 319 ++++++++++++----------- 2 files changed, 521 insertions(+), 161 deletions(-) create mode 100644 src/platform/windows/aliased_cell.rs diff --git a/src/platform/windows/aliased_cell.rs b/src/platform/windows/aliased_cell.rs new file mode 100644 index 000000000..6399c40fc --- /dev/null +++ b/src/platform/windows/aliased_cell.rs @@ -0,0 +1,363 @@ +// Copyright 2018 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem::{self, ManuallyDrop}; +use std::thread; + +/// Dummy type that panics when its destructor is invoked. +#[derive(Debug)] +struct DropBomb(); + +impl Drop for DropBomb { + fn drop(&mut self) { + let message = "Trying to drop an AliasedCell, which may still have aliases outstanding."; + if thread::panicking() { + eprintln!("{}", message); + } else { + panic!(message); + } + } +} + +/// A wrapper for unsafely aliased memory locations. +/// +/// `AliasedCell' makes sure that its inner value +/// is completely inaccessible from safe code. +/// Once an `AliasedCell` has been constructed from some value, +/// until the value is moved out again with the (unsafe) `into_inner()` method, +/// the only way to access the wrapped value +/// is through the unsafe `alias_mut()` method. +/// +/// This is useful for FFI calls that take raw pointers as input, +/// and hold on to them even after returning control to the caller. +/// Since Rust's type system is not aware of such aliases, +/// it cannot provide the usual guarantees about validity of pointers +/// and exclusiveness of mutable pointers. +/// This means that any code that has access to the memory in question +/// is inherently unsafe as long as such untracked aliases exist. +/// Putting the value in an `AliasedCell` before the FFI call, +/// and only taking it out again +/// once the caller has ensured that all aliases have been dropped +/// (most likely through another FFI call), +/// makes certain that any such unsafe access to the aliased value +/// can only happen from code marked as `unsafe`. +/// +/// An `AliasedCell` should never be simply dropped in normal use. +/// Rather, it should always be freed explicitly with `into_inner()`, +/// signalling that there are no outstanding aliases anymore. +/// When an `AliasedCell` is dropped unexpectedly, +/// we have to assume that it likely still has untracked aliases, +/// and thus freeing the memory would probably be unsound. +/// Therefore, the `drop()` implementation of `AliasedCell` +/// leaks the inner value instead of freeing it; +/// and throws a panic. +/// Leaking the memory, while undesirable in general, +/// keeps the memory accessible to any outstanding aliases. +/// This is the only way to retain soundness during unwinding, +/// or when the panic gets catched. +/// +/// Note that making FFI access through untracked aliases +/// requires the value to have a stable memory location -- +/// typically by living on the heap rather than on the stack. +/// If the value isn't already in a heap-allocated container +/// such as `Box<>`, `Vec<>`, or `String`, +/// it is the caller's responsibility to wrap it in a `Box<>` explicitly. +/// `AliasedCell` itself cannot ensure that the address remains stable +/// when the `AliasedCell` gets moved around. +#[derive(Debug)] +pub struct AliasedCell { + value: ManuallyDrop, + drop_bomb: DropBomb, +} + +impl AliasedCell { + /// Wrap the provided value in an `AliasedCell`, making it inaccessible from safe code. + pub fn new(value: T) -> AliasedCell { + AliasedCell { + value: ManuallyDrop::new(value), + drop_bomb: DropBomb(), + } + } + + /// Get a pointer to the inner value. + /// + /// Note that this yields a regular reference. + /// To actually get an untracked alias, + /// it needs to be cast or coerced into a raw pointer. + /// This usually happens implicitly though + /// when calling an FFI function (or any other function) + /// taking a raw pointer as argument. + /// + /// `alias_mut()` can be called any number of times: + /// the wrapper doesn't keep track of the number of outstanding aliases -- + /// the caller is responsible for making sure that no aliases are left + /// before invoking `into_inner()`. + /// If you need to track the number of aliases, + /// wrap the inner value in an `Rc<>` or `Arc` -- + /// this way, the reference count will also be inaccessible from safe code. + pub unsafe fn alias_mut(&mut self) -> &mut T { + &mut self.value + } + + /// Move out the wrapped value, making it accessible from safe code again. + pub unsafe fn into_inner(self) -> T { + mem::forget(self.drop_bomb); + ManuallyDrop::into_inner(self.value) + } +} + +/// Some basic tests. +/// +/// Note: These mostly just check that various expected usage scenarios +/// can be compiled and basically work. +/// We can't verify though that the usage is actually sound; +/// nor do we check whether invalid usage is indeed prevented by the compiler... +/// +/// (The latter could probably be remedied though +/// with some sort of compile-fail tests.) +#[cfg(test)] +mod tests { + use super::AliasedCell; + + unsafe fn mutate_value(addr: *mut [i32; 4]) { + let value = addr.as_mut().unwrap(); + value[1] += value[3]; + } + + struct Mutator { + addr: *mut [i32; 4], + ascended: bool, + } + + impl Mutator { + unsafe fn new(addr: *mut [i32; 4]) -> Mutator { + Mutator { + addr: addr, + ascended: false, + } + } + + fn ascend(&mut self) { + self.ascended = true; + } + + unsafe fn mutate(&mut self) { + let value = self.addr.as_mut().unwrap(); + if self.ascended { + value[3] += value[2]; + } else { + value[1] += value[3]; + } + } + } + + #[test] + fn noop_roundtrip() { + let value = [1, 3, 3, 7]; + let cell = AliasedCell::new(Box::new(value)); + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 3, 3, 7]); + } + + #[test] + fn unused_alias() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + cell.alias_mut().as_mut(); + } + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 3, 3, 7]); + } + + #[test] + fn mutate() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 7]); + } + + /// Verify that we can take multiple aliases. + #[test] + fn mutate_twice() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + let new_value = unsafe { + *cell.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + /// Verify that we can do basic safe manipulations between unsafe blocks. + #[test] + fn moves() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + mutate_value(cell.alias_mut().as_mut()); + } + let mut cell2 = cell; + unsafe { + mutate_value(cell2.alias_mut().as_mut()); + } + let cell3 = cell2; + let new_value = unsafe { + *cell3.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + /// Verify that alias can be used at a later point. + #[test] + fn mutate_deferred() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator.mutate(); + } + let new_value = unsafe { + drop(mutator); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 7]); + } + + #[test] + fn mutate_deferred_twice() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator.mutate(); + } + unsafe { + mutator.mutate(); + } + let new_value = unsafe { + drop(mutator); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + /// Further safe manipulations. + #[test] + fn deferred_moves() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + let cell2 = cell; + let mut mutator2 = mutator; + unsafe { + mutator2.mutate(); + } + let cell3 = cell2; + let mutator3 = mutator2; + let new_value = unsafe { + drop(mutator3); + *cell3.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 7]); + } + + /// Non-trivial safe manipulation. + #[test] + fn safe_frobbing() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator.mutate(); + } + mutator.ascend(); + unsafe { + mutator.mutate(); + } + let new_value = unsafe { + drop(mutator); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 10, 3, 10]); + } + + /// Verify that two aliases can exist simultaneously. + #[test] + fn two_mutators() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + let mut mutator1 = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator1.mutate(); + } + let mut mutator2 = unsafe { + Mutator::new(cell.alias_mut().as_mut()) + }; + unsafe { + mutator2.mutate(); + } + let new_value = unsafe { + drop(mutator1); + drop(mutator2); + *cell.into_inner() + }; + assert_eq!(new_value, [1, 17, 3, 7]); + } + + #[test] + #[should_panic(expected = "Trying to drop an AliasedCell, which may still have aliases outstanding.")] + fn invalid_drop() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + let mut mutator = Mutator::new(cell.alias_mut().as_mut()); + mutator.mutate(); + drop(cell); + } + } + + /// Verify that we skip the panic-on-drop while unwinding from another panic. + #[test] + #[should_panic(expected = "bye!")] + fn panic() { + let value = [1, 3, 3, 7]; + let mut cell = AliasedCell::new(Box::new(value)); + unsafe { + let mut mutator = Mutator::new(cell.alias_mut().as_mut()); + mutator.mutate(); + panic!("bye!"); + } + } +} diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index b68a49f44..e00ff61b8 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -27,6 +27,9 @@ use uuid::Uuid; use winapi::{HANDLE, INVALID_HANDLE_VALUE, LPVOID}; use winapi; +mod aliased_cell; +use self::aliased_cell::AliasedCell; + lazy_static! { static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); @@ -361,23 +364,30 @@ struct MessageReader { /// Since Rust's type system is not aware of the kernel alias, /// the compiler cannot guarantee exclusive access the way it normally would, /// i.e. any access to this value is inherently unsafe! - /// The only way to avoid undefined behaviour - /// is to never access this field from user space, - /// except initialising it before issuing the async read to the kernel, - /// and taking out the result value after the kernel signals completion. - /// - /// (Unfortunately, there is no way to express this condition in the type system, - /// without some fundamental change to how we handle these fields...) - ov: Option>, + /// We thus wrap it in an `AliasedCell`, + /// making sure the value is only accessible from code marked `unsafe`; + /// and only move it out when the kernel signals that the async read is done. + ov: Option>>, - /// A read buffer for any pending reads. + /// Buffer for outstanding data, that has been received but not yet processed. /// - /// WARNING: This has the same aliasing problem as `ov` above, - /// meaning it never should be accessed from user space - /// while an async read operation is in progress with the kernel, - /// i.e. while the `ov` field has a value. + /// Note: this is only set while no async read operation + /// is currently in progress with the kernel. + /// When an async read is in progress, + /// the receive buffer is aliased by the kernel just like `ov` above; + /// so we need to temporarily move it into an `AliasedCell` too, + /// thus making it inaccessible from safe code -- + /// see `aliased_buf` below. + /// We only move it back once the kernel signals completion of the async read. read_buf: Vec, + /// Buffer for the kernel to store the results of async read operations. + /// + /// WARNING: This has the same aliasing problem as `ov` above -- + /// i.e. it should never be accessed from user space + /// while an async read operation is in progress. + aliased_buf: Option>>, + /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// /// This is returned to callers of `OsIpcReceiverSet.add()` and `OsIpcReceiverSet.select()`. @@ -411,6 +421,7 @@ impl MessageReader { handle: handle, ov: None, read_buf: Vec::new(), + aliased_buf: None, entry_id: None, } } @@ -426,28 +437,23 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.ov.is_some() { - kernel32::CancelIoEx(self.handle.as_raw(), self.ov.take().unwrap().deref_mut()); + kernel32::CancelIoEx(self.handle.as_raw(), + self.ov.as_mut().unwrap().alias_mut().deref_mut()); + self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); } } } /// Kick off an asynchronous read. /// - /// Note: This is *highly* unsafe, since upon successful return, - /// the `ov` and `read_buf` fields will be left mutably aliased by the kernel - /// (until we receive an event signalling completion of the async read) -- - /// and Rust's type system doesn't know about these aliases! - /// - /// This means that after invoking this method, - /// up to the point where we receive the completion notification, - /// nothing is allowed to access the `ov` and `read_buf` fields; - /// but the compiler cannot guarantee this for us. - /// It is our responsibility to make sure of it -- - /// i.e. all code on the path from invoking this method, - /// up to receiving the completion event, is unsafe. - /// + /// When an async read is started successfully, + /// the receive buffer is moved out of `read_buf` + /// into the `AliasedCell<>` in `aliased_buf`, + /// thus making it inaccessible from safe code; + /// it will only be moved back in `notify_completion()`. /// (See documentation of the `ov` and `read_buf` fields.) - unsafe fn start_read(&mut self) -> Result<(),WinError> { + fn start_read(&mut self) -> Result<(),WinError> { // Nothing needs to be done if an async read operation is already in progress. if self.ov.is_some() { return Ok(()); @@ -459,80 +465,89 @@ impl MessageReader { self.read_buf.reserve(PIPE_BUFFER_SIZE); } - // Temporarily extend the vector to span its entire capacity, - // so we can safely sub-slice it for the actual read. - let buf_len = self.read_buf.len(); - let buf_cap = self.read_buf.capacity(); - self.read_buf.set_len(buf_cap); - - // issue the read to the buffer, at the current length offset - self.ov = Some(Box::new(mem::zeroed())); - let mut bytes_read: u32 = 0; - let ok = { - let remaining_buf = &mut self.read_buf[buf_len..]; - kernel32::ReadFile(self.handle.as_raw(), - remaining_buf.as_mut_ptr() as LPVOID, - remaining_buf.len() as u32, - &mut bytes_read, - self.ov.as_mut().unwrap().deref_mut()) - }; - - // Reset the vector to only expose the already filled part. - // - // This means that the async read - // will actually fill memory beyond the exposed part of the vector. - // While this use of a vector is officially sanctioned for such cases, - // it still feel rather icky to me... - // - // On the other hand, this way we make sure - // the buffer never appears to have more valid data - // than what is actually present, - // which could pose a potential danger in its own right. - // Also, it avoids the need to keep a separate state variable -- - // which would bear some risk of getting out of sync. - self.read_buf.set_len(buf_len); - - let result = if ok == winapi::FALSE { - Err(GetLastError()) - } else { - Ok(()) - }; + unsafe { + // Temporarily extend the vector to span its entire capacity, + // so we can safely sub-slice it for the actual read. + let buf_len = self.read_buf.len(); + let buf_cap = self.read_buf.capacity(); + self.read_buf.set_len(buf_cap); + + // issue the read to the buffer, at the current length offset + self.ov = Some(AliasedCell::new(Box::new(mem::zeroed()))); + self.aliased_buf = Some(AliasedCell::new(mem::replace(&mut self.read_buf, vec![]))); + let mut bytes_read: u32 = 0; + let ok = { + let remaining_buf = &mut self.aliased_buf.as_mut().unwrap().alias_mut()[buf_len..]; + kernel32::ReadFile(self.handle.as_raw(), + remaining_buf.as_mut_ptr() as LPVOID, + remaining_buf.len() as u32, + &mut bytes_read, + self.ov.as_mut().unwrap().alias_mut().deref_mut()) + }; - match result { - // Normally, for an async operation, a call like - // `ReadFile` would return `FALSE`, and the error code - // would be `ERROR_IO_PENDING`. But in some situations, - // `ReadFile` can complete synchronously (returns `TRUE`). - // Even if it does, a notification that the IO completed - // is still sent to the IO completion port that this - // handle is part of, meaning that we don't have to do any - // special handling for sync-completed operations. - Ok(()) | - Err(winapi::ERROR_IO_PENDING) => { + // Reset the vector to only expose the already filled part. + // + // This means that the async read + // will actually fill memory beyond the exposed part of the vector. + // While this use of a vector is officially sanctioned for such cases, + // it still feel rather icky to me... + // + // On the other hand, this way we make sure + // the buffer never appears to have more valid data + // than what is actually present, + // which could pose a potential danger in its own right. + // Also, it avoids the need to keep a separate state variable -- + // which would bear some risk of getting out of sync. + self.aliased_buf.as_mut().unwrap().alias_mut().set_len(buf_len); + + let result = if ok == winapi::FALSE { + Err(GetLastError()) + } else { Ok(()) - }, - Err(winapi::ERROR_BROKEN_PIPE) => { - win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.ov = None; - Err(WinError::ChannelClosed) - }, - Err(err) => { - self.ov = None; - Err(WinError::from_system(err, "ReadFile")) - }, + }; + + match result { + // Normally, for an async operation, a call like + // `ReadFile` would return `FALSE`, and the error code + // would be `ERROR_IO_PENDING`. But in some situations, + // `ReadFile` can complete synchronously (returns `TRUE`). + // Even if it does, a notification that the IO completed + // is still sent to the IO completion port that this + // handle is part of, meaning that we don't have to do any + // special handling for sync-completed operations. + Ok(()) | + Err(winapi::ERROR_IO_PENDING) => { + Ok(()) + }, + Err(winapi::ERROR_BROKEN_PIPE) => { + win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); + self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + Err(WinError::ChannelClosed) + }, + Err(err) => { + self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + Err(WinError::from_system(err, "ReadFile")) + }, + } } } /// Called when we receive an IO Completion Packet for this handle. /// - /// Unsafe, since calling this in error + /// During its course, this method moves `aliased_buf` back into `read_buf`, + /// thus making it accessible from normal code again; + /// so `get_message()` can extract the received messages from the buffer. + /// + /// Invoking this is unsafe, since calling it in error /// while an async read is actually still in progress in the kernel /// would have catastrophic effects, - /// as `ov` and `read_buf` are still mutably aliased by the kernel in that case! + /// as `ov` and `aliased_buf` are still mutably aliased by the kernel in that case! /// /// (See documentation of the `ov` and `read_buf` fields.) /// - /// Also, this method relies on `ov` and `read_buf` actually having valid data, + /// Also, this method relies on `ov` and `aliased_buf` actually having valid data, /// i.e. nothing should modify these fields /// between receiving the completion notification from the kernel /// and invoking this method. @@ -541,8 +556,9 @@ impl MessageReader { // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. - // (And it's safe again to access the `ov` and `read_buf` fields.) - let ov = self.ov.take().unwrap(); + // (And it's safe again to access the `ov` and `aliased_buf` fields.) + let ov = self.ov.take().unwrap().into_inner(); + self.read_buf = self.aliased_buf.take().unwrap().into_inner(); match io_result { Ok(()) => {} @@ -582,13 +598,12 @@ impl MessageReader { /// /// In non-blocking mode, this may return with `WinError:NoData`, /// while the async operation remains in flight. - /// - /// Note: Upon successful return, - /// the internal `ov` and `read_buf` fields - /// won't be aliased by the kernel anymore. - /// When getting `NoData` however, - /// access to these fields remains invalid, - /// i.e. we are still in unsafe mode in that case! + /// The read buffer remains unavailable in that case, + /// since it's still aliased by the kernel. + /// (And there is nothing new to pick up anyway.) + /// It will only become available again + /// when `fetch_async_result()` returns sucessfully upon retry. + /// (Or the async read is aborted with `cancel_io()`.) fn fetch_async_result(&mut self, blocking_mode: BlockingMode) -> Result<(), WinError> { unsafe { // Get the overlapped result, blocking if we need to. @@ -598,7 +613,7 @@ impl MessageReader { BlockingMode::Nonblocking => winapi::FALSE, }; let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), - self.ov.as_mut().unwrap().deref_mut(), + self.ov.as_mut().unwrap().alias_mut().deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { @@ -695,17 +710,13 @@ impl MessageReader { if ret.is_null() { return Err(WinError::last("CreateIoCompletionPort")); } + } - self.entry_id = Some(entry_id); + self.entry_id = Some(entry_id); - // Make sure that the reader has a read in flight, - // otherwise a later select() will hang. - // - // Note: Just like in `OsIpcReceiver.receive_message()` below, - // this makes us vulnerable to invalid `ov` and `read_buf` modification - // from code not marked as unsafe... - self.start_read() - } + // Make sure that the reader has a read in flight, + // otherwise a later select() will hang. + self.start_read() } /// Specialized read for out-of-band data ports. @@ -721,27 +732,25 @@ impl MessageReader { while self.read_buf.len() < size { // Because our handle is asynchronous, we have to do a two-part read -- // first issue the operation, then wait for its completion. - unsafe { - match self.start_read() { - Err(WinError::ChannelClosed) => { - // If the helper channel closes unexpectedly - // (i.e. before supplying the expected amount of data), - // don't report that as a "sender closed" condition on the main channel: - // rather, fail with the actual raw error code. - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); - } - Err(err) => return Err(err), - Ok(()) => {} - }; - match self.fetch_async_result(BlockingMode::Blocking) { - Err(WinError::ChannelClosed) => { - return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) - } - // In blocking mode, `fetch_async_result()` has no other expected failure modes. - Err(_) => unreachable!(), - Ok(()) => {} - }; - } + match self.start_read() { + Err(WinError::ChannelClosed) => { + // If the helper channel closes unexpectedly + // (i.e. before supplying the expected amount of data), + // don't report that as a "sender closed" condition on the main channel: + // rather, fail with the actual raw error code. + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")); + } + Err(err) => return Err(err), + Ok(()) => {} + }; + match self.fetch_async_result(BlockingMode::Blocking) { + Err(WinError::ChannelClosed) => { + return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) + } + // In blocking mode, `fetch_async_result()` has no other expected failure modes. + Err(_) => unreachable!(), + Ok(()) => {} + }; } Ok(mem::replace(&mut self.read_buf, vec![])) @@ -882,29 +891,16 @@ impl OsIpcReceiver { return Ok((data, channels, shmems)); } - unsafe { - // Then, issue a read if we don't have one already in flight. - // We must not issue a read if we have complete unconsumed - // messages, because getting a message modifies the read_buf. - try!(reader.start_read()); - - // May return `WinError::NoData` in non-blocking mode. - // - // The async read remains in flight in that case; - // and another attempt at getting a result - // can be done the next time we are called. - // - // Note: This leaks unsafety outside the `unsafe` block, - // since the method returns while an async read is still in progress; - // meaning the kernel still holds a mutable alias - // of the read buffer and `OVERLAPPED` structure - // that the Rust type system doesn't know about -- - // nothing prevents code that isn't marked as `unsafe` - // from performing invalid reads or writes to these fields! - // - // (See documentation of `ov` and `read_buf` fields.) - try!(reader.fetch_async_result(blocking_mode)); - } + // Then, issue a read if we don't have one already in flight. + try!(reader.start_read()); + + // Attempt to complete the read. + // + // May return `WinError::NoData` in non-blocking mode. + // The async read remains in flight in that case; + // and another attempt at getting a result + // can be done the next time we are called. + try!(reader.fetch_async_result(blocking_mode)); // If we're not blocking, pretend that we are blocking, since we got part of // a message already. Keep reading until we get a complete message. @@ -933,14 +929,12 @@ impl OsIpcReceiver { let handle = &reader_borrow.handle; // Boxing this to get a stable address is not strictly necesssary here, // since we are not moving the local variable around -- but better safe than sorry... - let mut ov = Box::new(mem::zeroed::()); - let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.deref_mut()); + let mut ov = AliasedCell::new(Box::new(mem::zeroed::())); + let ok = kernel32::ConnectNamedPipe(handle.as_raw(), ov.alias_mut().deref_mut()); // we should always get FALSE with async IO assert!(ok == winapi::FALSE); - let err = GetLastError(); - - match err { + let result = match GetLastError() { // did we successfully connect? (it's reported as an error [ok==false]) winapi::ERROR_PIPE_CONNECTED => { win32_trace!("[$ {:?}] accept (PIPE_CONNECTED)", handle.as_raw()); @@ -960,7 +954,7 @@ impl OsIpcReceiver { // the connect is pending; wait for it to complete winapi::ERROR_IO_PENDING => { let mut nbytes: u32 = 0; - let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.deref_mut(), &mut nbytes, winapi::TRUE); + let ok = kernel32::GetOverlappedResult(handle.as_raw(), ov.alias_mut().deref_mut(), &mut nbytes, winapi::TRUE); if ok == winapi::FALSE { return Err(WinError::last("GetOverlappedResult[ConnectNamedPipe]")); } @@ -972,7 +966,10 @@ impl OsIpcReceiver { win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), err); Err(WinError::last("ConnectNamedPipe")) }, - } + }; + + ov.into_inner(); + result } } @@ -1338,7 +1335,7 @@ impl OsIpcReceiverSet { // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. - closed = match unsafe { reader.start_read() } { + closed = match reader.start_read() { Ok(()) => { // We just successfully reinstated it as an active reader -- // so add it back to the list. From 95589faecf77064202c4cfaf1228e5db7d4d0a55 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 15 May 2018 16:53:02 +0200 Subject: [PATCH 099/109] windows: Clarify comment about `start_read()` for receivers in sets --- src/platform/windows/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e00ff61b8..ae5f85150 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -714,8 +714,9 @@ impl MessageReader { self.entry_id = Some(entry_id); - // Make sure that the reader has a read in flight, - // otherwise a later select() will hang. + // The readers in the IOCP need to have async reads in flight, + // so they can actually get completion events -- + // otherwise, a subsequent `select()` call would just hang indefinitely. self.start_read() } From 0ecf1cc80815ad3679ae755d1385f0941102924d Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 14 May 2018 20:31:24 +0200 Subject: [PATCH 100/109] windows: Put `ov` and `buf` in the same `AliasedCell<>` Put both of the fields aliased by the kernel during an async read operation together in a common `AliasedCell<>`. This increases robustness, and further tightens unsafe boundaries, by making sure the two fields stay consistent with each other. When they are wrapped in `AliasedCell<>` separately, safe code is prevented from giving any of them invalid values individually -- but they could still get out of sync with each other, if some code moves just one of them and not the other. Consistency between these fields however is crucial for correctness as well as soundness. --- src/platform/windows/mod.rs | 128 +++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index ae5f85150..80639f763 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -333,6 +333,25 @@ impl WinHandle { } } +/// Helper struct for all data being aliased by the kernel during async reads. +#[derive(Debug)] +struct AsyncData { + /// Meta-data for this async read operation, filled by the kernel. + /// + /// This must be on the heap, in order for its memory location -- + /// which is registered in the kernel during an async read -- + /// to remain stable even when the enclosing structure is passed around. + ov: Box, + + /// Buffer for the kernel to store the results of the async read operation. + /// + /// The vector provided here needs to have some allocated yet unused space, + /// i.e. `capacity()` needs to be larger than `len()`. + /// If part of the vector is already filled, that is left in place; + /// the new data will only be written to the unused space. + buf: Vec, +} + /// Main object keeping track of a receive handle and its associated state. /// /// Implements blocking/nonblocking reads of messages from the handle. @@ -341,52 +360,39 @@ struct MessageReader { /// The pipe read handle. handle: WinHandle, - /// The OVERLAPPED struct for async IO on this receiver. - /// - /// We'll only ever have one in flight. + /// Buffer for outstanding data, that has been received but not yet processed. /// - /// This must be on the heap, in order for its memory location -- - /// which is registered in the kernel during an async read -- - /// to remain stable even when the enclosing structure is passed around. + /// Note: this is only set while no async read operation + /// is currently in progress with the kernel. + /// When an async read is in progress, + /// the receive buffer is aliased by the kernel; + /// so we need to temporarily move it into an `AliasedCell`, + /// thus making it inaccessible from safe code -- + /// see `async` below. + /// We only move it back once the kernel signals completion of the async read. + read_buf: Vec, + + /// Data used by the kernel during an async read operation. /// /// Note: Since this field only has a value /// when an async read operation is in progress /// (i.e. has been issued to the system, and not completed yet), /// this also serves as an indicator of the latter. /// - /// WARNING: As the kernel holds a mutable alias of this structure + /// WARNING: As the kernel holds mutable aliases of this data /// while an async read is in progress, - /// it is crucial that this value is never accessed in user space + /// it is crucial that it is never accessed in user space /// from the moment we issue an async read in `start_read()`, /// until the moment we process the event /// signalling completion of the async read in `notify_completion()`. /// - /// Since Rust's type system is not aware of the kernel alias, + /// Since Rust's type system is not aware of the kernel aliases, /// the compiler cannot guarantee exclusive access the way it normally would, /// i.e. any access to this value is inherently unsafe! /// We thus wrap it in an `AliasedCell`, - /// making sure the value is only accessible from code marked `unsafe`; + /// making sure the data is only accessible from code marked `unsafe`; /// and only move it out when the kernel signals that the async read is done. - ov: Option>>, - - /// Buffer for outstanding data, that has been received but not yet processed. - /// - /// Note: this is only set while no async read operation - /// is currently in progress with the kernel. - /// When an async read is in progress, - /// the receive buffer is aliased by the kernel just like `ov` above; - /// so we need to temporarily move it into an `AliasedCell` too, - /// thus making it inaccessible from safe code -- - /// see `aliased_buf` below. - /// We only move it back once the kernel signals completion of the async read. - read_buf: Vec, - - /// Buffer for the kernel to store the results of async read operations. - /// - /// WARNING: This has the same aliasing problem as `ov` above -- - /// i.e. it should never be accessed from user space - /// while an async read operation is in progress. - aliased_buf: Option>>, + async: Option>, /// Token identifying the reader/receiver within an `OsIpcReceiverSet`. /// @@ -401,7 +407,7 @@ struct MessageReader { // // Note: the `Send` claim is only really fulfilled // as long as nothing can ever alias the aforementioned raw pointer. -// As explained in the documentation of the `ov` field, +// As explained in the documentation of the `async` field, // this is a tricky condition (because of kernel aliasing), // which we however need to uphold regardless of the `Send` property -- // so claiming `Send` should not introduce any additional issues. @@ -419,9 +425,8 @@ impl MessageReader { fn new(handle: WinHandle) -> MessageReader { MessageReader { handle: handle, - ov: None, read_buf: Vec::new(), - aliased_buf: None, + async: None, entry_id: None, } } @@ -436,11 +441,11 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { - if self.ov.is_some() { + if self.async.is_some() { kernel32::CancelIoEx(self.handle.as_raw(), - self.ov.as_mut().unwrap().alias_mut().deref_mut()); - self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let async_data = self.async.take().unwrap().into_inner(); + self.read_buf = async_data.buf; } } } @@ -449,13 +454,13 @@ impl MessageReader { /// /// When an async read is started successfully, /// the receive buffer is moved out of `read_buf` - /// into the `AliasedCell<>` in `aliased_buf`, + /// into the `AliasedCell<>` in `async`, /// thus making it inaccessible from safe code; /// it will only be moved back in `notify_completion()`. - /// (See documentation of the `ov` and `read_buf` fields.) + /// (See documentation of the `read_buf` and `async` fields.) fn start_read(&mut self) -> Result<(),WinError> { // Nothing needs to be done if an async read operation is already in progress. - if self.ov.is_some() { + if self.async.is_some() { return Ok(()); } @@ -473,16 +478,19 @@ impl MessageReader { self.read_buf.set_len(buf_cap); // issue the read to the buffer, at the current length offset - self.ov = Some(AliasedCell::new(Box::new(mem::zeroed()))); - self.aliased_buf = Some(AliasedCell::new(mem::replace(&mut self.read_buf, vec![]))); + self.async = Some(AliasedCell::new(AsyncData { + ov: Box::new(mem::zeroed()), + buf: mem::replace(&mut self.read_buf, vec![]), + })); let mut bytes_read: u32 = 0; let ok = { - let remaining_buf = &mut self.aliased_buf.as_mut().unwrap().alias_mut()[buf_len..]; + let async_data = self.async.as_mut().unwrap().alias_mut(); + let remaining_buf = &mut async_data.buf[buf_len..]; kernel32::ReadFile(self.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, - self.ov.as_mut().unwrap().alias_mut().deref_mut()) + async_data.ov.deref_mut()) }; // Reset the vector to only expose the already filled part. @@ -498,7 +506,7 @@ impl MessageReader { // which could pose a potential danger in its own right. // Also, it avoids the need to keep a separate state variable -- // which would bear some risk of getting out of sync. - self.aliased_buf.as_mut().unwrap().alias_mut().set_len(buf_len); + self.async.as_mut().unwrap().alias_mut().buf.set_len(buf_len); let result = if ok == winapi::FALSE { Err(GetLastError()) @@ -521,13 +529,11 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + self.read_buf = self.async.take().unwrap().into_inner().buf; Err(WinError::ChannelClosed) }, Err(err) => { - self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + self.read_buf = self.async.take().unwrap().into_inner().buf; Err(WinError::from_system(err, "ReadFile")) }, } @@ -536,19 +542,18 @@ impl MessageReader { /// Called when we receive an IO Completion Packet for this handle. /// - /// During its course, this method moves `aliased_buf` back into `read_buf`, + /// During its course, this method moves `async.buf` back into `read_buf`, /// thus making it accessible from normal code again; /// so `get_message()` can extract the received messages from the buffer. /// /// Invoking this is unsafe, since calling it in error /// while an async read is actually still in progress in the kernel /// would have catastrophic effects, - /// as `ov` and `aliased_buf` are still mutably aliased by the kernel in that case! - /// - /// (See documentation of the `ov` and `read_buf` fields.) + /// as the `async` data is still mutably aliased by the kernel in that case! + /// (See documentation of the `async` field.) /// - /// Also, this method relies on `ov` and `aliased_buf` actually having valid data, - /// i.e. nothing should modify these fields + /// Also, this method relies on `async` actually having valid data, + /// i.e. nothing should modify its constituent fields /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { @@ -556,9 +561,10 @@ impl MessageReader { // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. - // (And it's safe again to access the `ov` and `aliased_buf` fields.) - let ov = self.ov.take().unwrap().into_inner(); - self.read_buf = self.aliased_buf.take().unwrap().into_inner(); + // (And it's safe again to access the `async` data.) + let async_data = self.async.take().unwrap().into_inner(); + let ov = async_data.ov; + self.read_buf = async_data.buf; match io_result { Ok(()) => {} @@ -613,7 +619,7 @@ impl MessageReader { BlockingMode::Nonblocking => winapi::FALSE, }; let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), - self.ov.as_mut().unwrap().alias_mut().deref_mut(), + self.async.as_mut().unwrap().alias_mut().ov.deref_mut(), &mut nbytes, block); let io_result = if ok == winapi::FALSE { @@ -639,7 +645,7 @@ impl MessageReader { fn get_message(&mut self) -> Result, Vec, Vec)>, WinError> { // Never touch the buffer while it's still mutably aliased by the kernel! - if self.ov.is_some() { + if self.async.is_some() { return Ok(None); } @@ -871,7 +877,7 @@ impl OsIpcReceiver { pub fn consume(&self) -> OsIpcReceiver { let mut reader = self.reader.borrow_mut(); - assert!(reader.ov.is_none()); + assert!(reader.async.is_none()); OsIpcReceiver::from_handle(reader.handle.take()) } From 33b15fc11d5c91be02213c6d80fa19f82cf7b8cf Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 15 May 2018 18:44:47 +0200 Subject: [PATCH 101/109] windows: Don't ignore `CancelIoEx()` errors --- src/platform/windows/mod.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 80639f763..3681ba621 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -442,8 +442,23 @@ impl MessageReader { fn cancel_io(&mut self) { unsafe { if self.async.is_some() { - kernel32::CancelIoEx(self.handle.as_raw(), - self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let status = kernel32::CancelIoEx(self.handle.as_raw(), + self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + + // A cancel operation is not expected to fail. + // If it does, callers are not prepared for that -- so we have to bail. + // + // Note that we should never ignore a failed cancel, + // since that would affect further operations; + // and the caller definitely must not free the aliased data in that case! + // + // Sometimes `CancelIoEx()` fails with `ERROR_NOT_FOUND` though, + // meaning there is actually no async operation outstanding at this point, + // i.e. we can safely free the async data without further action. + // (Specifically, this is triggered by the `receiver_set_big_data()` test.) + // Not sure why that happens -- but I *think* it should be benign... + assert!(status != winapi::FALSE || GetLastError() == winapi::ERROR_NOT_FOUND); + let async_data = self.async.take().unwrap().into_inner(); self.read_buf = async_data.buf; } From 62f30b135e66a04ee6a0a38de5890360531a9633 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Tue, 15 May 2018 20:36:45 +0200 Subject: [PATCH 102/109] windows: Don't panic on unknown errors in `notify_completion()` While this might have been a problem in the past, the current code properly passes errors from `notify_completion()` up through all layers; so there is nothing really preventing us from orderly returning any kind of error reported by the `GetOverlappedResult()` or `GetQueuedCompletionStatus()` system calls. (The only problem would be if `GetOverlappedResult()` has failure modes that actually leave the async operation in progress, and thus the async data in use: in that case, unpacking the `AliasedCell<>` in async and returning to the caller would be wrong... But if that's the case, the previous behaviour of panicking after unpacking the `AliasedCell<>` was just as wrong.) Since returning arbitrary errors requires us to invoke `WinError::from_system()` (either directly, or indirectly through `WinError::last()`), and this one wants to know the origin of the error (so it can put it in debug traces), we need to do these conversions near the call sites, and pass an already converted error to `notify_completion()`. (Which seems cleaner anyway.) This has the side effect of also logging "broken pipe" (sender closed) errors in the debug trace, which might be slightly redundant with any other tracing done by the "closed" handling... I don't think that's really a problem, though. --- src/platform/windows/mod.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 3681ba621..5c153fe1d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -571,7 +571,7 @@ impl MessageReader { /// i.e. nothing should modify its constituent fields /// between receiving the completion notification from the kernel /// and invoking this method. - unsafe fn notify_completion(&mut self, io_result: Result<(), u32>) -> Result<(), WinError> { + unsafe fn notify_completion(&mut self, io_result: Result<(), WinError>) -> Result<(), WinError> { win32_trace!("[$ {:?}] notify_completion", self.handle); // Regardless whether the kernel reported success or error, @@ -583,16 +583,11 @@ impl MessageReader { match io_result { Ok(()) => {} - Err(winapi::ERROR_BROKEN_PIPE) => { + Err(WinError::WindowsResult(winapi::ERROR_BROKEN_PIPE)) => { // Remote end closed the channel. return Err(WinError::ChannelClosed); } - Err(err) => { - // Other errors shouldn't come up here... - // If they do, we don't really understand the situation -- - // so we can't handle this gracefully. - panic!("[$ {:?}] *** notify_completion: unhandled error reported! {}", self.handle, err); - } + Err(err) => return Err(err), } let nbytes = ov.InternalHigh as u32; @@ -646,7 +641,7 @@ impl MessageReader { } // We pass err through to notify_completion so // that it can handle other errors. - Err(err) + Err(WinError::from_system(err, "GetOverlappedResult")) } else { Ok(()) }; @@ -769,8 +764,7 @@ impl MessageReader { Err(WinError::ChannelClosed) => { return Err(WinError::from_system(winapi::ERROR_BROKEN_PIPE, "ReadFile")) } - // In blocking mode, `fetch_async_result()` has no other expected failure modes. - Err(_) => unreachable!(), + Err(err) => return Err(err), Ok(()) => {} }; } @@ -1309,15 +1303,17 @@ impl OsIpcReceiverSet { winapi::INFINITE); win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); let io_result = if ok == winapi::FALSE { + let err = WinError::last("GetQueuedCompletionStatus"); + // If the OVERLAPPED result is NULL, then the // function call itself failed or timed out. // Otherwise, the async IO operation failed, and // we want to hand the error to notify_completion below. if ov_ptr.is_null() { - return Err(WinError::last("GetQueuedCompletionStatus")); + return Err(err); } - Err(GetLastError()) + Err(err) } else { Ok(()) }; From ebdfe2bc7069252d49ec42f5cb9eb77824859fd6 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 19 May 2018 21:41:25 +0200 Subject: [PATCH 103/109] windows: refactor: Split out `OsIpcReceiverSet.fetch_iocp_result()` Split out the system call and associated handling for getting completion notifications on an IOCP (set) from the rest of the `select()` method, similar to how `fetch_async_result()` encapsulates the event handling for regular readers. This will be necessary for proper `Drop` handling for `OsIpcReceiverSet`. Incidentally, this exactly covers tha unsafe code section of the `select()` implementation; and as such, it's a good step towards better layering of the IOCP handling in general. I guess it could be argued that a method call is also more readable than assigning from a large anonymous block... --- src/platform/windows/mod.rs | 115 ++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 5c153fe1d..a64171e4f 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -1275,6 +1275,75 @@ impl OsIpcReceiverSet { Ok(entry_id) } + /// Conclude an async read operation on any of the receivers in the set. + /// + /// This fetches a completion event from the set's IOCP; + /// finds the matching `MessageReader`; + /// removes it from the list of active readers + /// (since no operation is in flight on this reader at this point); + /// and notifies the reader of the completion event. + /// + /// If the IOCP call is successful, this returns the respective reader, + /// along with an inner status describing the type of event received. + /// This can be a success status, indicating data has been received, + /// and is ready to be picked up with `get_message()` on the reader; + /// an error status indicating that the sender connected to this receiver + /// has closed the connection; + /// or some other I/O error status. + /// + /// Unless a "closed" status is returned, + /// the respective reader remains a member of the set, + /// and the caller should add it back to the list of active readers + /// after kicking off a new read operation on it. + fn fetch_iocp_result(&mut self) -> Result<(MessageReader, Result<(), WinError>), WinError> { + unsafe { + let mut nbytes: u32 = 0; + let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; + let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); + // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! + let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), + &mut nbytes, + &mut completion_key, + &mut ov_ptr, + winapi::INFINITE); + win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); + let io_result = if ok == winapi::FALSE { + let err = WinError::last("GetQueuedCompletionStatus"); + + // If the OVERLAPPED result is NULL, then the + // function call itself failed or timed out. + // Otherwise, the async IO operation failed, and + // we want to hand the error to notify_completion below. + if ov_ptr.is_null() { + return Err(err); + } + + Err(err) + } else { + Ok(()) + }; + + assert!(!ov_ptr.is_null()); + assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); + + // Find the matching receiver + let (reader_index, _) = self.readers.iter().enumerate() + .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) + .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); + + // Remove the entry from the set for now -- we will re-add it later, + // if we can successfully initiate another async read operation. + let mut reader = self.readers.swap_remove(reader_index); + + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + + // tell it about the completed IO op + let result = reader.notify_completion(io_result); + + Ok((reader, result)) + } + } + pub fn select(&mut self) -> Result,WinError> { assert!(self.readers.len() + self.closed_readers.len() > 0, "selecting with no objects?"); win32_trace!("[# {:?}] select() with {} active and {} closed receivers", self.iocp.as_raw(), self.readers.len(), self.closed_readers.len()); @@ -1291,51 +1360,7 @@ impl OsIpcReceiverSet { // Do this in a loop, because we may need to dequeue multiple packets to // read a complete message. while selection_results.is_empty() { - let (mut reader, result) = unsafe { - let mut nbytes: u32 = 0; - let mut completion_key = INVALID_HANDLE_VALUE as winapi::ULONG_PTR; - let mut ov_ptr: *mut winapi::OVERLAPPED = ptr::null_mut(); - // XXX use GetQueuedCompletionStatusEx to dequeue multiple CP at once! - let ok = kernel32::GetQueuedCompletionStatus(self.iocp.as_raw(), - &mut nbytes, - &mut completion_key, - &mut ov_ptr, - winapi::INFINITE); - win32_trace!("[# {:?}] GetQueuedCS -> ok:{} nbytes:{} key:{:?}", self.iocp.as_raw(), ok, nbytes, completion_key); - let io_result = if ok == winapi::FALSE { - let err = WinError::last("GetQueuedCompletionStatus"); - - // If the OVERLAPPED result is NULL, then the - // function call itself failed or timed out. - // Otherwise, the async IO operation failed, and - // we want to hand the error to notify_completion below. - if ov_ptr.is_null() { - return Err(err); - } - - Err(err) - } else { - Ok(()) - }; - - assert!(!ov_ptr.is_null()); - assert!(completion_key != INVALID_HANDLE_VALUE as winapi::ULONG_PTR); - - // Find the matching receiver - let (reader_index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) - .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); - - // Remove the entry from the set for now -- we will re-add it later, - // if we can successfully initiate another async read operation. - let mut reader = self.readers.swap_remove(reader_index); - - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); - - // tell it about the completed IO op - let result = reader.notify_completion(io_result); - (reader, result) - }; + let (mut reader, result) = try!(self.fetch_iocp_result()); let mut closed = match result { Ok(()) => false, From f646ea2cc2c020c730431342728b5358a6e243b9 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Sat, 19 May 2018 22:11:46 +0200 Subject: [PATCH 104/109] windows: Make `cancel_io()` sound According to the documentation of `CancelIoEx()`, successful completion of the `CancelIoEx()` call only indicates that the cancel request has been successfully *queued* -- but it does *not* mean we can safely free the aliased buffers yet! Rather, we have to wait for a notification signalling the completion of the async operation itself. We thus split out the actual `CancelIoEx()` call into a new `issue_async_cancel()` method, and turn `cancel_io()` into a wrapper that waits for the actualy async read to conclude (using `fetch_async_result()`) after issuing the cancel request. Since that doesn't work on readers in a receiver set, we need to add an explicit `Drop` implementation for `OsIpcReceiverSet`, which issues cancel requests for all outstanding read operations, and then uses `fetch_iocp_result()` to wait for all of them to conclude. --- src/platform/windows/mod.rs | 77 ++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index a64171e4f..4e37e107d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -439,12 +439,27 @@ impl MessageReader { mem::replace(self, MessageReader::new(WinHandle::invalid())) } - fn cancel_io(&mut self) { + /// Request the kernel to cancel a pending async I/O operation on this reader. + /// + /// Note that this only schedules the cancel request; + /// but doesn't guarantee that the operation is done + /// (and the buffers are no longer used by the kernel) + /// before this method returns. + /// + /// A caller that wants to ensure the operation is really done, + /// will need to wait using `fetch_async_result()`. + /// (Or `fetch_iocp_result()` for readers in a set.) + /// + /// The only exception is if the kernel indicates + /// that no operation was actually outstanding at this point. + /// In that case, the `async` data is released immediately; + /// and the caller should not attempt waiting for completion. + fn issue_async_cancel(&mut self) { unsafe { - if self.async.is_some() { - let status = kernel32::CancelIoEx(self.handle.as_raw(), - self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + let status = kernel32::CancelIoEx(self.handle.as_raw(), + self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); + if status == winapi::FALSE { // A cancel operation is not expected to fail. // If it does, callers are not prepared for that -- so we have to bail. // @@ -453,14 +468,38 @@ impl MessageReader { // and the caller definitely must not free the aliased data in that case! // // Sometimes `CancelIoEx()` fails with `ERROR_NOT_FOUND` though, - // meaning there is actually no async operation outstanding at this point, - // i.e. we can safely free the async data without further action. + // meaning there is actually no async operation outstanding at this point. // (Specifically, this is triggered by the `receiver_set_big_data()` test.) // Not sure why that happens -- but I *think* it should be benign... - assert!(status != winapi::FALSE || GetLastError() == winapi::ERROR_NOT_FOUND); + // + // In that case, we can safely free the async data right now; + // and the caller should not attempt to wait for completion. + assert!(GetLastError() == winapi::ERROR_NOT_FOUND); + + self.read_buf = self.async.take().unwrap().into_inner().buf; + } + } + } + + fn cancel_io(&mut self) { + if self.async.is_some() { + // This doesn't work for readers in a receiver set. + // (`fetch_async_result()` would hang indefinitely.) + // Receiver sets have to handle cancellation specially, + // and make sure they always do that *before* dropping readers. + assert!(self.entry_id.is_none()); + + self.issue_async_cancel(); - let async_data = self.async.take().unwrap().into_inner(); - self.read_buf = async_data.buf; + // If there is an operation still in flight, wait for it to complete. + // + // This will usually fail with `ERROR_OPERATION_ABORTED`; + // but it could also return success, or some other error, + // if the operation actually completed in the mean time. + // We don't really care either way -- + // we just want to be certain there is no operation in flight any more. + if self.async.is_some() { + let _ = self.fetch_async_result(BlockingMode::Blocking); } } } @@ -1232,6 +1271,26 @@ pub struct OsIpcReceiverSet { closed_readers: Vec, } +impl Drop for OsIpcReceiverSet { + fn drop(&mut self) { + // We need to cancel any in-flight read operations before we drop the receivers, + // since otherwise the receivers' `Drop` implementation would try to cancel them -- + // but the implementation there doesn't work for receivers in a set... + for reader in &mut self.readers { + reader.issue_async_cancel(); + } + + // Wait for any reads still in flight to complete, + // thus freeing the associated async data. + self.readers.retain(|r| r.async.is_some()); + while !self.readers.is_empty() { + // We unwrap the outer result (can't deal with the IOCP call failing here), + // but don't care about the actual results of the completed read operations. + let _ = self.fetch_iocp_result().unwrap(); + } + } +} + impl OsIpcReceiverSet { pub fn new() -> Result { unsafe { From 1e0751027f776813dac5566bd238d612a281e83e Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Wed, 30 May 2018 23:29:36 +0200 Subject: [PATCH 105/109] [RemoveMe] Temporarily restore all CI targets using `unix` back-end Increase chance of catching intermittent failures while working on other stuff... --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index bc367972f..d9c66694a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ env: - RUST_BACKTRACE=1 matrix: - FEATURES="unstable" + - FEATURES="unstable memfd" notifications: webhooks: http://build.servo.org:54856/travis From a7d36e13a12d3a79083de0bc1bdabbc70a956a3b Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Thu, 7 Jun 2018 20:38:55 +0200 Subject: [PATCH 106/109] WIP: threaded fragment tests --- src/platform/test.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/test.rs b/src/platform/test.rs index a321e44bc..d3d0ef642 100644 --- a/src/platform/test.rs +++ b/src/platform/test.rs @@ -177,9 +177,15 @@ fn with_n_fds(n: usize, size: usize) { let (super_tx, super_rx) = platform::channel().unwrap(); let data: Vec = (0..size).map(|i| (i % 251) as u8).collect(); - super_tx.send(&data[..], sender_fds, vec![]).unwrap(); + let thread = { + let data = data.clone(); + thread::spawn(move || { + super_tx.send(&data[..], sender_fds, vec![]).unwrap(); + }) + }; let (received_data, received_channels, received_shared_memory_regions) = super_rx.recv().unwrap(); + thread.join().unwrap(); assert_eq!(received_data.len(), data.len()); assert_eq!(&received_data[..], &data[..]); From 44f262cd638aacd922699b0c96acc7ccaab03924 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 8 Jun 2018 18:24:13 +0200 Subject: [PATCH 107/109] windows: Properly hide all `win32-trace` code behind conditionals Make sure all code related to the `win32-trace` feature is compiled only when the feature is enabled. This avoids unnecessary bloat; as well as potential compile errors when other code conditional on this feature is added. --- src/platform/windows/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 4e37e107d..278791029 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -14,6 +14,7 @@ use libc::intptr_t; use std::cell::{Cell, RefCell}; use std::cmp::PartialEq; use std::default::Default; +#[cfg(feature = "win32-trace")] use std::env; use std::ffi::CString; use std::io::{Error, ErrorKind}; @@ -33,14 +34,17 @@ use self::aliased_cell::AliasedCell; lazy_static! { static ref CURRENT_PROCESS_ID: winapi::ULONG = unsafe { kernel32::GetCurrentProcessId() }; static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { kernel32::GetCurrentProcess() }); +} +#[cfg(feature = "win32-trace")] +lazy_static! { static ref DEBUG_TRACE_ENABLED: bool = { env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some() }; } /// Debug macro to better track what's going on in case of errors. macro_rules! win32_trace { ($($rest:tt)*) => { - if cfg!(feature = "win32-trace") { + #[cfg(feature = "win32-trace")] { if *DEBUG_TRACE_ENABLED { println!($($rest)*); } } } @@ -1017,8 +1021,8 @@ impl OsIpcReceiver { }, // Anything else signifies some actual I/O error. - err => { - win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), err); + _err => { + win32_trace!("[$ {:?}] accept error -> {}", handle.as_raw(), _err); Err(WinError::last("ConnectNamedPipe")) }, }; @@ -1700,8 +1704,8 @@ impl WinError { } } - fn from_system(err: u32, f: &str) -> WinError { - win32_trace!("WinError: {} ({}) from {}", WinError::error_string(err), err, f); + fn from_system(err: u32, _f: &str) -> WinError { + win32_trace!("WinError: {} ({}) from {}", WinError::error_string(err), err, _f); WinError::WindowsResult(err) } From 9a672451d7d8e8aadf7b314a02f0d34209e4068a Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Fri, 8 Jun 2018 18:31:34 +0200 Subject: [PATCH 108/109] windows: Introduce `MessageReader.get_raw_handle()` debug helper Add a (conditional) helper method for obtaining the raw handle of the reader -- which is often needed for the `win32_trace` invocations -- to abstract the internal structure of this type, thus facilitating further refactoring. An alternate approach would be overriding the `Debug` trait on `MessageReader`, to just print the raw handle value. That would provide better encapsulation; however, it would also preclude the possibility of easily printing all the constituents of the structure during debugging... (Or we could leave `Debug` alone, and instead implement it as `Display` -- but that feels like an abuse of the `Display` facility... Not sure what to think about that.) --- src/platform/windows/mod.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 278791029..cdb27643d 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -814,6 +814,14 @@ impl MessageReader { Ok(mem::replace(&mut self.read_buf, vec![])) } + + /// Get raw handle of the receive port. + /// + /// This is only for debug tracing purposes, and must not be used for anything else. + #[cfg(feature = "win32-trace")] + fn get_raw_handle(&self) -> HANDLE { + self.handle.as_raw() + } } #[derive(Clone, Copy, Debug)] @@ -1323,13 +1331,13 @@ impl OsIpcReceiverSet { match reader.add_to_iocp(&self.iocp, entry_id) { Ok(()) => { - win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); + win32_trace!("[# {:?}] ReceiverSet add {:?}, id {}", self.iocp.as_raw(), reader.get_raw_handle(), entry_id); self.readers.push(reader); } Err(WinError::ChannelClosed) => { // If the sender has already been closed, we need to stash this information, // so we can report the corresponding event in the next `select()` call. - win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.handle.as_raw(), entry_id); + win32_trace!("[# {:?}] ReceiverSet add {:?} (closed), id {}", self.iocp.as_raw(), reader.get_raw_handle(), entry_id); self.closed_readers.push(entry_id); } Err(err) => return Err(err), @@ -1398,7 +1406,7 @@ impl OsIpcReceiverSet { // if we can successfully initiate another async read operation. let mut reader = self.readers.swap_remove(reader_index); - win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.handle.as_raw()); + win32_trace!("[# {:?}] result for receiver {:?}", self.iocp.as_raw(), reader.get_raw_handle()); // tell it about the completed IO op let result = reader.notify_completion(io_result); @@ -1434,10 +1442,10 @@ impl OsIpcReceiverSet { if !closed { // Drain as many messages as we can. while let Some((data, channels, shmems)) = try!(reader.get_message()) { - win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) got a message", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); selection_results.push(OsIpcSelectionResult::DataReceived(reader.entry_id.unwrap(), data, channels, shmems)); } - win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- no message", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); // Now that we are done frobbing the buffer, // we can safely initiate the next async read operation. @@ -1461,7 +1469,7 @@ impl OsIpcReceiverSet { // or while trying to re-initiate an async read after receiving data -- // add an event to this effect to the result list. if closed { - win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.handle.as_raw(), reader.entry_id.unwrap()); + win32_trace!("[# {:?}] receiver {:?} ({}) -- now closed!", self.iocp.as_raw(), reader.get_raw_handle(), reader.entry_id.unwrap()); selection_results.push(OsIpcSelectionResult::ChannelClosed(reader.entry_id.unwrap())); } } From fac85e5eaed38810e984030e948f4944a7ae77b0 Mon Sep 17 00:00:00 2001 From: Olaf Buddenhagen Date: Mon, 21 May 2018 10:56:19 +0200 Subject: [PATCH 109/109] windows: Move `handle` into `AsyncData` as well For the duration of an async read operation, move the pipe handle into the `AliasedCell` along with the other fields used for the async operation. This prevents anything else from messing with the pipe while the async read is in progress; and makes sure the handle and the other fields can never get mismatched. While I'm not sure whether there is any scenario in which such a mismatch could result in undefined behaviour, it's good for general robustness in any case. --- src/platform/windows/aliased_cell.rs | 13 ++++++++ src/platform/windows/mod.rs | 46 ++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/platform/windows/aliased_cell.rs b/src/platform/windows/aliased_cell.rs index 6399c40fc..6488882aa 100644 --- a/src/platform/windows/aliased_cell.rs +++ b/src/platform/windows/aliased_cell.rs @@ -105,6 +105,19 @@ impl AliasedCell { &mut self.value } + /// Get a shared (immutable) pointer to the inner value. + /// + /// With this method it's possible to get an alias + /// while only holding a shared reference to the `AliasedCell`. + /// + /// Since all the unsafe aliases are untracked, + /// it's up to the callers to make sure no shared aliases are used + /// while the data might actually be mutated elsewhere + /// through some outstanding mutable aliases. + pub unsafe fn alias(&self) -> &T { + &self.inner + } + /// Move out the wrapped value, making it accessible from safe code again. pub unsafe fn into_inner(self) -> T { mem::forget(self.drop_bomb); diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index cdb27643d..f8585c795 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -340,6 +340,9 @@ impl WinHandle { /// Helper struct for all data being aliased by the kernel during async reads. #[derive(Debug)] struct AsyncData { + /// File handle of the pipe on which the async operation is performed. + handle: WinHandle, + /// Meta-data for this async read operation, filled by the kernel. /// /// This must be on the heap, in order for its memory location -- @@ -362,12 +365,21 @@ struct AsyncData { #[derive(Debug)] struct MessageReader { /// The pipe read handle. + /// + /// Note: this is only set while no async read operation + /// is currently in progress with the kernel. + /// When an async read is in progress, + /// it is moved into the `async` sub-structure (see below) + /// along with the other fields used for the async operation, + /// to make sure they all stay in sync, + /// and nothing else can meddle with the the pipe + /// until the operation is completed. handle: WinHandle, /// Buffer for outstanding data, that has been received but not yet processed. /// - /// Note: this is only set while no async read operation - /// is currently in progress with the kernel. + /// Note: just like `handle` above, + /// this is only set while no async read is in progress. /// When an async read is in progress, /// the receive buffer is aliased by the kernel; /// so we need to temporarily move it into an `AliasedCell`, @@ -460,7 +472,7 @@ impl MessageReader { /// and the caller should not attempt waiting for completion. fn issue_async_cancel(&mut self) { unsafe { - let status = kernel32::CancelIoEx(self.handle.as_raw(), + let status = kernel32::CancelIoEx(self.async.as_ref().unwrap().alias().handle.as_raw(), self.async.as_mut().unwrap().alias_mut().ov.deref_mut()); if status == winapi::FALSE { @@ -480,7 +492,9 @@ impl MessageReader { // and the caller should not attempt to wait for completion. assert!(GetLastError() == winapi::ERROR_NOT_FOUND); - self.read_buf = self.async.take().unwrap().into_inner().buf; + let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; + self.read_buf = async_data.buf; } } } @@ -537,6 +551,7 @@ impl MessageReader { // issue the read to the buffer, at the current length offset self.async = Some(AliasedCell::new(AsyncData { + handle: self.handle.take(), ov: Box::new(mem::zeroed()), buf: mem::replace(&mut self.read_buf, vec![]), })); @@ -544,7 +559,7 @@ impl MessageReader { let ok = { let async_data = self.async.as_mut().unwrap().alias_mut(); let remaining_buf = &mut async_data.buf[buf_len..]; - kernel32::ReadFile(self.handle.as_raw(), + kernel32::ReadFile(async_data.handle.as_raw(), remaining_buf.as_mut_ptr() as LPVOID, remaining_buf.len() as u32, &mut bytes_read, @@ -587,11 +602,18 @@ impl MessageReader { }, Err(winapi::ERROR_BROKEN_PIPE) => { win32_trace!("[$ {:?}] BROKEN_PIPE straight from ReadFile", self.handle); - self.read_buf = self.async.take().unwrap().into_inner().buf; + + let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; + self.read_buf = async_data.buf; + Err(WinError::ChannelClosed) }, Err(err) => { - self.read_buf = self.async.take().unwrap().into_inner().buf; + let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; + self.read_buf = async_data.buf; + Err(WinError::from_system(err, "ReadFile")) }, } @@ -615,12 +637,13 @@ impl MessageReader { /// between receiving the completion notification from the kernel /// and invoking this method. unsafe fn notify_completion(&mut self, io_result: Result<(), WinError>) -> Result<(), WinError> { - win32_trace!("[$ {:?}] notify_completion", self.handle); + win32_trace!("[$ {:?}] notify_completion", self.async.as_ref().unwrap().alias().handle); // Regardless whether the kernel reported success or error, // it doesn't have an async read operation in flight at this point anymore. // (And it's safe again to access the `async` data.) let async_data = self.async.take().unwrap().into_inner(); + self.handle = async_data.handle; let ov = async_data.ov; self.read_buf = async_data.buf; @@ -671,7 +694,7 @@ impl MessageReader { BlockingMode::Blocking => winapi::TRUE, BlockingMode::Nonblocking => winapi::FALSE, }; - let ok = kernel32::GetOverlappedResult(self.handle.as_raw(), + let ok = kernel32::GetOverlappedResult(self.async.as_ref().unwrap().alias().handle.as_raw(), self.async.as_mut().unwrap().alias_mut().ov.deref_mut(), &mut nbytes, block); @@ -1399,7 +1422,10 @@ impl OsIpcReceiverSet { // Find the matching receiver let (reader_index, _) = self.readers.iter().enumerate() - .find(|&(_, ref reader)| reader.handle.as_raw() as winapi::ULONG_PTR == completion_key) + .find(|&(_, ref reader)| { + let raw_handle = reader.async.as_ref().unwrap().alias().handle.as_raw(); + raw_handle as winapi::ULONG_PTR == completion_key + }) .expect("Windows IPC ReceiverSet got notification for a receiver it doesn't know about"); // Remove the entry from the set for now -- we will re-add it later,