1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
# -*- Mode: text -*-
TO DO LARGER TASKS
- Need more examples.
- Benchmarkable but more realistic HTTP server?
- Example of using UDP.
- Write up a tutorial for the scheduling API.
- More systematic approach to logging. Logger objects? What about
heavy-duty logging, tracking essentially all task state changes?
- Restructure directory, move demos and benchmarks to subdirectories.
TO DO LATER
- When multiple tasks are accessing the same socket, they should
either get interleaved I/O or an immediate exception; it should not
compromise the integrity of the scheduler or the app or leave a task
hanging.
- For epoll you probably want to check/(log?) EPOLLHUP and EPOLLERR errors.
- Add the simplest API possible to run a generator with a timeout.
- Ensure multiple tasks can do atomic writes to the same pipe (since
UNIX guarantees that short writes to pipes are atomic).
- Ensure some easy way of distributing accepted connections across tasks.
- Be wary of thread-local storage. There should be a standard API to
get the current Context (which holds current task, event loop, and
maybe more) and a standard meta-API to change how that standard API
works (i.e. without monkey-patching).
- See how much of asyncore I've already replaced.
- Could BufferedReader reuse the standard io module's readers???
- Support ZeroMQ "sockets" which are user objects. Though possibly
this can be supported by getting the underlying fd? See
http://mail.python.org/pipermail/python-ideas/2012-October/017532.html
OTOH see
https://github.com/zeromq/pyzmq/blob/master/zmq/eventloop/ioloop.py
- Study goroutines (again).
- Benchmarks: http://nichol.as/benchmark-of-python-web-servers
FROM OLDER LIST
- Multiple readers/writers per socket? (At which level? pollster,
eventloop, or scheduler?)
- Could poll() usefully be an iterator?
- Do we need to support more epoll and/or kqueue modes/flags/options/etc.?
- Optimize register/unregister calls away if they cancel each other out?
- Add explicit wait queue to wait for Task's completion, instead of
callbacks?
- Implement various lock styles a la threading.py.
- Look at pyfdpdlib's ioloop.py:
http://code.google.com/p/pyftpdlib/source/browse/trunk/pyftpdlib/lib/ioloop.py
MISTAKES I MADE
- Forgetting yield from. (E.g.: scheduler.sleep(1); listener.accept().)
- Forgot to add bare yield at end of internal function, after block().
- Forgot to call add_done_callback().
- Forgot to pass an undoer to block(), bug only found when cancelled.
- Subtle accounting mistake in a callback.
- Used context.eventloop from a different thread, forgetting about TLS.
- Nasty race: eventloop.ready may contain both an I/O callback and a
cancel callback. How to avoid? Keep the DelayedCall in ready. Is
that enough?
- If a toplevel task raises an error it just stops and nothing is logged
unless you have debug logging on. This confused me. (Then again,
previously I logged whenever a task raised an error, and that was too
chatty...)
- Forgot to set the connection socket returned by accept() in
nonblocking mode.
- Nastiest so far (cost me about a day): A race condition in
call_in_thread() where the Future's done_callback (which was
task.unblock()) would run immediately at the time when
add_done_callback() was called, and this screwed over the task
state. Solution: wrap the callback in eventloop.call_later().
Ironically, I had a comment stating there might be a race condition.
- Another bug where I was calling unblock() for the current thread
immediately after calling block(), before yielding.
- readexactly() wasn't checking for EOF, so could be looping.
(Worse, the first fix I attempted was wrong.)
- Spent a day trying to understand why a tentative patch trying to
move the recv() implementation into the eventloop (or the pollster)
resulted in problems cancelling a recv() call. Ultimately the
problem is that the cancellation mechanism is part of the coroutine
scheduler, which simply throws an exception into a task when it next
runs, and there isn't anything to be interrupted in the eventloop;
but the eventloop still has a reader registered (which will never
fire because I suspended the server -- that's my test case :-).
Then, the eventloop keeps running until the last file descriptor is
unregistered. What contributed to this disaster?
* I didn't build the whole infrastructure, just played with recv()
* I don't have unittests
* I don't have good logging to see what is going
- In sockets.py, in some SSL error handling code, used the wrong
variable (sock instead of sslsock). A linter would have found this.
- In polling.py, in KqueuePollster.register_writer(), a copy/paste
error where I was testing for "if fd not in self.readers" instead of
writers. This only came out when I had both a reader and a writer
for the same fd.
- Submitted some changes prematurely (forgot to pass the filename on
hg ci).
- Forgot again that shutdown(SHUT_WR) on an ssl socket does not work
as I expected. I ran into this with the origininal sockets.py and
again in transport.py.
- Having the same callback for both reading and writing has a problem:
it may be scheduled twice, and if the first call closes the socket,
the second runs into trouble.
MISTAKES I MADE IN TULIP V2
- Nice one: Future._schedule_callbacks() wasn't scheduling any callbacks.
Spot the bug in these four lines:
def _schedule_callbacks(self):
callbacks = self._callbacks[:]
self._callbacks[:] = []
for callback in self._callbacks:
self._event_loop.call_soon(callback, self)
The good news is that I found it with a unittest (albeit not the
unittest intended to exercise this particular method :-( ).
- In _make_self_pipe_or_sock(), called _pollster.register_reader()
instead of add_reader(), trying to optimize something but breaking
things instead (since the -- internal -- API of register_reader()
had changed).
|