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

asyncore.dispatcher.recv doesn't handle EAGAIN / EWOULDBLOCK #60337

Closed
Nidan mannequin opened this issue Oct 4, 2012 · 14 comments
Closed

asyncore.dispatcher.recv doesn't handle EAGAIN / EWOULDBLOCK #60337

Nidan mannequin opened this issue Oct 4, 2012 · 14 comments
Assignees
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@Nidan
Copy link
Mannequin

Nidan mannequin commented Oct 4, 2012

BPO 16133
Nosy @josiahcarlson, @vstinner, @giampaolo, @xdegaye
Files
  • issue16133.patch
  • issue16133.patch
  • EWOULDBLOCK.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/giampaolo'
    closed_at = <Date 2014-07-24.17:17:40.812>
    created_at = <Date 2012-10-04.16:06:32.401>
    labels = ['type-bug', 'library']
    title = "asyncore.dispatcher.recv doesn't handle EAGAIN / EWOULDBLOCK"
    updated_at = <Date 2014-07-24.17:17:40.811>
    user = 'https://bugs.python.org/Nidan'

    bugs.python.org fields:

    activity = <Date 2014-07-24.17:17:40.811>
    actor = 'vstinner'
    assignee = 'giampaolo.rodola'
    closed = True
    closed_date = <Date 2014-07-24.17:17:40.812>
    closer = 'vstinner'
    components = ['Library (Lib)']
    creation = <Date 2012-10-04.16:06:32.401>
    creator = 'Nidan'
    dependencies = []
    files = ['30026', '30088', '30193']
    hgrepos = []
    issue_num = 16133
    keywords = ['patch']
    message_count = 14.0
    messages = ['171967', '187846', '187848', '188206', '188248', '188817', '188821', '220629', '221728', '221732', '221733', '223859', '223861', '223862']
    nosy_count = 8.0
    nosy_names = ['josiahcarlson', 'vstinner', 'giampaolo.rodola', 'stutzbach', 'BreamoreBoy', 'xdegaye', 'python-dev', 'Nidan']
    pr_nums = []
    priority = 'normal'
    resolution = 'fixed'
    stage = None
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue16133'
    versions = ['Python 2.7', 'Python 3.3', 'Python 3.4']

    @Nidan
    Copy link
    Mannequin Author

    Nidan mannequin commented Oct 4, 2012

    I recently had lots of the following exception:
    error: uncaptured python exception, closing channel <servercore_persistent.ConnectionHandler connected 127.0.0.1:53609 at 0x8d27eec> (<class 'socket.error'>:[Errno 11] Resource temporarily unavailable [/usr/lib/python2.7/asynchat.py|handle_read|110] [/usr/lib/python2.7/asyncore.py|recv|384])

    Error 11 is EAGAIN or EWOULDBLOCK, so asyncore/asynchat tries to read from a nonblocking socket which has no data available. Since this is a temporary error the socket shouldn't be closed.

    The bug can be fixed by changing asyncore.dispatcher.recv to
    def recv(self, buffer_size):
    try:
    data = self.socket.recv(buffer_size)
    if not data:
    # a closed connection is indicated by signaling
    # a read condition, and having recv() return 0.
    self.handle_close()
    return ''
    else:
    return data
    except socket.error, why:
    # winsock sometimes throws ENOTCONN
    if why.args[0] in _DISCONNECTED:
    self.handle_close()
    return ''
    elif why.args[0] in (EAGAIN, EWOULDBLOCK):
    return ''
    else:
    raise

    While looking at the source I also saw that asyncore.dispatcher.send and .connect check against EWOULDBLOCK but not against EAGAIN. Since both constants may have different values and POSIX allows to return either of them these functions should check against both error constants.

    @Nidan Nidan mannequin added stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Oct 4, 2012
    @giampaolo
    Copy link
    Contributor

    I confirm I can reproduce this issue also in pyftpdlib:
    https://code.google.com/p/pyftpdlib/issues/detail?id=255
    Current proposed patch returning '' is not ideal as for asynchat '' is an alias for 'connection lost'.
    In summary recv() in case of EAGAIN should return None and asynchat.handle_read() should take that into account.
    Will provide a patch later.

    @giampaolo giampaolo self-assigned this Apr 26, 2013
    @giampaolo
    Copy link
    Contributor

    Patch is in attachment.
    I'm a bit worried about Python versions < 3.4 because this kinds of breaks backward compatibility as recv() is not expected to return None but I see no other saner solution.

    @Nidan
    Copy link
    Mannequin Author

    Nidan mannequin commented May 1, 2013

    Why should asynchat.handle_read care about closed sockets if asyncore.recv does that already?
    Currently asynchat.handle_read handles empty strings from asycore.recv gracefully (by doing some unnecessary work aka executing the remainder of the function), it doesn't treat them specially. The only path that might cause asynchat.handle_read to close the socket requires asycore.recv to throw.
    Introducing None as possible return value from asyncore.recv therefore seems unnecessary to me.

    Changed the patch accordingly.

    @giampaolo
    Copy link
    Contributor

    recv() returning an empty string has always been an alias for "connection lost" though, that is why it cannot be used and I was proposing returning a new type in Python 3.4.

    Point is we're paying a bad design decision: asyncore shouldn't have asked the user to call recv() directly in the first place and call a data_received(chunk) callback method instead.

    Deciding what's best to do at this point without breaking existent code is not easy, that is why I think that on python <= 3.3 we should fix *asynchat* in order to take EAGAIN/EWOULDBLOCK into account and leave asyncore's recv() alone.
    The issue would still exist but it would be mitigated by the fact that who wants to write a protocol is likely to use asynchat, not asyncore.

    As for Python 3.4 we can:

    • make asyncore's recv() return None and document it
    • deprecate recv()
    • introduce data_received(chunk)

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented May 10, 2013

    For the reson why read() must still check for EWOULDBLOCK even though
    after select() has reported a file descriptor ready for reading, see
    the BUGS section of select linux man page, which says:

       Under Linux, select() may report a socket file descriptor as
       "ready for reading", while nevertheless a subsequent read
       blocks.  This could for  example  happen  when data has arrived
       but upon examination has wrong checksum and is discarded.
       There may be other circumstances in which a file descriptor is
       spuriously reported as ready.  Thus it may be safer to use
       O_NONBLOCK on sockets that should not block.
    

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented May 10, 2013

    Deciding what's best to do at this point without breaking existent
    code is not easy, that is why I think that on python <= 3.3 we
    should fix *asynchat* in order to take EAGAIN/EWOULDBLOCK into
    account and leave asyncore's recv() alone.

    IMHO for all python versions, asynchat should be fixed and recv() left
    unchanged raising OSError with EAGAIN/EWOULDBLOCK. With the proposed
    change on recv(), asyncore users would need to handle this new
    None returned value anyway, instead of handling the exception which is
    much more explicit.

    The attached patch does this on the default branch.

    @BreamoreBoy
    Copy link
    Mannequin

    BreamoreBoy mannequin commented Jun 15, 2014

    Could we have a review of the latest path from Xavier please.

    Aside, msg172160 "add Windows project file for _sha3 module. I choose to build _sha3 as a sparat module as it's rather large (190k for AMD64).", presumably Christian mistyped an issue number?

    @vstinner
    Copy link
    Member

    EWOULDBLOCK.patch: asyncio ignores BlockingIOError on sock.recv(), "except BlockingIOError:" is more portable and future proof than "_RETRY = frozenset((EWOULDBLOCK, EAGAIN))".

    Except of that, EWOULDBLOCK.patch change looks correct.

    @vstinner
    Copy link
    Member

    Modifying recv() to return None doesn't look correct. I read it as: "you should always use recv() output, except if the result is None: in this case, do nothing". In Python, we use exceptions for that. BUT in fact, sock.recv() already raises an exception, so asyncore should not convert the exception to a magic value (None).

    Modifying the behaviour of recv() in asyncore breaks the backward compatibility. Returning None makes it harder to write asyncore code working on Python 3.4 and 3.5 (if the change is done in Python 3.5).

    I prefer EWOULDBLOCK.patch approach: document the issue in asyncore documentation and handle BlockingIOError in asynchat.

    @vstinner
    Copy link
    Member

    See also the issue bpo-15982 which is the exactly the same on Windows.

    (By the way, the asyncore module has been marked as deprecated in Python 3.4 in favor of asyncio, and this issue is already solved in asyncio.)

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Jul 24, 2014

    New changeset b7f144d14798 by Victor Stinner in branch '3.4':
    Issue bpo-16133: The asynchat.async_chat.handle_read() method now ignores
    http://hg.python.org/cpython/rev/b7f144d14798

    New changeset aa150c7a5d24 by Victor Stinner in branch 'default':
    (Merge 3.4) Issue bpo-16133: The asynchat.async_chat.handle_read() method now
    http://hg.python.org/cpython/rev/aa150c7a5d24

    @python-dev
    Copy link
    Mannequin

    python-dev mannequin commented Jul 24, 2014

    New changeset d422062d7d36 by Victor Stinner in branch '2.7':
    Issue bpo-16133: The asynchat.async_chat.handle_read() method now ignores
    http://hg.python.org/cpython/rev/d422062d7d36

    @vstinner
    Copy link
    Member

    I modified EWOULDBLOCK.patch to use BlockingIOError on Python 3 and I added a unit test. I also added EALREADY and EINPROGRESS which are used by the BlockingIOError in Python 3, just in case.

    Thanks Xavier for your patch, sorry for the delay.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    2 participants