-
Notifications
You must be signed in to change notification settings - Fork 9
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
Do not open PTY with O_NONBLOCK on FreeBSD #48
Conversation
Thanks for the PR! With this change, the master PTY FD will never be set to nonblocking mode on FreeBSD. That seems like a problem since the point of the crate is to get nonblocking/async I/O over a PTY connection. Do we need to alter the flags on the master FD to set O_NONBLOCK after it's created, perhaps? Does FreeBSD just not support the concept of a nonblocking PTY connection? |
I'm not sure actually, maybe we can set it with |
Yeah, I feel bad about the lack of testing framework. But, empirically, not bad enough to add any :-/ With the your PR applied, are you able to test if the |
Hmm, I will take a look and see how to use stund 👍 - I've only really looked at tokio-pty-process... |
FreeBSD's posix_openpt(2) implementation only supports O_RDWR, O_NOCTTY, and O_CLOEXEC. According to the manpage, "The posix_openpt() function shall fail when oflag contains other values."
I've run the following manual smoke tests:
Not sure what more functionality to test and/or what to expect. UpdateWhen compiling |
I'm pretty sure that your first smoketest is indicating that the nonblocking setting isn't working :-( I'm having trouble pulling up anything relevant on Google, but looking at the FreeBSD source code, it looks like nonblocking PTY's should be possible on that OS. Do you know any experienced low-level FreeBSD developers who might know more about the details? The other thing is that this is definitely showing that it would be great to have a better test case for checking that nonblocking mode is working. It's hard for me to think of how exactly to go about that, though … maybe write a bunch of data and see whether you ever get an Links |
How so, should cargo run open ... terminate?
I'm guessing it would probably be worth writing to the freebsd-hackers@freebsd.org mailing list about this? I can do this, but since I feel you have a deeper understanding of the expected behavior, it might be better if you would. If you do, please CC me, or ping me to write the mail :).
Might be an idea to spawn a child connected to the slave that sleeps and doesn't read, and write a large bunch of data, until the buffers are full? I'll try to take a look.
|
Yes, the whole point is that the I should say that it's entirely possible that the If you can email freebsd-hackers, that might get a good answer. But I'll also see if I can install a FreeBSD VM and investigate a bit myself.
Right, that was what I was thinking. It's a bit of a hassle since you've got to write a custom client program … but well, sometimes testing takes work. |
Any idea how I would do this? If I call For reference, this is my naïve attempt: extern crate tokio;
extern crate tokio_pty_process;
use tokio::prelude::*;
fn main() {
let mut master = tokio_pty_process::AsyncPtyMaster::open().expect("could not open pty");
let mut buf = vec![0u8, 100];
// Reading when there is no data available should block.
let ret = master.read(&mut buf);
println!("ret: {:?}", ret);
} |
Hmm, maybe consult the Tokio tutorials? I think you need to create a "reactor" to drive the event loop. |
if libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) == -1 { | ||
return Err(io::Error::last_os_error()); | ||
}; | ||
} |
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.
Funnily enough, if this hunk is removed, the following still does not panic on FreeBSD:
extern crate tokio;
extern crate tokio_pty_process;
use tokio::prelude::*;
use tokio_pty_process::AsyncPtyMaster;
struct AsyncReadTest(AsyncPtyMaster);
impl Future for AsyncReadTest {
type Item = ();
type Error = std::io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut buf = vec![0u8, 100];
self.0.read(&mut buf).map(|_| Async::Ready(()))
}
}
fn main() {
let err = AsyncPtyMaster::open()
.map(AsyncReadTest)
.expect("could not open pty")
.wait()
.expect_err("Read succeeded but no data available");
assert_eq!(err.kind(), std::io::ErrorKind::WouldBlock);
}
Can you confirm this behavior on Linux too, if the AsyncPtyMaster is not opened with O_NONBLOCK
?
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.
Yes, I get this behavior ... but if I strace the program, it isn't actually doing any PTY I/O! I am not sure what's going on — for some reason I can't step through it in GDB at the moment.
Clarification: the program opens a PTY master, but I don't see any read operations on the resulting FD.
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're right, I see that too:
$ kdump -i ktrace.out
[...]
34900 tokio-runtime-worke RET thr_new 0
34900 wouldblock RET fork 0
34900 wouldblock CALL sigaltstack(0,0x7fffdfdfcf80)
34900 wouldblock RET sigaltstack 0
34900 wouldblock CALL mmap(0,0x8800,0x3<PROT_READ|PROT_WRITE>,0x1002<MAP_PRIVATE|MAP_ANON>,0xffffffff,0)
34900 tokio-runtime-worke CALL posix_openpt(0x8002)
34900 wouldblock RET mmap 34384453632/0x801792000
34900 wouldblock CALL sigaltstack(0x7fffdfdfcf80,0)
34900 wouldblock RET sigaltstack 0
34900 tokio-runtime-worke RET posix_openpt 15/0xf
34900 wouldblock CALL thr_set_name(0x18a46,0x80177d0e0)
[...]
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.
From how I understand it:
- the
Read
impl forPollEvented2
will callPollEvented2.poll_read_ready()
- this is a wrapper around
self.inner.registration.poll_read_ready()
- This is our registration:
stund/tokio-pty-process/src/lib.rs
Lines 92 to 95 in b56e613
EventedFd(&self.0.as_raw_fd()).register(poll, token, interest | UnixReady::hup(), opts)
What state does that start out in? Will it wait for the OS to signal readiness before even attempting the read?
@fabianfreyer looks like you are missing an executor to run your futures? If you are using any of the tokio items it needs a tokio runtime to run the futures. Wait generally won't work here. extern crate tokio;
extern crate tokio_pty_process;
use tokio::prelude::*;
use tokio_pty_process::AsyncPtyMaster;
struct AsyncReadTest(AsyncPtyMaster);
impl Future for AsyncReadTest {
type Item = ();
type Error = std::io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let mut buf = vec![0u8, 100];
self.0.read(&mut buf).map(|_| Async::Ready(()))
}
}
fn main() {
let fut = futures::lazy(|| {
AsyncPtyMaster::open()
.map(AsyncReadTest)
.map_err(|e| panic!("{}", e))
});
tokio::run(fut);
} Now tokio run itself creates a The key to his code I wrote above is that the error you got is that you are trying to spawn a future without actually already being in a future based context. By using futures lazy you can create a future that will execute right away (no blocking or no |
Thanks @LucioFranco! Even with those changes though, there doesn't seem to be a difference to a blocking PTY descriptor. |
@fabianfreyer I don't quite understand what you mean by a blocking PTY descriptor in this case? Could you show me what code is blocking? |
@LucioFranco We're now opening the PTY without stund/tokio-pty-process/src/lib.rs Lines 126 to 130 in b56e613
The file descriptor returned from this is wrapped in an AsyncPtyFile :stund/tokio-pty-process/src/lib.rs Lines 152 to 155 in b56e613
According to the FreeBSD source code, I expect the read to block when the The above code's purpose is to verify this. However, it returns an
|
OTOH, I replicated this test case with two C programs which do show the desired behavior: Blocking#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
void blocked(int _) {
puts("blocked");
exit(0);
}
int main() {
alarm(1);
signal(SIGALRM, blocked);
int pty_master = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC);
char *buf[256];
ssize_t ret = read(pty_master, buf, 256);
assert(ret == -1);
assert(errno == EWOULDBLOCK);
} Output:
Non-Blocking#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
int main() {
int pty_master = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC);
char *buf[256];
int flags = fcntl(pty_master, F_GETFL, 0);
assert(fcntl(pty_master, F_SETFL, flags | O_NONBLOCK) != -1);
ssize_t ret = read(pty_master, buf, 256);
assert(ret == -1);
assert(errno == EWOULDBLOCK);
perror("read");
} Output:
|
@fabianfreyer sorry for the delayed response, honestly, this is a bit out of my personal knowledge, Anyways, good luck finding the solution! Feel free to ping me here or in gitter if help is needed with higher level tokio types and futures. |
Note to self: apparently CirrusCI offers FreeBSD CI so that this can be tested automatically one the proper approach is devised. |
I just checked the current version on macos, and it seems to be exhibiting exactly the same behavior. This leads me to the conclusion that this seems to be a different issue. Any chance we could merge this into |
@pkgw what do you think of the approach outlined in #48 (comment)? Seeing as the issue I am having seems to be an unrelated issue - any chance we could move this forward? |
Sorry for letting this languish. Yes, let's merge this and worry about the other bits later. |
Thanks, great! |
FreeBSD's
posix_openpt(2)
implementation only supportsO_RDWR
, O_NOCTTY,and
O_CLOEXEC
. According to the manpage: