Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add windows NamedPipe #1351

Merged
merged 23 commits into from Oct 2, 2020
Merged

Add windows NamedPipe #1351

merged 23 commits into from Oct 2, 2020

Conversation

carllerche
Copy link
Member

Tokio needs windows named pipe support in order to upgrade to Mio 0.7. I thought I would take a quick initial stab at this.

It is based on https://github.com/bbqsrc/mio-named-pipes, but simplified some. Instead of shoehorning this into the Source trait, I dod registration in new. Also, since the Registration internal queue is gone in Mio 0.6, I built the API on top of std::task::Waker instead. As this is essentially adding an IOCP based API on top of Mio in order to power Tokio, it seemed silly to add a shim to make the API "seem" like epoll and then layer wakers back on.

@carllerche
Copy link
Member Author

The reason I am opting to add this to mio proper is because #1047 is not done, so NamedPipe cannot be added outside of mio proper.

@Thomasdezeeuw
Copy link
Collaborator

Thomasdezeeuw commented Sep 29, 2020

Want me to move https://github.com/Thomasdezeeuw/mio-pipe into Mio as well? I was planning on proposing that for a while.

@carllerche
Copy link
Member Author

Sure! In general, I think it is fair to have mio constructs for common OS primitives.

@Thomasdezeeuw
Copy link
Collaborator

@carllerche created an issue for the Mio-pipe move: #1354.

@carllerche carllerche marked this pull request as ready for review September 30, 2020 05:04
@carllerche
Copy link
Member Author

Ok, I got this building and tests passing. I would appreciate a review. This is an upgrade of the mio-named-pipes crate. I could use a review now, but let me get tokio-rs/tokio#2893 building w/ this before merging.

Copy link
Collaborator

@Thomasdezeeuw Thomasdezeeuw left a comment

Choose a reason for hiding this comment

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

I'm mostly OK with the code, however this uses the task/Future system while other Mio types do not. I don't think we should change to it now. I've added a number of inline comments, but a large number of them are small nits.

src/sys/windows/named_pipe.rs Outdated Show resolved Hide resolved
src/sys/windows/named_pipe.rs Outdated Show resolved Hide resolved
src/sys/windows/named_pipe.rs Show resolved Hide resolved
src/sys/windows/named_pipe.rs Outdated Show resolved Hide resolved
src/sys/windows/afd.rs Show resolved Hide resolved
use std::task::{Context, Poll};
use std::time::Duration;

// use mio::{Events, Poll, PollOpt, Ready, Token};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove.

tests/win_named_pipe.rs Outdated Show resolved Hide resolved
src/sys/windows/named_pipe.rs Outdated Show resolved Hide resolved
tests/win_named_pipe.rs Outdated Show resolved Hide resolved
tests/win_named_pipe.rs Show resolved Hide resolved
@carllerche
Copy link
Member Author

@Thomasdezeeuw Ok, the feedback should be addressed. I opted to stay w/ SeqCst as the lowering was not obviously correct to me and I didn't want to do a deeper analysis. Because load and store is being used, w/o SeqCst there is no guarantee that the atomic ops see the "latest value".

@carllerche
Copy link
Member Author

Ok, I tested that this works for Tokio. 👍

@Thomasdezeeuw
Copy link
Collaborator

@Thomasdezeeuw Ok, the feedback should be addressed. I opted to stay w/ SeqCst as the lowering was not obviously correct to me and I didn't want to do a deeper analysis. Because load and store is being used, w/o SeqCst there is no guarantee that the atomic ops see the "latest value".

If I understood correctly, the only benefit SeqCst provides over AcqRel is that it guarantees the ordering in relation to atomic operations on on data location. In other words if we had two AtomicBools and we did a store on 1 and then on 2, that order has to be preserved with SeqCst, while with AcqRel the compiler/hardware is allowed to switch them around. But as we're only using a single atomic location that shouldn't matter.

Copy link
Collaborator

@Thomasdezeeuw Thomasdezeeuw left a comment

Choose a reason for hiding this comment

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

Couple more small things, but LGTM.

src/sys/windows/named_pipe.rs Show resolved Hide resolved
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
Copy link
Collaborator

Choose a reason for hiding this comment

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

This combined with the scheduled write above might give issues were a user thinks the bytes have been written to the pipe and closes the application, but it's not actually the case.

src/sys/windows/named_pipe.rs Show resolved Hide resolved
src/sys/windows/named_pipe.rs Outdated Show resolved Hide resolved
src/sys/windows/named_pipe.rs Outdated Show resolved Hide resolved
tests/win_named_pipe.rs Show resolved Hide resolved
@@ -0,0 +1,267 @@
#![cfg(windows)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: These tests can use some of the test utilities in tests/util, such as expect_events, but that can be done later.

}

#[test]
fn write_then_drop() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Re: not implementing flush (or a no-op implementation) above. I think running this test with the client in a different process that stops right after the write (line 194) would fail.

@carllerche
Copy link
Member Author

@Thomasdezeeuw Ok, the feedback should be addressed. I opted to stay w/ SeqCst as the lowering was not obviously correct to me and I didn't want to do a deeper analysis. Because load and store is being used, w/o SeqCst there is no guarantee that the atomic ops see the "latest value".

If I understood correctly, the only benefit SeqCst provides over AcqRel is that it guarantees the ordering in relation to atomic operations on on data location. In other words if we had two AtomicBools and we did a store on 1 and then on 2, that order has to be preserved with SeqCst, while with AcqRel the compiler/hardware is allowed to switch them around. But as we're only using a single atomic location that shouldn't matter.

SeqCst gets you total order on a single atomic cell as well. load(Acquire) is permitted to return stale values as long as the coherence rules are followed. store(Release) has no requirement to immediately make the store visible to other threads as long as coherence rules are followed. It is a common mistake to make and I have made it a few times myself. If you want, you can play around w/ this w/ loom to see how it would behave.

@Thomasdezeeuw
Copy link
Collaborator

SeqCst gets you total order on a single atomic cell as well.

True, but AcqRel already guarantees that.

load(Acquire) is permitted to return stale values as long as the coherence rules are followed.

I don't think this is correct. load(Acquire), coupled with store(Release) (not Relaxed), may never return a stale value according to the docs in atomic::Ordering.

store(Release) has no requirement to immediately make the store visible to other threads as long as coherence rules are followed.

This is true, but if we're using store or swap on another thread then the store(Release) must be made visible.

@carllerche
Copy link
Member Author

@Thomasdezeeuw AcqRel does not guarantee that for stores and loads. If, given a single atomic cell, only read-modify-write operation are performed, then AcqRel is sufficient to establish a linear modification order. This is because of the guarantees that come w/ read-modify-write operations. However, once load and store is thrown into the mix, all bets are off.

I have seen this in practice and the documentation indicates as such.

@carllerche
Copy link
Member Author

Look at the ASM generated by this:

use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{Acquire, Release};

static CELL: AtomicUsize = AtomicUsize::new(0);


pub fn main() -> usize {
CELL.store(1, Release);
CELL.load(Acquire)
}
example::main:
        mov     qword ptr [rip + example::CELL], 1
        mov     rax, qword ptr [rip + example::CELL]
        ret

example::CELL:
        .zero   8

Plain mov instructions. No synchronization. Stale values can be obtained when a CPU loads the value from its cache w/o synchronizing across threads.

@carllerche
Copy link
Member Author

Alright! CI passes 🎉 let me investigate Tokio now.

@carllerche carllerche merged commit 52e8c22 into master Oct 2, 2020
@Thomasdezeeuw
Copy link
Collaborator

Look at the ASM generated by this:

use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering::{Acquire, Release};

static CELL: AtomicUsize = AtomicUsize::new(0);


pub fn main() -> usize {
CELL.store(1, Release);
CELL.load(Acquire)
}
example::main:
        mov     qword ptr [rip + example::CELL], 1
        mov     rax, qword ptr [rip + example::CELL]
        ret

example::CELL:
        .zero   8

Plain mov instructions. No synchronization. Stale values can be obtained when a CPU loads the value from its cache w/o synchronizing across threads.

The mov for the load is expected, as an <= 8 bytes load is always atomic on x86, but I would expect xchg for the store. I learned something new, thanks!

For cas operations it doesn't matter and I incorrectly assumed the same to be true for load/store operations.

use std::sync::atomic::{AtomicUsize, Ordering};

static CELL: AtomicUsize = AtomicUsize::new(0);

pub fn main() {
    CELL.fetch_add(1, Ordering::AcqRel);
    CELL.fetch_add(1, Ordering::SeqCst);
    CELL.load(Ordering::SeqCst);
}
example::main:
        lock            add     qword ptr [rip + example::CELL], 1
        lock            add     qword ptr [rip + example::CELL], 1
        mov     rax, qword ptr [rip + example::CELL]
        ret

example::CELL:
        .zero   8

https://rust.godbolt.org/z/1r5M4Y

@Thomasdezeeuw Thomasdezeeuw deleted the named-pipe branch October 2, 2020 08:07
@bbqsrc
Copy link

bbqsrc commented Oct 5, 2020

I've deprecated and directed users of the old mio-named-pipes to mio 0.7. Thanks!

udoprog added a commit to tokio-rs/tokio that referenced this pull request Jun 15, 2021
This builds on tokio-rs/mio#1351 and introduces the
tokio::net::windows::named_pipe module which provides low level types for
building and communicating asynchronously over windows named pipes.

Named pipes require the `net` feature flag to be enabled on Windows.

Co-authored-by: Alice Ryhl <alice@ryhl.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants