Skip to content

Commit 51ac27f

Browse files
authored
Auto merge of #272 - tdgne:windows-rebased-20210227, r=jdm
Windows (rebased and updated) This is a rebase of #233 with some updates, namely * fixed unresolved dependencies * impls of error conversions * remaining work explained in #233 (comment) Existing tests passed on my Windows 10 PC with toolchain `nightly-x86_64-pc-windows-msvc` running `cargo test --features="windows-shared-memory-equality,unstable"`, `cargo test --features="windows-shared-memory-equality"` and `cargo test --features="force-inprocess,windows-shared-memory-equality"` so far. Happened to need this, hope it helps...
2 parents f4a0890 + 2c88763 commit 51ac27f

File tree

10 files changed

+2483
-28
lines changed

10 files changed

+2483
-28
lines changed

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ipc-channel"
3-
version = "0.14.1"
3+
version = "0.15.0"
44
description = "A multiprocess drop-in replacement for Rust channels"
55
authors = ["The Servo Project Developers"]
66
license = "MIT/Apache-2.0"
@@ -12,6 +12,8 @@ force-inprocess = []
1212
memfd = ["sc"]
1313
unstable = []
1414
async = ["futures-preview", "futures-test-preview"]
15+
win32-trace = []
16+
windows-shared-memory-equality = []
1517

1618
[dependencies]
1719
bincode = "1"
@@ -32,3 +34,6 @@ sc = { version = "0.2.2", optional = true }
3234

3335
[dev-dependencies]
3436
crossbeam-utils = "0.7"
37+
38+
[target.'cfg(target_os = "windows")'.dependencies]
39+
winapi = {version = "0.3.7", features = ["minwindef", "ioapiset", "memoryapi", "namedpipeapi", "handleapi", "fileapi", "impl-default"]}

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,3 @@ In order to bootstrap an IPC connection across processes, you create an instance
2121
## Major missing features
2222

2323
* Servers only accept one client at a time. This is fine if you simply want to use this API to split your application up into a fixed number of mutually untrusting processes, but it's not suitable for implementing a system service. An API for multiple clients may be added later if demand exists for it.
24-
25-
* No Windows support exists yet. The right way to implement this will likely be with named pipes and `DuplicateHandle`.

appveyor.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,23 @@ environment:
33
RUST_BACKTRACE: 1
44
matrix:
55
- TARGET: x86_64-pc-windows-msvc
6+
FEATURES: "unstable"
67
- TARGET: i686-pc-windows-msvc
8+
FEATURES: "unstable"
9+
- TARGET: i686-pc-windows-gnu
10+
FEATURES: "unstable"
11+
- TARGET: x86_64-pc-windows-msvc
12+
FEATURES: "unstable force-inprocess"
13+
- TARGET: i686-pc-windows-msvc
14+
FEATURES: "unstable force-inprocess"
15+
- TARGET: i686-pc-windows-gnu
16+
FEATURES: "unstable force-inprocess"
17+
- TARGET: x86_64-pc-windows-msvc
18+
FEATURES: "unstable windows-shared-memory-equality"
19+
- TARGET: i686-pc-windows-msvc
20+
FEATURES: "unstable windows-shared-memory-equality"
21+
- TARGET: i686-pc-windows-gnu
22+
FEATURES: "unstable windows-shared-memory-equality"
723
install:
824
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe"
925
- rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
@@ -13,4 +29,4 @@ install:
1329
build: false
1430

1531
test_script:
16-
- 'cargo test --verbose --features "unstable"'
32+
- cargo test --verbose --features "%FEATURES%"

src/ipc.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,9 +454,11 @@ impl IpcReceiverSet {
454454
/// let shmem = IpcSharedMemory::from_bytes(&data);
455455
/// tx.send(shmem.clone()).unwrap();
456456
/// # let rx_shmem = rx.recv().unwrap();
457+
/// # #[cfg(any(not(target_os = "windows"), all(target_os = "windows", feature = "windows-shared-memory-equality")))]
457458
/// # assert_eq!(shmem, rx_shmem);
458459
/// ```
459-
#[derive(Clone, Debug, PartialEq)]
460+
#[derive(Clone, Debug)]
461+
#[cfg_attr(any(not(target_os = "windows"), all(target_os = "windows", feature = "windows-shared-memory-equality")), derive(PartialEq))]
460462
pub struct IpcSharedMemory {
461463
os_shared_memory: OsIpcSharedMemory,
462464
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ extern crate futures_test;
7979
#[cfg(feature = "async")]
8080
pub mod asynch;
8181

82+
#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))]
83+
extern crate winapi;
84+
85+
8286
pub mod ipc;
8387
pub mod platform;
8488
pub mod router;

src/platform/mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,21 @@ mod os {
2525
pub use super::macos::*;
2626
}
2727

28+
#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))]
29+
mod windows;
30+
#[cfg(all(not(feature = "force-inprocess"), target_os = "windows"))]
31+
mod os {
32+
pub use super::windows::*;
33+
}
34+
2835
#[cfg(any(
2936
feature = "force-inprocess",
30-
target_os = "windows", target_os = "android", target_os = "ios", target_os = "wasi", target_os = "unknown"
37+
target_os = "android", target_os = "ios", target_os = "wasi", target_os = "unknown"
3138
))]
3239
mod inprocess;
3340
#[cfg(any(
3441
feature = "force-inprocess",
35-
target_os = "windows", target_os = "android", target_os = "ios", target_os = "wasi", target_os = "unknown"
42+
target_os = "android", target_os = "ios", target_os = "wasi", target_os = "unknown"
3643
))]
3744
mod os {
3845
pub use super::inprocess::*;

src/platform/test.rs

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,29 @@
77
// option. This file may not be copied, modified, or distributed
88
// except according to those terms.
99

10+
// Most of this file won't compile without `windows-shared-memory-equality` feature on Windows since `PartialEq` won't be implemented for `IpcSharedMemory`.
11+
#![cfg(any(not(target_os = "windows"), all(target_os = "windows", feature = "windows-shared-memory-equality")))]
12+
1013
use crate::platform::{self, OsIpcChannel, OsIpcReceiverSet};
1114
use crate::platform::{OsIpcSharedMemory};
1215
use std::collections::HashMap;
1316
use std::sync::Arc;
1417
use std::time::{Duration, Instant};
1518
use std::thread;
1619

20+
#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))]
21+
use libc;
1722
use crate::platform::{OsIpcSender, OsIpcOneShotServer};
1823
#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))]
1924
use libc::{kill, SIGSTOP, SIGCONT};
2025
#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))]
2126
use crate::test::{fork, Wait};
2227

28+
// Helper to get a channel_name argument passed in; used for the
29+
// cross-process spawn server tests.
30+
#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))]
31+
use crate::test::{get_channel_name_arg, spawn_server};
32+
2333
#[test]
2434
fn simple() {
2535
let (tx, rx) = platform::channel().unwrap();
@@ -209,7 +219,8 @@ fn with_n_fds(n: usize, size: usize) {
209219

210220
// These tests only apply to platforms that need fragmentation.
211221
#[cfg(all(not(feature = "force-inprocess"), any(target_os = "linux",
212-
target_os = "freebsd")))]
222+
target_os = "freebsd",
223+
target_os = "windows")))]
213224
mod fragment_tests {
214225
use crate::platform;
215226
use super::with_n_fds;
@@ -656,9 +667,32 @@ fn server_connect_first() {
656667
(data, vec![], vec![]));
657668
}
658669

670+
#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))]
671+
#[test]
672+
fn cross_process_spawn() {
673+
let data: &[u8] = b"1234567";
674+
675+
let channel_name = get_channel_name_arg("server");
676+
if let Some(channel_name) = channel_name {
677+
let tx = OsIpcSender::connect(channel_name).unwrap();
678+
tx.send(data, vec![], vec![]).unwrap();
679+
680+
unsafe { libc::exit(0); }
681+
}
682+
683+
let (server, name) = OsIpcOneShotServer::new().unwrap();
684+
let mut child_pid = spawn_server("cross_process_spawn", &[("server", &*name)]);
685+
686+
let (_, received_data, received_channels, received_shared_memory_regions) =
687+
server.accept().unwrap();
688+
child_pid.wait().expect("failed to wait on child");
689+
assert_eq!((&received_data[..], received_channels, received_shared_memory_regions),
690+
(data, vec![], vec![]));
691+
}
692+
659693
#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))]
660694
#[test]
661-
fn cross_process() {
695+
fn cross_process_fork() {
662696
let (server, name) = OsIpcOneShotServer::new().unwrap();
663697
let data: &[u8] = b"1234567";
664698

@@ -674,9 +708,42 @@ fn cross_process() {
674708
(data, vec![], vec![]));
675709
}
676710

711+
#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))]
712+
#[test]
713+
fn cross_process_sender_transfer_spawn() {
714+
let channel_name = get_channel_name_arg("server");
715+
if let Some(channel_name) = channel_name {
716+
let super_tx = OsIpcSender::connect(channel_name).unwrap();
717+
let (sub_tx, sub_rx) = platform::channel().unwrap();
718+
let data: &[u8] = b"foo";
719+
super_tx.send(data, vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap();
720+
sub_rx.recv().unwrap();
721+
let data: &[u8] = b"bar";
722+
super_tx.send(data, vec![], vec![]).unwrap();
723+
724+
unsafe { libc::exit(0); }
725+
}
726+
727+
let (server, name) = OsIpcOneShotServer::new().unwrap();
728+
let mut child_pid = spawn_server("cross_process_sender_transfer_spawn", &[("server", &*name)]);
729+
730+
let (super_rx, _, mut received_channels, _) = server.accept().unwrap();
731+
assert_eq!(received_channels.len(), 1);
732+
let sub_tx = received_channels[0].to_sender();
733+
let data: &[u8] = b"baz";
734+
sub_tx.send(data, vec![], vec![]).unwrap();
735+
736+
let data: &[u8] = b"bar";
737+
let (received_data, received_channels, received_shared_memory_regions) =
738+
super_rx.recv().unwrap();
739+
child_pid.wait().expect("failed to wait on child");
740+
assert_eq!((&received_data[..], received_channels, received_shared_memory_regions),
741+
(data, vec![], vec![]));
742+
}
743+
677744
#[cfg(not(any(feature = "force-inprocess", target_os = "windows", target_os = "android", target_os = "ios")))]
678745
#[test]
679-
fn cross_process_sender_transfer() {
746+
fn cross_process_sender_transfer_fork() {
680747
let (server, name) = OsIpcOneShotServer::new().unwrap();
681748

682749
let child_pid = unsafe { fork(|| {
@@ -691,7 +758,7 @@ fn cross_process_sender_transfer() {
691758

692759
let (super_rx, _, mut received_channels, _) = server.accept().unwrap();
693760
assert_eq!(received_channels.len(), 1);
694-
let sub_tx = received_channels.pop().unwrap().to_sender();
761+
let sub_tx = received_channels[0].to_sender();
695762
let data: &[u8] = b"baz";
696763
sub_tx.send(data, vec![], vec![]).unwrap();
697764

@@ -981,3 +1048,82 @@ mod sync_test {
9811048
platform::OsIpcSender::test_not_sync();
9821049
}
9831050
}
1051+
1052+
// This test panics on Windows, because the other process will panic
1053+
// when it detects that it receives handles that are intended for another
1054+
// process. It's marked as ignore/known-fail on Windows for this reason.
1055+
//
1056+
// TODO -- this fails on OSX as well with a MACH_SEND_INVALID_RIGHT!
1057+
// Needs investigation. It may be a similar underlying issue, just done by
1058+
// the kernel instead of explicitly (ports in a message that's already
1059+
// buffered are intended for only one process).
1060+
#[cfg(not(any(feature = "force-inprocess", target_os = "android", target_os = "ios")))]
1061+
#[cfg_attr(any(target_os = "windows", target_os = "macos"), ignore)]
1062+
#[test]
1063+
fn cross_process_two_step_transfer_spawn() {
1064+
let cookie: &[u8] = b"cookie";
1065+
1066+
let channel_name = get_channel_name_arg("server");
1067+
if let Some(channel_name) = channel_name {
1068+
// connect by name to our other process
1069+
let super_tx = OsIpcSender::connect(channel_name).unwrap();
1070+
1071+
// create a channel for real communication between the two processes
1072+
let (sub_tx, sub_rx) = platform::channel().unwrap();
1073+
1074+
// send the other process the tx side, so it can send us the channels
1075+
super_tx.send(&[], vec![OsIpcChannel::Sender(sub_tx)], vec![]).unwrap();
1076+
1077+
// get two_rx from the other process
1078+
let (_, mut received_channels, _) = sub_rx.recv().unwrap();
1079+
assert_eq!(received_channels.len(), 1);
1080+
let two_rx = received_channels[0].to_receiver();
1081+
1082+
// get one_rx from two_rx's buffer
1083+
let (_, mut received_channels, _) = two_rx.recv().unwrap();
1084+
assert_eq!(received_channels.len(), 1);
1085+
let one_rx = received_channels[0].to_receiver();
1086+
1087+
// get a cookie from one_rx
1088+
let (data, _, _) = one_rx.recv().unwrap();
1089+
assert_eq!(&data[..], cookie);
1090+
1091+
// finally, send a cookie back
1092+
super_tx.send(&data, vec![], vec![]).unwrap();
1093+
1094+
// terminate
1095+
unsafe { libc::exit(0); }
1096+
}
1097+
1098+
// create channel 1
1099+
let (one_tx, one_rx) = platform::channel().unwrap();
1100+
// put data in channel 1's pipe
1101+
one_tx.send(cookie, vec![], vec![]).unwrap();
1102+
1103+
// create channel 2
1104+
let (two_tx, two_rx) = platform::channel().unwrap();
1105+
// put channel 1's rx end in channel 2's pipe
1106+
two_tx.send(&[], vec![OsIpcChannel::Receiver(one_rx)], vec![]).unwrap();
1107+
1108+
// create a one-shot server, and spawn another process
1109+
let (server, name) = OsIpcOneShotServer::new().unwrap();
1110+
let mut child_pid = spawn_server("cross_process_two_step_transfer_spawn",
1111+
&[("server", &*name)]);
1112+
1113+
// The other process will have sent us a transmit channel in received channels
1114+
let (super_rx, _, mut received_channels, _) = server.accept().unwrap();
1115+
assert_eq!(received_channels.len(), 1);
1116+
let sub_tx = received_channels[0].to_sender();
1117+
1118+
// Send the outer payload channel, so the server can use it to
1119+
// retrive the inner payload and the cookie
1120+
sub_tx.send(&[], vec![OsIpcChannel::Receiver(two_rx)], vec![]).unwrap();
1121+
1122+
// Then we wait for the cookie to make its way back to us
1123+
let (received_data, received_channels, received_shared_memory_regions) =
1124+
super_rx.recv().unwrap();
1125+
let child_exit_code = child_pid.wait().expect("failed to wait on child");
1126+
assert!(child_exit_code.success());
1127+
assert_eq!((&received_data[..], received_channels, received_shared_memory_regions),
1128+
(cookie, vec![], vec![]));
1129+
}

0 commit comments

Comments
 (0)