forked from aosabook/500lines
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcrawler.tex
More file actions
1604 lines (1325 loc) · 59.5 KB
/
Copy pathcrawler.tex
File metadata and controls
1604 lines (1325 loc) · 59.5 KB
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\begin{aosachapter}{A Web Crawler With asyncio Coroutines}{s:crawler}{A. Jesse Jiryu Davis and Guido van Rossum}
Classical computer science emphasizes efficient algorithms that complete
computations as quickly as possible. But many networked programs spend
their time not computing, but holding open many connections that are
slow, or have infrequent events. These programs present a very different
challenge: to wait for a huge number of network events efficiently. A
contemporary approach to this problem is asynchronous I/O, or ``async''.
This chapter presents a simple web crawler. The crawler is an archetypal
async application because it waits for many responses, but does little
computation. The more pages it can fetch at once, the sooner it
completes. If it devotes a thread to each in-flight request, then as the
number of concurrent requests rises it will run out of memory or other
thread-related resource before it runs out of sockets. It avoids the
need for threads by using asynchronous I/O.
We present the example in three stages. First, we show an async event
loop and sketch a crawler that uses the event loop with callbacks: it is
very efficient, but extending it to more complex problems would lead to
unmanageable spaghetti code. Second, therefore, we show that Python
coroutines are both efficient and extensible. We implement simple
coroutines in Python using generator functions. In the third stage, we
use the full-featured coroutines from Python's standard ``asyncio''
library\footnote{Guido introduced the standard asyncio library, called
``Tulip'' then, at PyCon 2013.}, and coordinate them using an async
queue.
\aosasecti{The Task}\label{the-task}
A web crawler finds and downloads all pages on a website, perhaps to
archive or index them. Beginning with a root URL, it fetches each page,
parses it for links to pages it has not seen, and adds the new links to
a queue. When it fetches a page with no unseen links and the queue is
empty, it stops.
We can hasten this process by downloading many pages concurrently. As
the crawler finds new links, it launches simultaneous fetch operations
for the new pages on separate sockets. It parses responses as they
arrive, adding new links to the queue. There may come some point of
diminishing returns where too much concurrency degrades performance, so
we cap the number of concurrent requests, and leave the remaining links
in the queue until some in-flight requests complete.
\aosasecti{The Traditional Approach}\label{the-traditional-approach}
How do we make the crawler concurrent? Traditionally we would create a
thread pool. Each thread would be in charge of downloading one page at a
time over a socket. For example, to download a page from xkcd.com:
\begin{verbatim}
def fetch(url):
sock = socket.socket()
sock.connect(('xkcd.com', 80))
request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
sock.send(request.encode('ascii'))
response = b''
chunk = sock.recv(4096)
while chunk:
response += chunk
chunk = sock.recv(4096)
# Page is now downloaded.
links = parse_links(response)
q.add(links)
\end{verbatim}
By default, socket operations are \emph{blocking}: when the thread calls
a method like \texttt{connect} or \texttt{recv}, it pauses until the
operation completes.\footnote{Even calls to \texttt{send} can block, if
the recipient is slow to acknowledge outstanding messages and the
system's buffer of outgoing data is full.} Consequently to download
many pages at once, we need many threads. A sophisticated application
amortizes the cost of thread-creation by keeping idle threads in a
thread pool, then checking them out to reuse them for subsequent tasks;
it does the same with sockets in a connection pool.
And yet, threads are expensive, and operating systems enforce a variety
of hard caps on the number of threads a process, user, or machine may
have. On Jesse's system, a Python thread costs around 50k of memory, and
starting tens of thousands of threads causes failures. If we scale up to
tens of thousands of simultaneous operations on concurrent sockets, we
run out of threads before we run out of sockets. Per-thread overhead or
system limits on threads are the bottleneck.
In his influential article ``The C10K problem''\footnote{\url{http://www.kegel.com/c10k.html}},
Dan Kegel outlines the limitations of multithreading for I/O
concurrency. He begins,
\begin{quote}
It's time for web servers to handle ten thousand clients simultaneously,
don't you think? After all, the web is a big place now.
\end{quote}
Kegel coined the term ``C10K'' in 1999. Ten thousand connections sounds
dainty now, but the problem has changed only in size, not in kind. Back
then, using a thread per connection for C10K was impractical. Now the
cap is orders of magnitude higher. Indeed, our toy web crawler would
work just fine with threads. Yet for very large scale applications, with
hundreds of thousands of connections, the cap remains: there is a limit
beyond which most systems can still create sockets, but have run out of
threads. How can we overcome this?
\aosasecti{Async}\label{async}
Asynchronous I/O frameworks do concurrent operations on a single thread.
Let us find out how.
Async frameworks use \emph{non-blocking} sockets. In our async crawler,
we set the socket non-blocking before we begin to connect to the server:
\begin{verbatim}
sock = socket.socket()
sock.setblocking(False)
try:
sock.connect(('xkcd.com', 80))
except BlockingIOError:
pass
\end{verbatim}
Irritatingly, a non-blocking socket throws an exception from
\texttt{connect}, even when it is working normally. This exception
replicates the irritating behavior of the underlying C function, which
sets \texttt{errno} to \texttt{EINPROGRESS} to tell you it has begun.
Now our crawler needs a way to know when the connection is established,
so it can send the HTTP request. We could simply keep trying in a tight
loop:
\begin{verbatim}
request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
encoded = request.encode('ascii')
while True:
try:
sock.send(encoded)
break # Done.
except OSError as e:
pass
print('sent')
\end{verbatim}
This method not only wastes electricity, but it cannot efficiently await
events on \emph{multiple} sockets. In ancient times, BSD Unix's solution
to this problem was \texttt{select}, a C function that waits for an
event to occur on a non-blocking socket or a small array of them.
Nowadays the demand for Internet applications with huge numbers of
connections has led to replacements like \texttt{poll}, then
\texttt{kqueue} on BSD and \texttt{epoll} on Linux. These APIs are
similar to \texttt{select}, but perform well with very large numbers of
connections.
Python 3.4's \texttt{DefaultSelector} uses the best \texttt{select}-like
function available on your system. To register for notifications about
network I/O, we create a non-blocking socket and register it with the
default selector:
\begin{verbatim}
from selectors import DefaultSelector
selector = DefaultSelector()
sock = socket.socket()
sock.setblocking(False)
try:
sock.connect(('xkcd.com', 80))
except BlockingIOError:
pass
def connected():
selector.unregister(sock.fileno())
print('connected!')
selector.register(sock.fileno(), EVENT_WRITE, connected)
\end{verbatim}
We disregard the spurious error and call \texttt{selector.register},
passing in the socket's file descriptor and a constant that expresses
what event we are waiting for. To be notified when the connection is
established, we pass \texttt{EVENT\_WRITE}: that is, we want to know
when the socket is ``writable''. We also pass a Python function,
\texttt{connected}, to run when that event occurs. Such a function is
known as a \emph{callback}.
We process I/O notifications as the selector receives them, in a loop:
\begin{verbatim}
def loop():
while True:
events = selector.select()
for event_key, event_mask in events:
callback = event_key.data
callback()
\end{verbatim}
The \texttt{connected} callback is stored as \texttt{event\_key.data},
which we retrieve and execute once the non-blocking socket is connected.
Unlike in our fast-spinning loop above, the call to \texttt{select} here
pauses, awaiting the next I/O events. Then the loop runs callbacks that
are waiting for these events. Operations that have not completed remain
pending until some future tick of the event loop.
What have we demonstrated already? We showed how to begin an operation
and execute a callback when the operation is ready. An async
\emph{framework} builds on the two features we have shown---non-blocking
sockets and the event loop---to run concurrent operations on a single
thread.
We have achieved ``concurrency'' here, but not what is traditionally
called ``parallelism''. That is, we built a tiny system that does
overlapping I/O. It is capable of beginning new operations while others
are in flight. It does not actually utilize multiple cores to execute
computation in parallel. But then, this system is designed for I/O-bound
problems, not CPU-bound ones.\footnote{Python's global interpreter lock
prohibits running Python code in parallel in one process anyway.
Parallelizing CPU-bound algorithms in Python requires multiple
processes, or writing the parallel portions of the code in C. But that
is a topic for another day.}
So our event loop is efficient at concurrent I/O because it does not
devote thread resources to each connection. But before we proceed, it is
important to correct a common misapprehension that async is
\emph{faster} than multithreading. Often it is not---indeed, in Python,
an event loop like ours is moderately slower than multithreading at
serving a small number of very active connections. In a runtime without
a global interpreter lock, threads would perform even better on such a
workload. What asynchronous I/O is right for, is applications with many
slow or sleepy connections with infrequent events.\footnote{Jesse listed
indications and contraindications for using async in ``What Is Async,
How Does It Work, And When Should I Use It?'', available at
pyvideo.org.}\footnote{Mike Bayer compared the throughput of asyncio
and multithreading for different workloads in his ``Asynchronous
Python and Databases'':
http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/}
\aosasecti{Programming With Callbacks}\label{programming-with-callbacks}
With the runty async framework we have built so far, how can we build a
web crawler? Even a simple URL-fetcher is painful to write.
We begin with global sets of the URLs we have yet to fetch, and the URLs
we have seen:
\begin{verbatim}
urls_todo = set(['/'])
seen_urls = set(['/'])
\end{verbatim}
The \texttt{seen\_urls} set includes \texttt{urls\_todo} plus completed
URLs. The two sets are initialized with the root URL ``/''.
Fetching a page will require a series of callbacks. The
\texttt{connected} callback fires when a socket is connected, and sends
a GET request to the server. But then it must await a response, so it
registers another callback. If, when that callback fires, it cannot read
the full response yet, it registers again, and so on.
Let us collect these callbacks into a \texttt{Fetcher} object. It needs
a URL, a socket object, and a place to accumulate the response bytes:
\begin{verbatim}
class Fetcher:
def __init__(self, url):
self.response = b'' # Empty array of bytes.
self.url = url
self.sock = None
\end{verbatim}
We begin by calling \texttt{Fetcher.fetch}:
\begin{verbatim}
# Method on Fetcher class.
def fetch(self):
self.sock = socket.socket()
self.sock.setblocking(False)
try:
self.sock.connect(('xkcd.com', 80))
except BlockingIOError:
pass
# Register next callback.
selector.register(self.sock.fileno(),
EVENT_WRITE,
self.connected)
\end{verbatim}
The \texttt{fetch} method begins connecting a socket. But notice the
method returns before the connection is established. It must return
control to the event loop to wait for the connection. To understand why,
imagine our whole application was structured so:
\begin{verbatim}
# Begin fetching http://xkcd.com/353/
fetcher = Fetcher('/353/')
fetcher.fetch()
while True:
events = selector.select()
for event_key, event_mask in events:
callback = event_key.data
callback(event_key, event_mask)
\end{verbatim}
All event notifications are processed in the event loop when it calls
\texttt{select}. Hence \texttt{fetch} must hand control to the event
loop, so that the program knows when the socket has connected. Only then
does the loop run the \texttt{connected} callback, which was registered
at the end of \texttt{fetch} above.
Here is the implementation of \texttt{connected}:
\begin{verbatim}
# Method on Fetcher class.
def connected(self, key, mask):
print('connected!')
selector.unregister(key.fd)
request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
self.sock.send(request.encode('ascii'))
# Register the next callback.
selector.register(key.fd,
EVENT_READ,
self.read_response)
\end{verbatim}
The method sends a GET request. A real application would check the
return value of \texttt{send} in case the whole message cannot be sent
at once. But our request is small and our application unsophisticated.
It blithely calls \texttt{send}, then waits for a response. Of course,
it must register yet another callback and relinquish control to the
event loop. The next and final callback, \texttt{read\_response},
processes the server's reply:
\begin{verbatim}
# Method on Fetcher class.
def read_response(self, key, mask):
global stopped
chunk = self.sock.recv(4096) # 4k chunk size.
if chunk:
self.response += chunk
else:
selector.unregister(key.fd) # Done reading.
links = self.parse_links()
# Python set-logic:
for link in links.difference(seen_urls):
urls_todo.add(link)
Fetcher(link).fetch() # <- New Fetcher.
seen_urls.update(links)
urls_todo.remove(self.url)
if not urls_todo:
stopped = True
\end{verbatim}
The callback is executed each time the selector sees that the socket is
``readable'', which could mean two things: the socket has data or it is
closed.
The callback asks for up to four kilobytes of data from the socket. If
less is ready, \texttt{chunk} contains whatever data is available. If
there is more, \texttt{chunk} is four kilobytes long and the socket
remains readable, so the event loop runs this callback again on the next
tick. When the response is complete, the server has closed the socket
and \texttt{chunk} is empty.
The \texttt{parse\_links} method, not shown, returns a set of URLs. We
start a new fetcher for each new URL, with no concurrency cap. Note a
nice feature of async programming with callbacks: we need no mutex
around changes to shared data, such as when we add links to
\texttt{seen\_urls}. There is no preemptive multitasking, so we cannot
be interrupted at arbitrary points in our code.
We add a global \texttt{stopped} variable and use it to control the
loop:
\begin{verbatim}
stopped = False
def loop():
while not stopped:
events = selector.select()
for event_key, event_mask in events:
callback = event_key.data
callback()
\end{verbatim}
Once all pages are downloaded the fetcher stops the global event loop
and the program exits.
This example makes async's problem plain: spaghetti code.
We need some way to express a series of computations and I/O operations,
and schedule multiple such series of operations to run concurrently. But
without threads, a series of operations cannot be collected into a
single function: whenever a function begins an I/O operation, it
explicitly saves whatever state will be needed in the future, then
returns. You are responsible for thinking about and writing this
state-saving code.
Let us explain what we mean by that. Consider how simply we fetched a
URL on a thread with a conventional blocking socket:
\begin{verbatim}
# Blocking version.
def fetch(url):
sock = socket.socket()
sock.connect(('xkcd.com', 80))
request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url)
sock.send(request.encode('ascii'))
response = b''
chunk = sock.recv(4096)
while chunk:
response += chunk
chunk = sock.recv(4096)
# Page is now downloaded.
links = parse_links(response)
q.add(links)
\end{verbatim}
What state does this function remember between one socket operation and
the next? It has the socket, a URL, and the accumulating
\texttt{response}. A function that runs on a thread uses basic features
of the programming language to store this temporary state in local
variables, on its stack. The function also has a ``continuation''---that
is, the code it plans to execute after I/O completes. The runtime
remembers the continuation by storing the thread's instruction pointer.
You need not think about restoring these local variables and the
continuation after I/O. It is built in to the language.
But with a callback-based async framework, these language features are
no help. While waiting for I/O, a function must save its state
explicitly, because the function returns and loses its stack frame
before I/O completes. In lieu of local variables, our callback-based
example stores \texttt{sock} and \texttt{response} as attributes of
\texttt{self}, the Fetcher instance. In lieu of the instruction pointer,
it stores its continuation by registering the callbacks
\texttt{connected} and \texttt{read\_response}. As the application's
features grow, so does the complexity of the state we manually save
across callbacks. Such onerous bookkeeping makes the coder prone to
migraines.
Even worse, what happens if a callback throws an exception, before it
schedules the next callback in the chain? Say we did a poor job on the
\texttt{parse\_links} method and it throws an exception parsing some
HTML:
\begin{verbatim}
Traceback (most recent call last):
File "loop-with-callbacks.py", line 111, in <module>
loop()
File "loop-with-callbacks.py", line 106, in loop
callback(event_key, event_mask)
File "loop-with-callbacks.py", line 51, in read_response
links = self.parse_links()
File "loop-with-callbacks.py", line 67, in parse_links
raise Exception('parse error')
Exception: parse error
\end{verbatim}
The stack trace shows only that the event loop was running a callback.
We do not remember what led to the error. The chain is broken on both
ends: we forgot where we were going and whence we came. This loss of
context is called ``stack ripping'', and in many cases it confounds the
investigator. Stack ripping also prevents us from installing an
exception handler for a chain of callbacks, the way a ``try / except''
block wraps a function call and its tree of descendents.\footnote{For a
complex solution to this problem, see
\url{http://www.tornadoweb.org/en/stable/stack_context.html}}
So, even apart from the long debate about the relative efficiencies of
multithreading and async, there is this other debate regarding which is
more error-prone: threads are susceptible to data races if you make a
mistake synchronizing them, but callbacks are stubborn to debug due to
stack ripping.
\aosasecti{Coroutines}\label{coroutines}
We entice you with a promise. It is possible to write asynchronous code
that combines the efficiency of callbacks with the classic good looks of
multithreaded programming. This combination is achieved with a pattern
called ``coroutines''. Using Python 3.4's standard asyncio library, and
a package called ``aiohttp'', fetching a URL in a coroutine is very
direct\footnote{The \texttt{@asyncio.coroutine} decorator is not
magical. In fact, if it decorates a generator function and the
\texttt{PYTHONASYNCIODEBUG} environment variable is not set, the
decorator does practically nothing. It just sets an attribute,
\texttt{\_is\_coroutine}, for the convenience of other parts of the
framework. It is possible to use asyncio with bare generators not
decorated with \texttt{@asyncio.coroutine} at all.}:
\begin{verbatim}
@asyncio.coroutine
def fetch(self, url):
response = yield from aiohttp.request('get', url)
body = yield from response.read()
\end{verbatim}
It is also scalable. Compared to the 50k of memory per thread and the
operating system's hard limits on threads, a Python coroutine takes
barely 3k of memory on Jesse's system. Python can easily start hundreds
of thousands of coroutines.
The concept of a coroutine, dating to the elder days of computer
science, is simple: it is a subroutine that can be paused and resumed.
Whereas threads are preemptively multitasked by the operating system,
coroutines multitask cooperatively: they choose when to pause, and which
coroutine to run next.
There are many implementations of coroutines; even in Python there are
several. The coroutines in the standard ``asyncio'' library in Python
3.4 are built upon generators, a Future class, and the ``yield from''
statement. Starting in Python 3.5, coroutines will be a native feature
of the language itself\footnote{Python 3.5's built-in coroutines are
described in \href{https://www.python.org/dev/peps/pep-0492/}{PEP
492}, ``Coroutines with async and await syntax.'' At the time of this
writing, Python 3.5 was in beta, due for release in September 2015.};
however, understanding coroutines as they were first implemented in
Python 3.4, using pre-existing language facilities, is the foundation to
tackle Python 3.5's native coroutines.
To explain Python 3.4's generator-based coroutines, we will engage in an
exposition of generators and how they are used as coroutines in asyncio,
and trust you will enjoy reading it as much as we enjoyed writing it.
Once we have explained generator-based coroutines, we shall use them in
our async web crawler.
\aosasecti{How Python Generators Work}\label{how-python-generators-work}
Before you grasp Python generators, you have to understand how regular
Python functions work. Normally, when a Python function calls a
subroutine, the subroutine retains control until it returns, or throws
an exception. Then control returns to the caller:
\begin{verbatim}
>>> def foo():
... bar()
...
>>> def bar():
... pass
\end{verbatim}
The standard Python interpreter is written in C. The C function that
executes a Python function is called, mellifluously,
\texttt{PyEval\_EvalFrameEx}. It takes a Python stack frame object and
evaluates Python bytecode in the context of the frame. Here is the
bytecode for \texttt{foo}:
\begin{verbatim}
>>> import dis
>>> dis.dis(foo)
2 0 LOAD_GLOBAL 0 (bar)
3 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
6 POP_TOP
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
\end{verbatim}
The \texttt{foo} function loads \texttt{bar} onto its stack and calls
it, then pops its return value from the stack, loads \texttt{None} onto
the stack, and returns \texttt{None}.
When \texttt{PyEval\_EvalFrameEx} encounters the \texttt{CALL\_FUNCTION}
bytecode, it creates a new Python stack frame and recurses: that is, it
calls \texttt{PyEval\_EvalFrameEx} recursively with the new frame, which
is used to execute \texttt{bar}.
\aosafigure[240pt]{crawler-images/function-calls.png}{Function Calls}{500l.crawler.functioncalls}
It is crucial to understand that Python stack frames are allocated in
heap memory! The Python interpreter is a normal C program, so its stack
frames are normal stack frames. But the \emph{Python} stack frames it
manipulates are on the heap. Among other surprises, this means a Python
stack frame can outlive its function call. To see this interactively,
save the current frame from within \texttt{bar}:
\begin{verbatim}
>>> import inspect
>>> frame = None
>>> def foo():
... bar()
...
>>> def bar():
... global frame
... frame = inspect.currentframe()
...
>>> foo()
>>> # The frame was executing the code for 'bar'.
>>> frame.f_code.co_name
'bar'
>>> # Its back pointer refers to the frame for 'foo'.
>>> caller_frame = frame.f_back
>>> caller_frame.f_code.co_name
'foo'
\end{verbatim}
The stage is now set for Python generators, which use the same building
blocks---code objects and stack frames---to marvelous effect.
This is a generator function:
\begin{verbatim}
>>> def gen_fn():
... result = yield 1
... print('result of yield: {}'.format(result))
... result2 = yield 2
... print('result of 2nd yield: {}'.format(result2))
... return 'done'
...
\end{verbatim}
When Python compiles \texttt{gen\_fn} to bytecode, it sees the
\texttt{yield} statement and knows that \texttt{gen\_fn} is a generator
function, not a regular one. It sets a flag to remember this fact:
\begin{verbatim}
>>> # The generator flag is bit position 5.
>>> generator_bit = 1 << 5
>>> bool(gen_fn.__code__.co_flags & generator_bit)
True
\end{verbatim}
When you call a generator function, Python sees the generator flag, and
it does not actually run the function. Instead, it creates a generator:
\begin{verbatim}
>>> gen = gen_fn()
>>> type(gen)
<class 'generator'>
\end{verbatim}
A Python generator encapsulates a stack frame plus a reference to some
code, the body of \texttt{gen\_fn}:
\begin{verbatim}
>>> gen.gi_code.co_name
'gen_fn'
\end{verbatim}
All generators from calls to \texttt{gen\_fn} point to this same code.
But each has its own stack frame. This stack frame is not on any actual
stack, it sits in heap memory waiting to be used:
\aosafigure[240pt]{crawler-images/generator.png}{Generators}{500l.crawler.generators}
The frame has a ``last instruction'' pointer, the instruction it
executed most recently. In the beginning, the last instruction pointer
is -1, meaning the generator has not begun:
\begin{verbatim}
>>> gen.gi_frame.f_lasti
-1
\end{verbatim}
When we call \texttt{send}, the generator reaches its first
\texttt{yield}, and pauses. The return value of \texttt{send} is 1,
since that is what \texttt{gen} passes to the \texttt{yield} expression:
\begin{verbatim}
>>> gen.send(None)
1
\end{verbatim}
The generator's instruction pointer is now 3 bytecodes from the start,
part way through the 56 bytes of compiled Python:
\begin{verbatim}
>>> gen.gi_frame.f_lasti
3
>>> len(gen.gi_code.co_code)
56
\end{verbatim}
The generator can be resumed at any time, from any function, because its
stack frame is not actually on the stack: it is on the heap. Its
position in the call hierarchy is not fixed, and it need not obey the
first-in, last-out order of execution that regular functions do. It is
liberated, floating free like a cloud.
We can send the value ``hello'' into the generator and it becomes the
result of the \texttt{yield} expression, and the generator continues
until it yields 2:
\begin{verbatim}
>>> gen.send('hello')
result of yield: hello
2
\end{verbatim}
Its stack frame now contains the local variable \texttt{result}:
\begin{verbatim}
>>> gen.gi_frame.f_locals
{'result': 'hello'}
\end{verbatim}
Other generators created from \texttt{gen\_fn} will have their own stack
frames and their own local variables.
When we call \texttt{send} again, the generator continues from its
second \texttt{yield}, and finishes by raising the special
\texttt{StopIteration} exception:
\begin{verbatim}
>>> gen.send('goodbye')
result of 2nd yield: goodbye
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration: done
\end{verbatim}
The exception has a value, which is the return value of the generator:
the string ``done''.
\aosasecti{Building Coroutines With
Generators}\label{building-coroutines-with-generators}
So a generator can pause, and it can be resumed with a value, and it has
a return value. Sounds like a good primitive upon which to build an
async programming model, without spaghetti callbacks! We want to build a
``coroutine'': a routine that is cooperatively scheduled with other
routines in the program. Our coroutines will be a simplified version of
those in Python's standard ``asyncio'' library. As in asyncio, we will
use generators, futures, and the ``yield from'' statement.
First we need a way to represent some future result that a coroutine is
waiting for. A stripped-down version:
\begin{verbatim}
class Future:
def __init__(self):
self.result = None
self._callbacks = []
def add_done_callback(self, fn):
self._callbacks.append(fn)
def set_result(self, result):
self.result = result
for fn in self._callbacks:
fn(self)
\end{verbatim}
A future is initially ``pending''. It is ``resolved'' by a call to
\texttt{set\_result}.\footnote{This future has many deficiencies. For
example, once this future is resolved, a coroutine that yields it
should resume immediately instead of pausing, but with our code it
does not. See asyncio's Future class for a complete implementation.}
Let us adapt our fetcher to use futures and coroutines. Review how we
wrote \texttt{fetch} with a callback:
\begin{verbatim}
class Fetcher:
def fetch(self):
self.sock = socket.socket()
self.sock.setblocking(False)
try:
self.sock.connect(('xkcd.com', 80))
except BlockingIOError:
pass
selector.register(self.sock.fileno(),
EVENT_WRITE,
self.connected)
def connected(self, key, mask):
print('connected!')
# And so on....
\end{verbatim}
The \texttt{fetch} method begins connecting a socket, then registers the
callback, \texttt{connected}, to be executed when the socket is ready.
Now we can combine these two steps into one coroutine:
\begin{verbatim}
def fetch(self):
sock = socket.socket()
sock.setblocking(False)
try:
sock.connect(('xkcd.com', 80))
except BlockingIOError:
pass
f = Future()
def on_connected():
f.set_result(None)
selector.register(sock.fileno(),
EVENT_WRITE,
on_connected)
yield f
selector.unregister(sock.fileno())
print('connected!')
\end{verbatim}
Now \texttt{fetch} is a generator function, rather than a regular one,
because it contains a \texttt{yield} statement. We create a pending
future, then yield it to pause \texttt{fetch} until the socket is ready.
The inner function \texttt{on\_connected} resolves the future.
But when the future resolves, what resumes the generator? We need a
coroutine \emph{driver}. Let us call it ``task'':
\begin{verbatim}
class Task:
def __init__(self, coro):
self.coro = coro
f = Future()
f.set_result(None)
self.step(f)
def step(self, future):
try:
next_future = self.coro.send(future.result)
except StopIteration:
return
next_future.add_done_callback(self.step)
# Begin fetching http://xkcd.com/353/
fetcher = Fetcher('/353/')
Task(fetcher.fetch())
loop()
\end{verbatim}
The task starts the \texttt{fetch} generator by sending \texttt{None}
into it. Then \texttt{fetch} runs until it yields a future, which the
task captures as \texttt{next\_future}. When the socket is connected,
the event loop runs the callback \texttt{on\_connected}, which resolves
the future, which calls \texttt{step}, which resumes \texttt{fetch}.
\aosasecti{Factoring Coroutines With
\texttt{yield from}}\label{factoring-coroutines-with-yield-from}
Once the socket is connected, we send the HTTP GET request and read the
server response. These steps need no longer be scattered among
callbacks; we gather them into the same generator function:
\begin{verbatim}
def fetch(self):
# ... connection logic from above, then:
sock.send(request.encode('ascii'))
while True:
f = Future()
def on_readable():
f.set_result(sock.recv(4096))
selector.register(sock.fileno(),
EVENT_READ,
on_readable)
chunk = yield f
selector.unregister(sock.fileno())
if chunk:
self.response += chunk
else:
# Done reading.
break
\end{verbatim}
This code, which reads a whole message from a socket, seems generally
useful. How can we factor it from \texttt{fetch} into a subroutine? Now
Python 3's celebrated \texttt{yield from} takes the stage. It lets one
generator \emph{delegate} to another.
To see how, let us return to our simple generator example:
\begin{verbatim}
>>> def gen_fn():
... result = yield 1
... print('result of yield: {}'.format(result))
... result2 = yield 2
... print('result of 2nd yield: {}'.format(result2))
... return 'done'
...
\end{verbatim}
To call this generator from another generator, delegate to it with
\texttt{yield from}:
\begin{verbatim}
>>> # Generator function:
>>> def caller_fn():
... gen = gen_fn()
... rv = yield from gen
... print('return value of yield-from: {}'
... .format(rv))
...
>>> # Make a generator from the
>>> # generator function.
>>> caller = caller_fn()
\end{verbatim}
The \texttt{caller} generator acts as if it were \texttt{gen}, the
generator it is delegating to:
\begin{verbatim}
>>> caller.send(None)
1
>>> caller.gi_frame.f_lasti
15
>>> caller.send('hello')
result of yield: hello
2
>>> caller.gi_frame.f_lasti # Hasn't advanced.
15
>>> caller.send('goodbye')
result of 2nd yield: goodbye
return value of yield-from: done
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
\end{verbatim}
While \texttt{caller} yields from \texttt{gen}, \texttt{caller} does not
advance. Notice that its instruction pointer remains at 15, the site of
its \texttt{yield from} statement, even while the inner generator
\texttt{gen} advances from one \texttt{yield} statement to the
next.\footnote{In fact, this is exactly how ``yield from'' works in
CPython. A function increments its instruction pointer before
executing each statement. But after the outer generator executes
``yield from'', it subtracts 1 from its instruction pointer to keep
itself pinned at the ``yield from'' statement. Then it yields to
\emph{its} caller. The cycle repeats until the inner generator throws
\texttt{StopIteration}, at which point the outer generator finally
allows itself to advance to the next instruction.} From our
perspective outside \texttt{caller}, we cannot tell if the values it
yields are from \texttt{caller} or from the generator it delegates to.
And from inside \texttt{gen}, we cannot tell if values are sent in from
\texttt{caller} or from outside it. The \texttt{yield from} statement is
a frictionless channel, through which values flow in and out of
\texttt{gen} until it \texttt{gen} completes.
A coroutine can delegate work to a sub-coroutine with
\texttt{yield from} and receive the result of the work. Notice, above,
that \texttt{caller} printed ``return value of yield-from: done''. When
\texttt{gen} completed, its return value became the value of the
\texttt{yield from} statement in \texttt{caller}:
\begin{verbatim}
rv = yield from gen
\end{verbatim}
Earlier, when we criticized callback-based async programming, our most
strident complaint was about ``stack ripping'': when a callback throws
an exception, the stack trace is typically useless. It only shows that
the event loop was running the callback, not \emph{why}. How do
coroutines fare?
\begin{verbatim}
>>> def gen_fn():
... raise Exception('my error')
>>> caller = caller_fn()
>>> caller.send(None)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 3, in caller_fn
File "<input>", line 2, in gen_fn
Exception: my error
\end{verbatim}
This is much more useful! The stack trace shows \texttt{caller\_fn} was
delegating to \texttt{gen\_fn} when it threw the error. Even more
comforting, we can wrap the call to a sub-coroutine in an exception
handler, the same is with normal subroutines:
\begin{verbatim}
>>> def gen_fn():
... yield 1
... raise Exception('uh oh')
...
>>> def caller_fn():
... try:
... yield from gen_fn()
... except Exception as exc:
... print('caught {}'.format(exc))
...
>>> caller = caller_fn()
>>> caller.send(None)
1
>>> caller.send('hello')
caught uh oh
\end{verbatim}
So we factor logic with sub-coroutines just like with regular
subroutines. Let us factor some useful sub-coroutines from our fetcher.
We write a \texttt{read} coroutine to receive one chunk:
\begin{verbatim}
def read(sock):
f = Future()
def on_readable():