Skip to content

Commit d74900e

Browse files
committed
Committing Py3k version of changelist 64080 and 64257, along with updated tests
for smtpd, which required updating with the new semantics.
1 parent d51ee54 commit d74900e

8 files changed

Lines changed: 268 additions & 133 deletions

File tree

Doc/library/asynchat.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ connection requests.
8181
:exc:`NotImplementedError` exception.
8282

8383

84+
.. method:: async_chat._collect_incoming_data(data)
85+
86+
Sample implementation of a data collection rutine to be used in conjunction
87+
with :meth:`_get_data` in a user-specified :meth:`found_terminator`.
88+
89+
8490
.. method:: async_chat.discard_buffers()
8591

8692
In emergencies this method will discard any data held in the input and/or
@@ -95,6 +101,12 @@ connection requests.
95101
should be available via an instance attribute.
96102

97103

104+
.. method:: async_chat._get_data()
105+
106+
Will return and clear the data received with the sample
107+
:meth:`_collect_incoming_data` implementation.
108+
109+
98110
.. method:: async_chat.get_terminator()
99111

100112
Returns the current terminator for the channel.

Doc/library/asyncore.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,21 @@ any that have been added to the map during asynchronous service) is closed.
222222
flushed). Sockets are automatically closed when they are
223223
garbage-collected.
224224

225+
.. class:: file_dispatcher()
226+
227+
A file_dispatcher takes a file descriptor or file object along with an
228+
optional map argument and wraps it for use with the :cfunc:`poll` or
229+
:cfunc:`loop` functions. If provided a file object or anything with a
230+
:cfunc:`fileno` method, that method will be called and passed to the
231+
:class:`file_wrapper` constructor. Availability: UNIX.
232+
233+
.. class:: file_wrapper()
234+
235+
A file_wrapper takes an integer file descriptor and calls :func:`os.dup` to
236+
duplicate the handle so that the original handle may be closed independently
237+
of the file_wrapper. This class implements sufficient methods to emulate a
238+
socket for use by the :class:`file_dispatcher` class. Availability: UNIX.
239+
225240

226241
.. _asyncore-example:
227242

Lib/asynchat.py

Lines changed: 98 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,23 @@
4545
method) up to the terminator, and then control will be returned to
4646
you - by calling your self.found_terminator() method.
4747
"""
48-
49-
import sys
5048
import socket
5149
import asyncore
5250
from collections import deque
5351

52+
def buffer(obj, start=None, stop=None):
53+
# if memoryview objects gain slicing semantics,
54+
# this function will change for the better
55+
# memoryview used for the TypeError
56+
memoryview(obj)
57+
if start == None:
58+
start = 0
59+
if stop == None:
60+
stop = len(obj)
61+
x = obj[start:stop]
62+
## print("buffer type is: %s"%(type(x),))
63+
return x
64+
5465
class async_chat (asyncore.dispatcher):
5566
"""This is an abstract class. You must derive from this class, and add
5667
the two methods collect_incoming_data() and found_terminator()"""
@@ -60,20 +71,47 @@ class async_chat (asyncore.dispatcher):
6071
ac_in_buffer_size = 4096
6172
ac_out_buffer_size = 4096
6273

74+
# we don't want to enable the use of encoding by default, because that is a
75+
# sign of an application bug that we don't want to pass silently
76+
77+
use_encoding = 0
78+
encoding = 'latin1'
79+
6380
def __init__ (self, conn=None):
81+
# for string terminator matching
6482
self.ac_in_buffer = b''
65-
self.ac_out_buffer = b''
66-
self.producer_fifo = fifo()
83+
84+
# we use a list here rather than cStringIO for a few reasons...
85+
# del lst[:] is faster than sio.truncate(0)
86+
# lst = [] is faster than sio.truncate(0)
87+
# cStringIO will be gaining unicode support in py3k, which
88+
# will negatively affect the performance of bytes compared to
89+
# a ''.join() equivalent
90+
self.incoming = []
91+
92+
# we toss the use of the "simple producer" and replace it with
93+
# a pure deque, which the original fifo was a wrapping of
94+
self.producer_fifo = deque()
6795
asyncore.dispatcher.__init__ (self, conn)
6896

6997
def collect_incoming_data(self, data):
7098
raise NotImplementedError("must be implemented in subclass")
7199

100+
def _collect_incoming_data(self, data):
101+
self.incoming.append(data)
102+
103+
def _get_data(self):
104+
d = b''.join(self.incoming)
105+
del self.incoming[:]
106+
return d
107+
72108
def found_terminator(self):
73109
raise NotImplementedError("must be implemented in subclass")
74110

75111
def set_terminator (self, term):
76112
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
113+
if isinstance(term, str) and self.use_encoding:
114+
term = bytes(term, self.encoding)
77115
self.terminator = term
78116

79117
def get_terminator (self):
@@ -92,14 +130,14 @@ def handle_read (self):
92130
self.handle_error()
93131
return
94132

95-
if isinstance(data, str):
96-
data = data.encode('ascii')
97-
self.ac_in_buffer = self.ac_in_buffer + bytes(data)
133+
if isinstance(data, str) and self.use_encoding:
134+
data = bytes(str, self.encoding)
135+
self.ac_in_buffer = self.ac_in_buffer + data
98136

99137
# Continue to search for self.terminator in self.ac_in_buffer,
100138
# while calling self.collect_incoming_data. The while loop
101139
# is necessary because we might read several data+terminator
102-
# combos with a single recv(1024).
140+
# combos with a single recv(4096).
103141

104142
while self.ac_in_buffer:
105143
lb = len(self.ac_in_buffer)
@@ -108,7 +146,7 @@ def handle_read (self):
108146
# no terminator, collect it all
109147
self.collect_incoming_data (self.ac_in_buffer)
110148
self.ac_in_buffer = b''
111-
elif isinstance(terminator, int) or isinstance(terminator, int):
149+
elif isinstance(terminator, int):
112150
# numeric terminator
113151
n = terminator
114152
if lb < n:
@@ -129,8 +167,6 @@ def handle_read (self):
129167
# 3) end of buffer does not match any prefix:
130168
# collect data
131169
terminator_len = len(terminator)
132-
if isinstance(terminator, str):
133-
terminator = terminator.encode('ascii')
134170
index = self.ac_in_buffer.find(terminator)
135171
if index != -1:
136172
# we found the terminator
@@ -155,91 +191,87 @@ def handle_read (self):
155191
self.ac_in_buffer = b''
156192

157193
def handle_write (self):
158-
self.initiate_send ()
194+
self.initiate_send()
159195

160196
def handle_close (self):
161197
self.close()
162198

163199
def push (self, data):
164-
self.producer_fifo.push (simple_producer (data))
200+
sabs = self.ac_out_buffer_size
201+
if len(data) > sabs:
202+
for i in range(0, len(data), sabs):
203+
self.producer_fifo.append(data[i:i+sabs])
204+
else:
205+
self.producer_fifo.append(data)
165206
self.initiate_send()
166207

167208
def push_with_producer (self, producer):
168-
self.producer_fifo.push (producer)
209+
self.producer_fifo.append(producer)
169210
self.initiate_send()
170211

171212
def readable (self):
172213
"predicate for inclusion in the readable for select()"
173-
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
214+
# cannot use the old predicate, it violates the claim of the
215+
# set_terminator method.
216+
217+
# return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
218+
return 1
174219

175220
def writable (self):
176221
"predicate for inclusion in the writable for select()"
177-
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
178-
# this is about twice as fast, though not as clear.
179-
return not (
180-
(self.ac_out_buffer == b'') and
181-
self.producer_fifo.is_empty() and
182-
self.connected
183-
)
222+
return self.producer_fifo or (not self.connected)
184223

185224
def close_when_done (self):
186225
"automatically close this channel once the outgoing queue is empty"
187-
self.producer_fifo.push (None)
188-
189-
# refill the outgoing buffer by calling the more() method
190-
# of the first producer in the queue
191-
def refill_buffer (self):
192-
while 1:
193-
if len(self.producer_fifo):
194-
p = self.producer_fifo.first()
195-
# a 'None' in the producer fifo is a sentinel,
196-
# telling us to close the channel.
197-
if p is None:
198-
if not self.ac_out_buffer:
199-
self.producer_fifo.pop()
200-
self.close()
201-
return
202-
elif isinstance(p, str) or isinstance(p, bytes):
203-
if isinstance(p, str):
204-
p = p.encode('ascii')
205-
self.producer_fifo.pop()
206-
self.ac_out_buffer = self.ac_out_buffer + p
226+
self.producer_fifo.append(None)
227+
228+
def initiate_send(self):
229+
while self.producer_fifo and self.connected:
230+
first = self.producer_fifo[0]
231+
# handle empty string/buffer or None entry
232+
if not first:
233+
del self.producer_fifo[0]
234+
if first is None:
235+
## print("first is None")
236+
self.handle_close()
207237
return
208-
data = p.more()
238+
## print("first is not None")
239+
240+
# handle classic producer behavior
241+
obs = self.ac_out_buffer_size
242+
try:
243+
data = buffer(first, 0, obs)
244+
except TypeError:
245+
data = first.more()
209246
if data:
210-
if isinstance(data, str):
211-
data = data.encode('ascii')
212-
self.ac_out_buffer = self.ac_out_buffer + bytes(data)
213-
return
247+
self.producer_fifo.appendleft(data)
214248
else:
215-
self.producer_fifo.pop()
216-
else:
217-
return
249+
del self.producer_fifo[0]
250+
continue
218251

219-
def initiate_send (self):
220-
obs = self.ac_out_buffer_size
221-
# try to refill the buffer
222-
if (len (self.ac_out_buffer) < obs):
223-
self.refill_buffer()
252+
if isinstance(data, str) and self.use_encoding:
253+
data = bytes(data, self.encoding)
224254

225-
if self.ac_out_buffer and self.connected:
226-
# try to send the buffer
255+
# send the data
227256
try:
228-
num_sent = self.send (self.ac_out_buffer[:obs])
229-
if num_sent:
230-
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
231-
232-
except socket.error as why:
257+
num_sent = self.send(data)
258+
except socket.error:
233259
self.handle_error()
234260
return
235261

262+
if num_sent:
263+
if num_sent < len(data) or obs < len(first):
264+
self.producer_fifo[0] = first[num_sent:]
265+
else:
266+
del self.producer_fifo[0]
267+
# we tried to send some actual data
268+
return
269+
236270
def discard_buffers (self):
237271
# Emergencies only!
238272
self.ac_in_buffer = b''
239-
self.ac_out_buffer = b''
240-
while self.producer_fifo:
241-
self.producer_fifo.pop()
242-
273+
del self.incoming[:]
274+
self.producer_fifo.clear()
243275

244276
class simple_producer:
245277

0 commit comments

Comments
 (0)