classification
Title: asyncore incorrect failure when connection is refused and using async_chat channel
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.1, Python 3.2, Python 2.7, Python 2.6
process
Status: closed Resolution: out of date
Dependencies: Superseder:
Assigned To: josiahcarlson Nosy List: BreamoreBoy, giampaolo.rodola, josiahcarlson, nirs, vstinner
Priority: normal Keywords: needs review, patch

Created on 2009-07-22 22:42 by nirs, last changed 2014-08-27 16:47 by vstinner. This issue is now closed.

Files
File name Uploaded Description Edit
asyncore-handle-connect-event.patch nirs, 2009-07-22 22:42 Tests and fix.
asycore-handle-connect-event-2.patch nirs, 2009-07-24 21:53 Improve fix
asycore-handle-connect-event-3.patch nirs, 2009-07-24 22:15 Fix for Windows
asyncore_fix_refused-3.patch josiahcarlson, 2009-07-29 18:19 should handle everything
asyncore-fix-refused-4.patch nirs, 2009-08-01 12:09 Fix issues in -3.patch
Messages (19)
msg90830 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-22 22:42
When using asynchat.async_chat channel, and connection is refused, 
asyncore fail incorrectly.

First, instead of ECONNREFUSED, you get EPIPE, second, you also get a 
EBADF exception, and finally, the channel handle_close is called  twice.

The problem is the way asyncore detect a connect event. On the first 
read event, asyncore set the connected flag to true, and invoke the 
channel handle_connect method. Typically, the channel will try to push 
something to the remote endpoint at this point. Since "connected" is 
true, push() will try to send to the socket, and fail with EPIPE, 
because the socket is not connected.

The socket error in push will invoke the channel handle_error, which 
typically will call handle_close to close the socket. 
handle_connect_event will return without any error, because the error 
was handled inside push.

Now asyncore will invoke the channel handle_read method, which fails 
with bad file descriptor, since the channel closed the socket on the 
previous error.

How should this work correctly?

1. We want to get a connection refused error in this case
2. The failure should cause only single exception
3. The channel should be close once

I added these test to test_asyncore.py, and all of them fail with 
current code.

Turns out that single line change fix all the failing tests: set the 
"connected" state after the call to handle_connect, just like it used to 
be in 2.5.

With this change, when the channel try to push something to the remote 
endpoint, it will keep it in the fifo, since connected is false. This is 
also correct, since we will know that we are connected only after 
handle_read is called. In handle_read, we fail with ECONNREFUSED and 
close the channel.

The fix is tested currently only on Mac OS X 10.5. I have seen this 
issue also on Ubuntu 9.04.
msg90853 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-23 18:40
The patch is tested with release26-maint and trunk.
msg90895 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-24 20:28
Tested on Ubuntu Linux 9.04.

The tests will probably fail on Windows, since connection refused is
detected trough handle_expt_event, and not in hadnle_read_event. I hope
someone on Windows will fix this :-)
msg90901 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-24 21:53
The first fix reverted to 2.5 behavior, when self.connected is false when 
handle_connect is called. This behavior is little stupid - why call 
handle_connect if the socket is not really connected?

This fix ensure that handle_connect is called only if the socket is 
connected - just like it is done in handle_write_event. Now it does not 
matter when you set the connected flag, before or after handle_connect.

Tested in trunk on Mac OS X.
msg90903 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-24 22:15
This version fix also handle_expt_event, so connection refused error 
should be handled in the same way also on Windows.
msg90994 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2009-07-27 19:09
The attached patch cleans up the remnants of the "handle_expt is for 
exceptions", which isn't the case, as well as makes the "connection 
refused" fix actually work on Windows.  Nirs, could you verify this on 
*nix?
msg91014 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-28 19:21
I have a big problem with asyncore_fix_refused.patch  - it assumes that a 
dispatcher has a socket attribute, which can be used with t getsockopt(). 
This is true in the default dispatcher class implemented in asyncore, but 
wont work with file_dispatcher, or 3rd party dispatcher class.

The framework should let you drop in your dispatcher class, that will 
implement a minimal interface - handle_x_event, writable, readable etc.

What are the issues on Windows with asyncore-handle-connect-event-3.patch 
on Windows?
msg91016 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2009-07-28 20:23
Firstly, it expects that handle_expt_event() is for handling exceptional 
conditions.  This is not the case.  handle_expt_event() is meant for 
handling "OOB" or "priority" data coming across a socket.  FTP and some 
other protocols use this.  I forgot to fix it earlier, which is why it's 
making it into this patch.

Secondly, I pulled the part that was inside handle_expt_event() that was 
being used to find the exception and pulls it out into _exception(), 
removing the previous behavior (wrt to the broken API), and replacing it 
with something that is cleaner and more correct (assuming sockets).

To respond to it being an issue that the object has a socket with a 
getsockopt(), I can fix it to handle the AttributeError case.

Would you be willing to try this out given my explanation as to why I 
changed your patch?
msg91037 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-29 11:59
I'll check the patch this week.

The asyncore framework has low level events - handle_read_event, handle_write_event and 
handle_expt_event - these events are not used for reading, writing and OOB - they are just 
responsible to call the high level events.

The high level events - handle_connect, handle_accept, handle_read, handle_write, 
handle_close and handle_expt should be used only for specific events.

I don't see any problem in checking for errors in handle_expt_event, it works just like 
handle_read_event, that calls handle_connect.

This design allow you do replace the dispatcher with your own dispatcher class, 
implementing only the low level events.
msg91058 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2009-07-29 18:19
Originally, handle_expt_event() was described as "handles OOB data or 
exceptions", but over-using handle_expt_event() as an error/close 
handler is a bad idea.  The function asyncore.readwrite() (called by 
asyncore.poll2()) does the right thing WRT handle_expt_event(), which it 
makes sense to apply to the standard select-based asyncore.poll().  
That's what this does (in addition to fixing the close case that you 
pointed out).

In terms of "only implementing low-level stuff", this is still the case.  
You still only need to implement handle_*(), not handle_*_event() .  But 
now, handle_expt_event() isn't written to do more than it should have 
been doing in the first place.

I've updated the patch to include semantics for actually handling OOB 
data, which I've verified by using a slightly modified pyftpdlib (remove 
the socket option calls to set socket.SO_OOBINLINE) and it's tests on 
both Windows and Ubuntu 8.04 (I also ran the full Python test suite on 
my Ubuntu install, and any failures were obviously not asyncore/asynchat 
related).
msg91155 - (view) Author: Nir Soffer (nirs) * Date: 2009-07-31 23:21
I tested asyncore_fix_refused-3.patch on Mac OS X 10.5 - all asyncore 
and asynchat tests pass.

There is one minor issue - _exception calls the non existing 
handle_close_event instead of handle_close.

However, looking again at the code I think that it is ugly and wrong - 
handle_expt_event should handle the low level expt event called from 
select, allowing third party dispatcher to override the behavior as 
needed.

Another issue - lately, a new event was added - handle_connect_event - 
this is wrong! there is no such low level event. handle_connect is a 
high level event, implied by first read or write on the connecting 
socket. This event will break 3rd party dispatcher that does not 
implement it, and is not documented.
msg91159 - (view) Author: Nir Soffer (nirs) * Date: 2009-08-01 11:51
I was wrong about handle_connect_event - it is called only from the 
dispatcher, so it will not break 3rd party dispatcher.
msg91160 - (view) Author: Nir Soffer (nirs) * Date: 2009-08-01 12:09
This is asyncore-fix-refused-3.patch with some fixes:

 1. Call handle_close instead of non exiting handle_close_event
 2. Remove unneeded handle_close_event in test classes
 3. Revert removal of handle_expt_event in test classes - not clear why it 
was removed in the previous patch.

Tested on Mac OS X 10.5.
msg91165 - (view) Author: Josiah Carlson (josiahcarlson) * (Python triager) Date: 2009-08-01 17:49
handle_expt_event was removed in the test classes because it is no 
longer being used by any of the tests.  None of them send OOB data (also 
known as priority data), so handle_expt_event should never be called.  
When I have a chance to compare your patch to mine (Monday, likely), 
I'll comment then.

In terms of "handle_expt_event should handle the low level expt event 
called from select", what you don't seem understand is that and "expt 
event" is not an exception event, it's a "we got OOB data" event, and 
with the patches, it is called at *exactly* the time it should be: when 
there is OOB data.  It used to be mistakenly called in the select loop 
whenever any sort of non-normal condition happened on the socket, which 
was a serious design flaw, and lead to significant misunderstanding 
about the purpose of the method.

With the _exception() call as-is, it now behaves like the 
asyncore.poll2() function, which is the right thing.
msg91221 - (view) Author: Nir Soffer (nirs) * Date: 2009-08-03 12:19
handle_expt is documented to be called when there is OOB data. However,
handle_expt_event is not documented, and according the framework design
as I see it, it simply means "socket has exceptional condition" when
select returns. On unix, this means there is oob data, and on Windows,
it means "there is some error".

This works exactly the same for handle_read_event and handle_write_event
- they may be called on connection refused error. Checking for errors in
handle_expt_event is the right thing to do, and allow you to avoid the
ugly checks and double try..except in _exception.

If you want handle_foo_event to be called only on foo events, then they
will not have anything to do except calling handle_foo. This is actually
the case now in handle_expt_event. I don't see any advantage of this
design change.
msg113429 - (view) Author: Giampaolo Rodola' (giampaolo.rodola) * (Python committer) Date: 2010-08-09 17:15
What's the current status of this issue?
I think some of the problems raised at the time this was opened should have been fixed in meantime, like checking for errors in handle_expt_event and handle_connect_event (issue 2944).

The handle_close being called twice problem is still there though, as shown in issue 4690.

Not sure what other problems remain.
msg219981 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-06-07 21:30
Has maintenance of asyncore effectively ceased owing to tulip/asyncio or are outstanding problems here, if any, still considered valid?
msg221760 - (view) Author: Mark Lawrence (BreamoreBoy) * Date: 2014-06-28 01:38
@Victor as you've been looking at other asyncore/chat issues can you look at this please?
msg226000 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2014-08-27 16:47
The latest patch was written 5 years ago, it's probably a huge work to rebase it. Giampaolo wrote that the code changed a lot since this time (especially code handling errors).

asyncore-fix-refused-4.patch catchs EBADF. To me, it looks like a bug: if you get a EBADF error, we already made something wrong before.

The patch doesn't call obj.handle_expt_event() anymore in _exception() if obj is not a socket. It don't understand why. I don't understand neither why _exception() now raises a new socket error instead of calling obj.handle_expt_event().

All these changes are non trivial. I don't feel able to review the patch.

Sorry, I'm not interested to invest time on this deprecated module. It's time to move forward, to asyncio, and so I close this issue.
History
Date User Action Args
2014-08-27 16:47:31vstinnersetstatus: open -> closed
resolution: out of date
messages: + msg226000
2014-06-28 01:38:15BreamoreBoysetnosy: + vstinner
messages: + msg221760
2014-06-07 21:30:53BreamoreBoysetnosy: + BreamoreBoy
messages: + msg219981
2010-08-09 17:17:42giampaolo.rodolasetversions: + Python 3.1, Python 3.2
2010-08-09 17:15:17giampaolo.rodolasetmessages: + msg113429
2009-08-03 12:19:54nirssetmessages: + msg91221
2009-08-01 17:49:21josiahcarlsonsetmessages: + msg91165
2009-08-01 12:09:53nirssetfiles: + asyncore-fix-refused-4.patch

messages: + msg91160
2009-08-01 11:51:21nirssetmessages: + msg91159
2009-07-31 23:21:05nirssetmessages: + msg91155
2009-07-31 07:41:02giampaolo.rodolasetnosy: + giampaolo.rodola
2009-07-29 18:19:35josiahcarlsonsetfiles: - asyncore_fix_refused-2.patch
2009-07-29 18:19:31josiahcarlsonsetfiles: - asyncore_fix_refused.patch
2009-07-29 18:19:10josiahcarlsonsetfiles: + asyncore_fix_refused-3.patch

messages: + msg91058
2009-07-29 11:59:11nirssetmessages: + msg91037
2009-07-28 20:23:33josiahcarlsonsetfiles: + asyncore_fix_refused-2.patch

messages: + msg91016
2009-07-28 19:21:05nirssetmessages: + msg91014
2009-07-27 19:09:18josiahcarlsonsetkeywords: + needs review
assignee: josiahcarlson
messages: + msg90994

files: + asyncore_fix_refused.patch
2009-07-27 01:01:14r.david.murraysetpriority: normal
nosy: + josiahcarlson
2009-07-24 22:15:06nirssetfiles: + asycore-handle-connect-event-3.patch

messages: + msg90903
2009-07-24 21:53:41nirssetfiles: + asycore-handle-connect-event-2.patch

messages: + msg90901
2009-07-24 20:28:18nirssetmessages: + msg90895
2009-07-23 18:40:52nirssetmessages: + msg90853
versions: + Python 2.7
2009-07-22 22:42:49nirscreate