Hi,
On Jul 31, 2:48 am, Geert Jansen <
gee...@gmail.com> wrote:
> Hi,
>
> Attached is an updated patch. Changes:
>
> * Implemented the proposed new semantics where the patched os.read() and
> os.write() restore file descriptor flags.
> * The patched os.read() and os.write() now only raise an OSError.
> Previously they could also raise an IOError which is inconsistent with the
> stdlib versions.
> * The module is now called gevent.os instead of gevent.fd. This module
> just provides the patched functions, it does not expose the entire stdlib.
> * fork() moved to gevent.os as well. This is a user visible change. Hope
> that's still OK given that we're in beta, otherwise a stub can be added to
> gevent.hub.
> * Added absolute_import where necessary to prevent other gevent code
> importing gevent.os instead of the stdlib os.
> * Cleaned up most __import__ hacks and moved to absolute_import.
> * Updated tests for gevent.os.
> * Make sure gevent.fileobject uses the non-patched os.read/os.write.
>
> I have tested this patch on Linux both with the posix and the threadpool
> variants. On my system the test suite runs with 25 errors which all are
> there before my patch too.
>
> Regards,
> Geert
>
> gevent-os.patch
> 15KViewDownload
The following annotated subset of the proposed gevent-os.patch details
serious bugs and os read/write API incompatibilities that I found in
the patch. I will post the fixed posix_read and posix_write shortly.
Serious Bugs and OS read/write incompatibilities in the proposed patch
are tagged with BUG below; corresponding
fix proposals are tagged with FIX:
+def _wrap_errors(func, *args):
+ """Convert any error to an OSError."""
+ try:
+ return func(*args)
+ except Exception, e:
BUG1: this breaks OSError, which is defined as (note errno
and strerror
attributes): "It is raised when a
function returns a system-related error (not for illegal
argument
types or other incidental errors). The errno attribute is a
numeric
error code from errno, and the strerror attribute is the
corresponding
string, as would be printed by the C function perror(). See
the module
errno, which contains names for the error codes defined by
the
underlying operating system."
BUG2: This also causes loss of the original traceback, making
it
impossible for user code to determine which lower-level API
call
actually failed.
FIX: Don't use _wrap_errors, it is not needed; the actual
exception
raised by the lower-level API is the acceptable exception.
+ raise OSError(*e.args)
+
+
+def posix_read(fd, n):
+ """Read up to `n` bytes from file descriptor `fd`."""
+ flags = _wrap_errors(fcntl.fcntl, fd, fcntl.F_GETFL, 0)
+ if not flags & os.O_NONBLOCK:
+ _wrap_errors(fcntl.fcntl, fd, fcntl.F_SETFL, flags|
os.O_NONBLOCK)
+ try:
+ while True:
+ try:
+ return os_read(fd, n)
+ except OSError, e:
+ if e[0] == EBADF:
BUG: This breaks the definition of os.read, which
is defined
as: "Read at most n bytes from file descriptor
fd. Return
a string containing the bytes read. If the end
of the file
referred to by fd has been reached, an empty
string is
returned.".
FIX: Remove the "if e[0] == EBADF: return ''"
block. When
given a bad file descriptor, os.read raises
OSError with errno=EBADF in this case, which is
the
correct behavior (try os.read(999999, 10) and
see for yourself);
+ return ''
+ elif e[0] != EAGAIN:
+ raise
+ sys.exc_clear()
+ hub = get_hub()
+ event =
hub.loop.io(fd, 1)
+ try:
+ _wrap_errors(hub.wait, event)
+ except OSError, e:
+ if e[0] == EBADF:
BUG: same as above EBADF BUG
FIX: same as above EBADF FIX; the original
OSError
exception with errno=EBADF is the correct
behavior and must
NOT be overridden
+ return ''
+ raise
+ finally:
BUG: The flags are restored too late; there is a serious
problem with
these posix_read and posix_write functions when used with
things like
stdin/stdout file descriptors, or other file descriptors
whose fcntl
flags are linked in a similar way. For example, if you
attempt to use
posix_read() on the stdin.fileno(), fcntl.fcntl(fd,
fcntl.F_SETFL, flags|os.O_NONBLOCK)
also makes stdout non-blocking. This causes writes to stdout
to fail
sporadically with unexpected errors like EWOULDBLOCK when
attempted
during gevent context switch while this function is waiting
in hub.wait.
The converse also fails. I experienced this on MacOS (and
Linux?) very
recently when trying to solve a similar problem. My app
needed to make
gevent-friendly reads from stdin, while logging in my app
was configured
to go to stdout. So, while my gevent-friendly fd-read
function blocked
on the hub waiting for input, other greenlets would execute
and some
would make logging calls, which would sporadically and
unexpectedly
fail exception with OSError with errno=EWOULDBLOCK because
O_NONBLOCK
on stdin also made stdout O_NONBLOCK (stdin and stdout have
different
file descriptors (0 and 1), but share fcntl flags, including
O_NONBLOCK).
FIX: restore fcntl flags in a finally clause for return
os_write(fd, buf)
(instead of the outer finally clause, which is not needed)
+ if not flags & os.O_NONBLOCK:
+ _wrap_errors(fcntl.fcntl, fd, fcntl.F_SETFL, flags)
+
+def posix_write(fd, buf):
+ """Write bytes from buffer `buf` to file descriptor `fd`."""
+ flags = _wrap_errors(fcntl.fcntl, fd, fcntl.F_GETFL, 0)
+ if not flags & os.O_NONBLOCK:
+ _wrap_errors(fcntl.fcntl, fd, fcntl.F_SETFL, flags|
os.O_NONBLOCK)
+ try:
+ while True:
+ try:
+ return os_write(fd, buf)
+ except OSError, e:
+ if e[0] == EBADF:
BUG: This breaks the definition of os.write,
which is defined
as: "Write the string str to file descriptor fd.
Return the
number of bytes actually written."
FIX: Remove the "if e[0] == EBADF: return ''"
block. When
given a bad file descriptor, os.write raises
OSError with errno=EBADF in this case, which is
the
correct behavior (try os.write(999999, "abc")
and see for yourself);
+ return ''
+ elif e[0] != EAGAIN:
+ raise
+ sys.exc_clear()
+ hub = get_hub()
+ event =
hub.loop.io(fd, 2)
+ try:
+ _wrap_errors(hub.wait, event)
+ except OSError, e:
+ if e[0] == EBADF:
BUG: same as above EBADF BUG
FIX: same as above EBADF FIX; the original
OSError
exception with errno=EBADF is the correct
behavior and must
NOT be overridden
+ return ''
+ raise
+ finally:
BUG: The flags are restored too late; there is a serious
problem with
these posix_read and posix_write functions when used with
things like
stdin/stdout file descriptors, or other file descriptors
whose fcntl
flags are linked in a similar way. For example, if you
attempt to use
posix_read() on the stdin.fileno(), fcntl.fcntl(fd,
fcntl.F_SETFL, flags|os.O_NONBLOCK)
also makes stdout non-blocking. This causes writes to stdout
to fail
sporadically with unexpected errors like EWOULDBLOCK when
attempted
during gevent context switch while this function is waiting
in hub.wait.
The converse also fails. I experienced this on MacOS (and
Linux?) very
recently when trying to solve a similar problem. My app
needed to make
gevent-friendly reads from stdin, while logging in my app
was configured
to go to stdout. So, while my gevent-friendly fd-read
function blocked
on the hub waiting for input, other greenlets would execute
and some
would make logging calls, which would sporadically and
unexpectedly
fail exception with OSError with errno=EWOULDBLOCK because
O_NONBLOCK
on stdin also made stdout O_NONBLOCK (stdin and stdout have
different
file descriptors (0 and 1), but share fcntl flags, including
O_NONBLOCK).
FIX: restore fcntl flags in a finally clause for return
os_write(fd, buf)
(instead of the outer finally clause, which is not needed)
+ if not flags & os.O_NONBLOCK:
+ _wrap_errors(fcntl.fcntl, fd, fcntl.F_SETFL, flags)