-
Notifications
You must be signed in to change notification settings - Fork 312
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 socketpair shim #3609
base: master
Are you sure you want to change the base?
Add socketpair shim #3609
Conversation
This is not entirely ready, and still waiting for discussion to happen, but I will tag it with @rustbot ready |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will also need to handle the case where the other socket was dropped. What do the manpages say about that case? Will we need to report https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.ConnectionAborted or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I think the general strategy works, I left some comments to complement Oli's feedback.
There is one issue we need to still consider: the interaction with data race detection. Since this can be used to send data from one thread to another, arguably a write
to a buffer should be considered a "release" operation, and a read
should be considered an "acquire". The way to test for this is to have a test with -Zmiri-preemption-rate=0
and then
- have a global
static mut VAL: u8 = 0;
- in main, create a socketpair and then spawn a thread
- in the main thread, go on by setting
VAL
to 1 and then sending a byte onto one socket. then callthread::yield_now
. - in the other thread, read one byte from the socket, then
assert_eq!(VAL, 1)
.
In your PR as-is, this should report UB due to a data race. However, arguably there is no data race here, since the other thread waited to receive a signal from the main thread.
To fix that, you should move the VecDeque<u8>
into a new type, Buffer
. This buffer needs a second field clock: VClock
.
- When doing a
write
, you need to do something likeclock.join(data_race.release_clock(ecx.get_active_thread(), ecx.machine.current_span()))
, wheredata_race
is inecx.machine.data_race
but it can beNone
(in which case you don't need to do anything). - When doing a read, you need to
data_race.acquire_clock(&buffer.clock, ecx.get_active_thread())
.
(We should really have some nicer wrapper functions for this...)
Hmm, it seems like I got into some rebase trouble. Sorry if anyone get pinged on this PR. |
@rustbot author |
It turns out that But we have quite a few tasks that should be handled first, maybe we can delay the support |
Yes we need special handling for these cases1, this is the new specification: For non-blocking
For blocking
Footnotes
|
We also need to implement We can add in a new field that is being shared between 2 socketpair struct: struct SocketPair {
readbuf: Rc<RefCell<VecDeque<u8>>>,
writebuf: Rc<RefCell<VecDeque<u8>>>,
is_nonblock: bool,
both_end_open: Rc<RefCell<bool>>,
} This is the initial state where both side is opened,
When
|
How is nonblocking EOF differentiated from no available bytes?
Prefer |
Blocking write/read will require some refactorings to allow us to park the thread and continue later. We already have the concept for |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
That's not going to work with duplicated file descriptors, we'd need another way to detect that the last copy of one end of the pair was dropped. What we could do instead is to use This does mean you need to upgrade before every write, but that seems ok |
Ah, that's neat! Since not being able to upgrade to struct SocketPair {
readbuf: Weak<RefCell<VecDeque<u8>>>,
writebuf: Weak<RefCell<VecDeque<u8>>>,
is_nonblock: bool,
} (Note: this won't be the final design, we still need to consider data race detection) |
readbuf needs to be Rc, not Weak, one side always needs to be Rc, otherwise both Weaks are dangling and can't be upgraded. When thr last Rc gets dropped, all weak become invalid |
I see. Let's say
we could use |
Yea, that would work |
@tiif your list above is not entirely right -- when all writers are closed but there is data left in the buffer, that data should still be returned. Only then should you indicate EOF. |
Checking the Maybe it's better to just have our own |
Yes, I didn't consider this case. The list is now updated.
I have been thinking about this for a whlie, but not sure how this is going to work yet. Initial state,
If we
Closing
So I am not sure how I am thinking of letting the struct SocketPair {
readbuf: Rc<RefCell<VecDeque<u8>>>,
writebuf: Weak<RefCell<VecDeque<u8>>>,
is_nonblock: bool,
// number of writer for readbuf
readbufWriterCount: Rc<Cell<i64>>,
// number of writer for writebuf
writebufWriterCount: Rc<Cell<i64>>
}
For this case, Since these counters is only used to track writer of buffers, it won't break when the buffer is referred for other purpose. Not sure if I over-complicated this problem, I feel a more elegant solution exists somewhere. |
Co-authored-by: Ralf Jung <post@ralfj.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
Co-authored-by: Oli Scherer <github35764891676564198441@oli-obk.de>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RalfJung this looks ready to me now. Once you've approved, too, let's squash all commits into one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot! I have a bunch of comments but they're mostly minor. :)
struct Buffer { | ||
buf: VecDeque<u8>, | ||
clock: VClock, | ||
buf_has_writer: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This field is also interesting enough that it should have a comment.
@@ -20,17 +38,97 @@ impl FileDescription for SocketPair { | |||
self: Box<Self>, | |||
_communicate_allowed: bool, | |||
) -> InterpResult<'tcx, io::Result<()>> { | |||
// This is used to signal socketfd of other side that there is no writer to it's readbuf. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// This is used to signal socketfd of other side that there is no writer to it's readbuf. | |
// This is used to signal socketfd of other side that there is no writer to its readbuf. |
@@ -20,17 +38,97 @@ impl FileDescription for SocketPair { | |||
self: Box<Self>, | |||
_communicate_allowed: bool, | |||
) -> InterpResult<'tcx, io::Result<()>> { | |||
// This is used to signal socketfd of other side that there is no writer to it's readbuf. | |||
// If the upgrade fails, there is no need to update as all read ends has been dropped. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If the upgrade fails, there is no need to update as all read ends has been dropped. | |
// If the upgrade fails, there is no need to update as all read ends have been dropped. |
if data_size == 0 { | ||
if !readbuf_has_writer { | ||
// Socketpair with no writer and empty buffer. | ||
return Ok(Ok(0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return Ok(Ok(0)); | |
// 0 bytes successfully read indicates end-of-file. | |
return Ok(Ok(0)); |
} | ||
} | ||
|
||
// Prevent false alarm in data race detection when doing synchronisation via socketpair. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Prevent false alarm in data race detection when doing synchronisation via socketpair. | |
// Synchronize with all previous writes to this buffer. | |
// FIXME: this over-synchronizes; a more precise approach would be to | |
// only sync with the writes whose data we will read. |
throw_unsup_format!("socketpair write: blocking isn't supported yet"); | ||
} | ||
} | ||
// Prevent false alarm in data race detection when doing synchronisation via socketpair. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Prevent false alarm in data race detection when doing synchronisation via socketpair. | |
// Remember this clock so `read` can synchronize with us. |
} | ||
} | ||
// Prevent false alarm in data race detection when doing synchronisation via socketpair. | ||
writebuf.clone().unwrap().borrow_mut().clock.join(&ecx.release_clock().unwrap()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You cannot unwrap
this, that can ICE. Instead do something like
if let Some(clock) = ecx.release_clock() {
// ...
}
|
||
// Parse and remove the type flags that we support. If type != 0 after removing, | ||
// unsupported flags are used. | ||
// FIXME: SOCK_DRAM that supposed to have message boundaries is not supported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// FIXME: SOCK_DRAM that supposed to have message boundaries is not supported |
This is more like "TODO: support SOCK_DGRAM", but I don't think that's something we want/need in the code. It can be an issue if needed.
// FIXME: To support macos for tokio socketpair, extra handling on the libc::O_NONBLOCK | ||
// flag is needed because macos doesn't have SOCK_CLOEXEC and SOCK_NONBLOCK flag. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// FIXME: To support macos for tokio socketpair, extra handling on the libc::O_NONBLOCK | |
// flag is needed because macos doesn't have SOCK_CLOEXEC and SOCK_NONBLOCK flag. | |
// SOCK_NONBLOCK only exists on Linux. |
Please also then adjust the check to be == "linux"
. Currently you re saying that all OSes except macOS support SOCK_NONBLOCK. Have you checked that FreeBSD, Solaris, OpenBSD, and so on all support it? If not, then please only add support on OSes where we are use support exists. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I don't think these tests are necessary. We don't have tests for all unsupported situations, that would quickly get out of hand. OTOH now that you already wrote the tests we may as well keep them. :)
Fixes #3442
Design proposal: https://hackmd.io/@tiif/Skhc1t0-C