25
25
from ..event import EventQueue
26
26
27
27
from ...common .flag import flags
28
- from ...common .constants import DEFAULT_BACKLOG , DEFAULT_IPV6_HOSTNAME , DEFAULT_NUM_WORKERS , DEFAULT_PORT
28
+ from ...common .constants import DEFAULT_BACKLOG , DEFAULT_IPV6_HOSTNAME
29
+ from ...common .constants import DEFAULT_NUM_WORKERS , DEFAULT_PORT
29
30
30
31
logger = logging .getLogger (__name__ )
31
32
32
- # Lock shared by worker processes
33
+ # Lock shared by acceptors for
34
+ # sequential acceptance of work.
33
35
LOCK = multiprocessing .Lock ()
34
36
35
37
61
63
62
64
63
65
class AcceptorPool :
64
- """AcceptorPool pre-spawns worker processes to utilize all cores available on the system.
65
- A server socket is initialized and dispatched over a pipe to these workers.
66
- Each worker process then concurrently accepts new client connection over
67
- the initialized server socket.
66
+ """AcceptorPool is a helper class which pre-spawns `Acceptor` processes
67
+ to utilize all available CPU cores for accepting new work.
68
+
69
+ A file descriptor to consume work from is shared with `Acceptor` processes
70
+ over a pipe. Each `Acceptor` process then concurrently accepts new work over
71
+ the shared file descriptor.
68
72
69
73
Example usage:
70
74
71
- pool = AcceptorPool(flags=..., work_klass=...)
72
- try:
73
- pool.setup()
75
+ with AcceptorPool(flags=..., work_klass=...) as pool:
74
76
while True:
75
77
time.sleep(1)
76
- finally:
77
- pool.shutdown()
78
78
79
79
`work_klass` must implement `work.Work` class.
80
80
"""
@@ -84,11 +84,16 @@ def __init__(
84
84
work_klass : Type [Work ], event_queue : Optional [EventQueue ] = None ,
85
85
) -> None :
86
86
self .flags = flags
87
+ # Eventing core queue
88
+ self .event_queue : Optional [EventQueue ] = event_queue
89
+ # File descriptor to use for accepting new work
87
90
self .socket : Optional [socket .socket ] = None
91
+ # Acceptor process instances
88
92
self .acceptors : List [Acceptor ] = []
93
+ # Work queue used to share file descriptor with acceptor processes
89
94
self .work_queues : List [connection .Connection ] = []
95
+ # Work class implementation
90
96
self .work_klass = work_klass
91
- self .event_queue : Optional [EventQueue ] = event_queue
92
97
93
98
def __enter__ (self ) -> 'AcceptorPool' :
94
99
self .setup ()
@@ -102,19 +107,43 @@ def __exit__(
102
107
) -> None :
103
108
self .shutdown ()
104
109
105
- def listen (self ) -> None :
110
+ def setup (self ) -> None :
111
+ """Listen on port and setup acceptors."""
112
+ self ._listen ()
113
+ # Override flags.port to match the actual port
114
+ # we are listening upon. This is necessary to preserve
115
+ # the server port when `--port=0` is used.
116
+ assert self .socket
117
+ self .flags .port = self .socket .getsockname ()[1 ]
118
+ self ._start_acceptors ()
119
+ # Send file descriptor to all acceptor processes.
120
+ assert self .socket is not None
121
+ for index in range (self .flags .num_workers ):
122
+ send_handle (
123
+ self .work_queues [index ],
124
+ self .socket .fileno (),
125
+ self .acceptors [index ].pid ,
126
+ )
127
+ self .work_queues [index ].close ()
128
+ self .socket .close ()
129
+
130
+ def shutdown (self ) -> None :
131
+ logger .info ('Shutting down %d workers' % self .flags .num_workers )
132
+ for acceptor in self .acceptors :
133
+ acceptor .running .set ()
134
+ for acceptor in self .acceptors :
135
+ acceptor .join ()
136
+ logger .debug ('Acceptors shutdown' )
137
+
138
+ def _listen (self ) -> None :
106
139
self .socket = socket .socket (self .flags .family , socket .SOCK_STREAM )
107
140
self .socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
108
141
self .socket .bind ((str (self .flags .hostname ), self .flags .port ))
109
142
self .socket .listen (self .flags .backlog )
110
143
self .socket .setblocking (False )
111
- # Override flags.port to match the actual port
112
- # we are listening upon. This is necessary to preserve
113
- # the server port when `--port=0` is used.
114
- self .flags .port = self .socket .getsockname ()[1 ]
115
144
116
- def start_workers (self ) -> None :
117
- """Start worker processes."""
145
+ def _start_acceptors (self ) -> None :
146
+ """Start acceptor processes."""
118
147
for acceptor_id in range (self .flags .num_workers ):
119
148
work_queue = multiprocessing .Pipe ()
120
149
acceptor = Acceptor (
@@ -134,26 +163,3 @@ def start_workers(self) -> None:
134
163
self .acceptors .append (acceptor )
135
164
self .work_queues .append (work_queue [0 ])
136
165
logger .info ('Started %d workers' % self .flags .num_workers )
137
-
138
- def shutdown (self ) -> None :
139
- logger .info ('Shutting down %d workers' % self .flags .num_workers )
140
- for acceptor in self .acceptors :
141
- acceptor .running .set ()
142
- for acceptor in self .acceptors :
143
- acceptor .join ()
144
- logger .debug ('Acceptors shutdown' )
145
-
146
- def setup (self ) -> None :
147
- """Listen on port, setup workers and pass server socket to workers."""
148
- self .listen ()
149
- self .start_workers ()
150
- # Send server socket to all acceptor processes.
151
- assert self .socket is not None
152
- for index in range (self .flags .num_workers ):
153
- send_handle (
154
- self .work_queues [index ],
155
- self .socket .fileno (),
156
- self .acceptors [index ].pid ,
157
- )
158
- self .work_queues [index ].close ()
159
- self .socket .close ()
0 commit comments