Skip to content
This repository was archived by the owner on Jun 27, 2018. It is now read-only.

Commit 596d490

Browse files
author
Joshua Reich
committed
checking in patches to asynchat.py
1 parent 12a4a32 commit 596d490

File tree

1 file changed

+311
-0
lines changed

1 file changed

+311
-0
lines changed

pyretic/backend/patch/asynchat.py

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
# partially manual merge of cpython.asyncore_3.patch http://bugs.python.org/issue17925
2+
3+
# -*- Mode: Python; tab-width: 4 -*-
4+
# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
5+
# Author: Sam Rushing <[email protected]>
6+
7+
# ======================================================================
8+
# Copyright 1996 by Sam Rushing
9+
#
10+
# All Rights Reserved
11+
#
12+
# Permission to use, copy, modify, and distribute this software and
13+
# its documentation for any purpose and without fee is hereby
14+
# granted, provided that the above copyright notice appear in all
15+
# copies and that both that copyright notice and this permission
16+
# notice appear in supporting documentation, and that the name of Sam
17+
# Rushing not be used in advertising or publicity pertaining to
18+
# distribution of the software without specific, written prior
19+
# permission.
20+
#
21+
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
22+
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
23+
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
24+
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
25+
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
26+
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
27+
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
28+
# ======================================================================
29+
30+
r"""A class supporting chat-style (command/response) protocols.
31+
32+
This class adds support for 'chat' style protocols - where one side
33+
sends a 'command', and the other sends a response (examples would be
34+
the common internet protocols - smtp, nntp, ftp, etc..).
35+
36+
The handle_read() method looks at the input stream for the current
37+
'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
38+
for multi-line output), calling self.found_terminator() on its
39+
receipt.
40+
41+
for example:
42+
Say you build an async nntp client using this class. At the start
43+
of the connection, you'll have self.terminator set to '\r\n', in
44+
order to process the single-line greeting. Just before issuing a
45+
'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
46+
command will be accumulated (using your own 'collect_incoming_data'
47+
method) up to the terminator, and then control will be returned to
48+
you - by calling your self.found_terminator() method.
49+
"""
50+
51+
import socket
52+
import asyncore
53+
from collections import deque
54+
from sys import py3kwarning
55+
from warnings import filterwarnings, catch_warnings
56+
57+
class async_chat (asyncore.dispatcher):
58+
"""This is an abstract class. You must derive from this class, and add
59+
the two methods collect_incoming_data() and found_terminator()"""
60+
61+
# these are overridable defaults
62+
63+
ac_in_buffer_size = 4096
64+
ac_out_buffer_size = 4096
65+
66+
def __init__ (self, sock=None, map=None):
67+
# for string terminator matching
68+
self.ac_in_buffer = ''
69+
70+
# we use a list here rather than cStringIO for a few reasons...
71+
# del lst[:] is faster than sio.truncate(0)
72+
# lst = [] is faster than sio.truncate(0)
73+
# cStringIO will be gaining unicode support in py3k, which
74+
# will negatively affect the performance of bytes compared to
75+
# a ''.join() equivalent
76+
self.incoming = []
77+
78+
# we toss the use of the "simple producer" and replace it with
79+
# a pure deque, which the original fifo was a wrapping of
80+
self.producer_fifo = deque()
81+
asyncore.dispatcher.__init__ (self, sock, map)
82+
83+
def collect_incoming_data(self, data):
84+
raise NotImplementedError("must be implemented in subclass")
85+
86+
def _collect_incoming_data(self, data):
87+
self.incoming.append(data)
88+
89+
def _get_data(self):
90+
d = ''.join(self.incoming)
91+
del self.incoming[:]
92+
return d
93+
94+
def found_terminator(self):
95+
raise NotImplementedError("must be implemented in subclass")
96+
97+
def set_terminator (self, term):
98+
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
99+
self.terminator = term
100+
101+
def get_terminator (self):
102+
return self.terminator
103+
104+
# grab some more data from the socket,
105+
# throw it to the collector method,
106+
# check for the terminator,
107+
# if found, transition to the next state.
108+
109+
def handle_read (self):
110+
111+
try:
112+
data = self.recv (self.ac_in_buffer_size)
113+
except socket.error, why:
114+
self.handle_error()
115+
return
116+
117+
self.ac_in_buffer = self.ac_in_buffer + data
118+
119+
# Continue to search for self.terminator in self.ac_in_buffer,
120+
# while calling self.collect_incoming_data. The while loop
121+
# is necessary because we might read several data+terminator
122+
# combos with a single recv(4096).
123+
124+
while self.ac_in_buffer:
125+
lb = len(self.ac_in_buffer)
126+
terminator = self.get_terminator()
127+
if not terminator:
128+
# no terminator, collect it all
129+
self.collect_incoming_data (self.ac_in_buffer)
130+
self.ac_in_buffer = ''
131+
elif isinstance(terminator, int) or isinstance(terminator, long):
132+
# numeric terminator
133+
n = terminator
134+
if lb < n:
135+
self.collect_incoming_data (self.ac_in_buffer)
136+
self.ac_in_buffer = ''
137+
self.terminator = self.terminator - lb
138+
else:
139+
self.collect_incoming_data (self.ac_in_buffer[:n])
140+
self.ac_in_buffer = self.ac_in_buffer[n:]
141+
self.terminator = 0
142+
self.found_terminator()
143+
else:
144+
# 3 cases:
145+
# 1) end of buffer matches terminator exactly:
146+
# collect data, transition
147+
# 2) end of buffer matches some prefix:
148+
# collect data to the prefix
149+
# 3) end of buffer does not match any prefix:
150+
# collect data
151+
terminator_len = len(terminator)
152+
index = self.ac_in_buffer.find(terminator)
153+
if index != -1:
154+
# we found the terminator
155+
if index > 0:
156+
# don't bother reporting the empty string (source of subtle bugs)
157+
self.collect_incoming_data (self.ac_in_buffer[:index])
158+
self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
159+
# This does the Right Thing if the terminator is changed here.
160+
self.found_terminator()
161+
else:
162+
# check for a prefix of the terminator
163+
index = find_prefix_at_end (self.ac_in_buffer, terminator)
164+
if index:
165+
if index != lb:
166+
# we found a prefix, collect up to the prefix
167+
self.collect_incoming_data (self.ac_in_buffer[:-index])
168+
self.ac_in_buffer = self.ac_in_buffer[-index:]
169+
break
170+
else:
171+
# no prefix, collect it all
172+
self.collect_incoming_data (self.ac_in_buffer)
173+
self.ac_in_buffer = ''
174+
175+
def handle_write (self):
176+
self.initiate_send()
177+
178+
def handle_close (self):
179+
self.close()
180+
181+
def push (self, data):
182+
sabs = self.ac_out_buffer_size
183+
if len(data) > sabs:
184+
for i in xrange(0, len(data), sabs):
185+
self.producer_fifo.append(data[i:i+sabs])
186+
else:
187+
self.producer_fifo.append(data)
188+
self.initiate_send()
189+
190+
def push_with_producer (self, producer):
191+
self.producer_fifo.append(producer)
192+
self.initiate_send()
193+
194+
def readable (self):
195+
"predicate for inclusion in the readable for select()"
196+
# cannot use the old predicate, it violates the claim of the
197+
# set_terminator method.
198+
199+
# return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
200+
return 1
201+
202+
def writable (self):
203+
"predicate for inclusion in the writable for select()"
204+
return self.producer_fifo or (not self.connected)
205+
206+
def close_when_done (self):
207+
"automatically close this channel once the outgoing queue is empty"
208+
self.producer_fifo.append(None)
209+
210+
def initiate_send(self):
211+
while self.producer_fifo and self.connected:
212+
first = self.producer_fifo.popleft()
213+
# handle empty string/buffer or None entry
214+
if not first:
215+
if first is None:
216+
self.handle_close()
217+
return
218+
219+
# handle classic producer behavior
220+
obs = self.ac_out_buffer_size
221+
try:
222+
with catch_warnings():
223+
if py3kwarning:
224+
filterwarnings("ignore", ".*buffer", DeprecationWarning)
225+
data = buffer(first, 0, obs)
226+
except TypeError:
227+
data = first.more()
228+
if data:
229+
self.producer_fifo.extendleft([data, first])
230+
continue
231+
232+
# send the data
233+
try:
234+
num_sent = self.send(data)
235+
except socket.error:
236+
self.handle_error()
237+
return
238+
239+
if num_sent:
240+
if num_sent < len(data) or obs < len(first):
241+
self.producer_fifo.appendleft(first[num_sent:])
242+
# we tried to send some actual data
243+
return
244+
245+
def discard_buffers (self):
246+
# Emergencies only!
247+
self.ac_in_buffer = ''
248+
del self.incoming[:]
249+
self.producer_fifo.clear()
250+
251+
class simple_producer:
252+
253+
def __init__ (self, data, buffer_size=512):
254+
self.data = data
255+
self.buffer_size = buffer_size
256+
257+
def more (self):
258+
if len (self.data) > self.buffer_size:
259+
result = self.data[:self.buffer_size]
260+
self.data = self.data[self.buffer_size:]
261+
return result
262+
else:
263+
result = self.data
264+
self.data = ''
265+
return result
266+
267+
class fifo:
268+
def __init__ (self, list=None):
269+
if not list:
270+
self.list = deque()
271+
else:
272+
self.list = deque(list)
273+
274+
def __len__ (self):
275+
return len(self.list)
276+
277+
def is_empty (self):
278+
return not self.list
279+
280+
def first (self):
281+
return self.list[0]
282+
283+
def push (self, data):
284+
self.list.append(data)
285+
286+
def pop (self):
287+
if self.list:
288+
return (1, self.list.popleft())
289+
else:
290+
return (0, None)
291+
292+
# Given 'haystack', see if any prefix of 'needle' is at its end. This
293+
# assumes an exact match has already been checked. Return the number of
294+
# characters matched.
295+
# for example:
296+
# f_p_a_e ("qwerty\r", "\r\n") => 1
297+
# f_p_a_e ("qwertydkjf", "\r\n") => 0
298+
# f_p_a_e ("qwerty\r\n", "\r\n") => <undefined>
299+
300+
# this could maybe be made faster with a computed regex?
301+
# [answer: no; circa Python-2.0, Jan 2001]
302+
# new python: 28961/s
303+
# old python: 18307/s
304+
# re: 12820/s
305+
# regex: 14035/s
306+
307+
def find_prefix_at_end (haystack, needle):
308+
l = len(needle) - 1
309+
while l and not haystack.endswith(needle[:l]):
310+
l -= 1
311+
return l

0 commit comments

Comments
 (0)