8
8
#
9
9
# Remaining work:
10
10
#
11
+ # = Test suite that verifies that emulated behaviour is correct.
12
+ # = When closing the socket, pending senders are sent ECONNRESET.
13
+ # This was obtained by opening a server socket, connecting a
14
+ # client and then closing the server. Then the client did a
15
+ # send and got ECONNRESET.
11
16
# = Asyncore does not add that much to this module. In fact, its
12
17
# limitations and differences between implementations in different Python
13
18
# versions just complicate things.
33
38
#
34
39
35
40
import stackless
36
- import asyncore , weakref , time , select , types
41
+ import asyncore , weakref , time , select , types , sys , gc
37
42
from collections import deque
38
43
39
44
# If you pump the scheduler and wish to prevent the scheduler from staying
57
62
import socket as stdsocket # We need the "socket" name for the function we export.
58
63
try :
59
64
from errno import EALREADY , EINPROGRESS , EWOULDBLOCK , ECONNRESET , \
60
- ENOTCONN , ESHUTDOWN , EINTR , EISCONN , EBADF , ECONNABORTED
65
+ ENOTCONN , ESHUTDOWN , EINTR , EISCONN , EBADF , ECONNABORTED , \
66
+ ECONNREFUSED
61
67
except Exception :
62
68
# Fallback on hard-coded PS3 constants.
63
69
EALREADY = 37
70
76
EISCONN = 56
71
77
EBADF = 9
72
78
ECONNABORTED = 53
79
+ ECONNREFUSED = 61
73
80
74
81
# If we are to masquerade as the socket module, we need to provide the constants.
75
82
if "__all__" in stdsocket .__dict__ :
@@ -170,6 +177,17 @@ def accept(self):
170
177
171
178
accept .__doc__ = _socketobject_old .accept .__doc__
172
179
180
+ def make_blocking_socket (family = AF_INET , type = SOCK_STREAM , proto = 0 ):
181
+ """
182
+ Sometimes you may want to create a normal Python socket, even when
183
+ monkey-patching is in effect. One use case might be when you are trying to
184
+ do socket operations on the last runnable tasklet, if these socket
185
+ operations are on small writes on a non-connected UDP socket then you
186
+ might as well just use a blocking socket, as the effect of blocking
187
+ is negligible.
188
+ """
189
+ _sock = _realsocket_old (family , type , proto )
190
+ return _socketobject_old (_sock = _sock )
173
191
174
192
175
193
def install (pi = None ):
@@ -185,6 +203,65 @@ def uninstall():
185
203
stdsocket ._realsocket = _realsocket_old
186
204
stdsocket .socket = stdsocket .SocketType = stdsocket ._socketobject = _socketobject_old
187
205
206
+ READY_TO_SCHEDULE_TAG = "_SET_ASIDE"
207
+
208
+ def ready_to_schedule (flag ):
209
+ """
210
+ There may be cases where it is desirable to have socket operations happen before
211
+ an application starts up its framework, which would then poll asyncore. This
212
+ function is intended to allow all sockets to be switched between working
213
+ "stacklessly" or working directly on their underlying socket objects in a
214
+ blocking manner.
215
+
216
+ Note that sockets created while this is in effect lack attribute values that
217
+ asyncore or this module may have set if the sockets were created in a full
218
+ monkey patched manner.
219
+ """
220
+
221
+ def reroute_wrapper (funcName ):
222
+ def reroute_call (self , * args , ** kwargs ):
223
+ if READY_TO_SCHEDULE_TAG not in _fakesocket .__dict__ :
224
+ return
225
+ return getattr (self .socket , funcName )(* args , ** kwargs )
226
+ return reroute_call
227
+
228
+ def update_method_referrers (methodName , oldClassMethod , newClassMethod ):
229
+ """
230
+ The instance methods we need to update are stored in slots on instances of
231
+ socket._socketobject (actually our replacement subclass _socketobject_new).
232
+ """
233
+ for referrer1 in gc .get_referrers (oldClassMethod ):
234
+ if isinstance (referrer1 , types .MethodType ):
235
+ for referrer2 in gc .get_referrers (referrer1 ):
236
+ if isinstance (referrer2 , _socketobject_new ):
237
+ setattr (referrer2 , methodName , types .MethodType (newClassMethod , referrer1 .im_self , referrer1 .im_class ))
238
+
239
+ # Guard against removal if not in place.
240
+ if flag :
241
+ if READY_TO_SCHEDULE_TAG not in _fakesocket .__dict__ :
242
+ return
243
+ del _fakesocket .__dict__ [READY_TO_SCHEDULE_TAG ]
244
+ else :
245
+ _fakesocket .__dict__ [READY_TO_SCHEDULE_TAG ] = None
246
+ # sys.__stdout__.write("READY_TO_SCHEDULE %s\n" % flag)
247
+
248
+ # Play switcheroo with the attributes to get direct socket usage, or normal socket usage.
249
+ for attributeName in dir (_realsocket_old ):
250
+ if not attributeName .startswith ("_" ):
251
+ storageAttributeName = attributeName + "_SET_ASIDE"
252
+ if flag :
253
+ storedValue = _fakesocket .__dict__ .pop (storageAttributeName , None )
254
+ if storedValue is not None :
255
+ rerouteValue = _fakesocket .__dict__ [attributeName ]
256
+ # sys.__stdout__.write("___ RESTORING %s (AS %s) (WAS %s)\n" % (attributeName, storedValue, rerouteValue))
257
+ _fakesocket .__dict__ [attributeName ] = storedValue
258
+ update_method_referrers (attributeName , rerouteValue , storedValue )
259
+ else :
260
+ if attributeName in _fakesocket .__dict__ :
261
+ # sys.__stdout__.write("___ STORING %s = %s\n" % (attributeName, _fakesocket.__dict__[attributeName]))
262
+ _fakesocket .__dict__ [storageAttributeName ] = _fakesocket .__dict__ [attributeName ]
263
+ _fakesocket .__dict__ [attributeName ] = reroute_wrapper (attributeName )
264
+
188
265
189
266
class _fakesocket (asyncore .dispatcher ):
190
267
connectChannel = None
@@ -434,23 +511,23 @@ def close(self):
434
511
435
512
self .connected = False
436
513
self .accepting = False
437
- self .writeQueue = []
438
514
439
515
# Clear out all the channels with relevant errors.
440
516
while self .acceptChannel and self .acceptChannel .balance < 0 :
441
- self .acceptChannel .send_exception (error , 9 , 'Bad file descriptor' )
517
+ self .acceptChannel .send_exception (stdsocket . error , EBADF , 'Bad file descriptor' )
442
518
while self .connectChannel and self .connectChannel .balance < 0 :
443
- self .connectChannel .send_exception (error , 10061 , 'Connection refused' )
444
- self ._clear_read_queue ()
519
+ self .connectChannel .send_exception (stdsocket .error , ECONNREFUSED , 'Connection refused' )
520
+ self ._clear_queue (self .writeQueue , stdsocket .error , ECONNRESET )
521
+ self ._clear_queue (self .readQueue )
445
522
446
- def _clear_read_queue (self , * args ):
447
- for t in self . readQueue :
523
+ def _clear_queue (self , queue , * args ):
524
+ for t in queue :
448
525
if t [0 ].balance < 0 :
449
526
if len (args ):
450
527
t [0 ].send_exception (* args )
451
528
else :
452
529
t [0 ].send ("" )
453
- self . readQueue = []
530
+ queue . clear ()
454
531
455
532
# asyncore doesn't support this. Why not?
456
533
def fileno (self ):
@@ -503,6 +580,11 @@ def handle_connect(self):
503
580
# Asyncore says its done but self.readBuffer may be non-empty
504
581
# so can't close yet. Do nothing and let 'recv' trigger the close.
505
582
def handle_close (self ):
583
+ # These do not interfere with ongoing reads, but should prevent
584
+ # sends and the like from going through.
585
+ self .connected = False
586
+ self .accepting = False
587
+
506
588
# This also gets called in the case that a non-blocking connect gets
507
589
# back to us with a no. If we don't reject the connect, then all
508
590
# connect calls that do not connect will block indefinitely.
@@ -595,10 +677,12 @@ def asyncore_send(self, data, flags=0):
595
677
try :
596
678
result = self .socket .send (data , flags )
597
679
return result
598
- except socket .error , why :
680
+ except stdsocket .error , why :
599
681
if why .args [0 ] == EWOULDBLOCK :
600
682
return 0
601
683
elif why .args [0 ] in (ECONNRESET , ENOTCONN , ESHUTDOWN , ECONNABORTED ):
684
+ # Ensure the sender appears to have directly received this exception.
685
+ channel .send_exception (why .__class__ , * why .args )
602
686
self .handle_close ()
603
687
return 0
604
688
else :
@@ -767,6 +851,10 @@ def UDPClient(address):
767
851
finally :
768
852
uninstall ()
769
853
854
+ if "notready" in sys .argv :
855
+ sys .argv .remove ("notready" )
856
+ ready_to_schedule (False )
857
+
770
858
if len (sys .argv ) == 2 :
771
859
if sys .argv [1 ] == "client" :
772
860
print "client started"
0 commit comments