|
Boost : |
Subject: [boost] [asio] Closing posix::stream_descriptor sets it back to blocking
From: Preston A. Elder (prez_at_[hidden])
Date: 2009-07-28 17:58:20
I have two posix::stream_descriptors to the same element, in this case,
a dup() of the STDIN_FILENO, reated as such (basically):
class MyClass
{
char buf[4096];
posix::stream_descriptor in;
void read(const system::error_code &err, size_t xfer)
{
// Do something ... then ...
in.async_read_some(buffer(buf, 4096), &read);
}
public:
MyClass(io_service &svc) : in(svc, dup(STDIN_FILENO))
{
in.async_read_some(buffer(buf, 4096), &read);
}
};
MyClass *myc;
// ...
io_service svc;
void init()
[
char buf[4096];
posix::stream_descriptor stdin(svc, dup(STDIN_FILENO));
stdin.async_read_some(buffer(buf, 4096), &read_func);
while (!myc)
sleep(1);
}
// ...
void read_func(const system::error_code &err, size_t xfer)
{
// ... do some stuff ...
myc = new MyClass(svc);
}
The reason I do this is a design one, and it's a lot more complicated
than the above, but suffice to say there are lots of other factors
involved, and I cannot just pass around the stdin object and send it to
the constructor of my MyClass. Making multiple dup's of STDIN_FILENO is
really not a problem. And having them in posix::stream_descriptors
means they even get cleaned up nicely.
That's all well and good, but the problem comes in with how
stream_descriptor handles non-blocking I/O. When I do my
async_read_some on the stdin object (created in init()), it sets
non-blocking I/O, and then sets a flag inside the stdin object.
When I create the in object inside of MyClass, it thinks the socket is
non-blocking, but when I call the async_read_some function on THAT
object, it again sets the FD to non-blocking I/O and sets the internal
flag. A little redundant but I can deal with this. In reality that
flag should be set by finding out what the blocking state is rather than
assuming it's blocking.
Here is where the problems start. When the stdin object is destructed
(goes out of scope), it CHANGES the blocking state to blocking (because
the internal flag says it's non-blocking) before it closes the FD. The
code that does this is in detail/reactive_descriptor_service.hpp in both
reactive_descriptor_service::destroy() and
reactive_descriptor_service::close(). Because both objects are dup's of
the same source FD, they all share blocking state, and this now sets my
in object inside of MyClass to blocking I/O - without changing the
internal flag. Meaning that when I DO get some data, and call
MyClass::read, and then go to call async_read_some, now this will be a
blocking call because reactive_descriptor_service::async_read_some()
sees the flag already set indicating we are non-blocking, and will not
re-do it.
There is no reason to change the blocking state on a descriptor you are
just about to close or destroy. The very idea seems just wrong, you're
about to make the descriptor invalid, yet you take the time to change
it's blocking state before it becomes invalid? To what purpose?
Additionally, there is no way to manually OVERRIDE this internal state
with boost calls. For example, executing the following:
posix::stream_descriptor::non_blocking_io nb(true);
in.command(nb);
Will just set a FLAG inside the reactive_descriptor_service that says
the user wants the handle to go to non-blocking mode when the next
blocking action takes place. It does not alter the state immediately.
And since the internal flag state (which is now wrong) is checked first,
setting this flag has no effect.
The only thing in the end I can do is manually call ioctl myself in my
code the first time MyClass::read() is invoked. This then basically
makes the blocking state match the internal flag of the 'in' object and
from then on everything is fine. But this is klugy.
So I would like to request the asio maintainer either a) STOP setting
the object back to blocking on close/destroy, given that it serves no
purpose when you're just about to close the damn descriptor anyway. b)
Set the internal blocking state flag based on the actual blocking state
on construction, rather than just assuming it's non-blocking. And c)
provide a way to actually change the blocking state of the descriptor,
rather than just setting a flag saying we want it changed when you next
go to do a blocking I/O operation. There may be a good reason to make
such a request, such as the user knowing the state has changed without
the reactive_descriptor_service having any knowledge of it, such as in
this case.
PreZ :)
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk