2525from ..event import EventQueue
2626
2727from ...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
2930
3031logger = logging .getLogger (__name__ )
3132
32- # Lock shared by worker processes
33+ # Lock shared by acceptors for
34+ # sequential acceptance of work.
3335LOCK = multiprocessing .Lock ()
3436
3537
6163
6264
6365class 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.
6872
6973 Example usage:
7074
71- pool = AcceptorPool(flags=..., work_klass=...)
72- try:
73- pool.setup()
75+ with AcceptorPool(flags=..., work_klass=...) as pool:
7476 while True:
7577 time.sleep(1)
76- finally:
77- pool.shutdown()
7878
7979 `work_klass` must implement `work.Work` class.
8080 """
@@ -84,11 +84,16 @@ def __init__(
8484 work_klass : Type [Work ], event_queue : Optional [EventQueue ] = None ,
8585 ) -> None :
8686 self .flags = flags
87+ # Eventing core queue
88+ self .event_queue : Optional [EventQueue ] = event_queue
89+ # File descriptor to use for accepting new work
8790 self .socket : Optional [socket .socket ] = None
91+ # Acceptor process instances
8892 self .acceptors : List [Acceptor ] = []
93+ # Work queue used to share file descriptor with acceptor processes
8994 self .work_queues : List [connection .Connection ] = []
95+ # Work class implementation
9096 self .work_klass = work_klass
91- self .event_queue : Optional [EventQueue ] = event_queue
9297
9398 def __enter__ (self ) -> 'AcceptorPool' :
9499 self .setup ()
@@ -102,19 +107,43 @@ def __exit__(
102107 ) -> None :
103108 self .shutdown ()
104109
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 :
106139 self .socket = socket .socket (self .flags .family , socket .SOCK_STREAM )
107140 self .socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
108141 self .socket .bind ((str (self .flags .hostname ), self .flags .port ))
109142 self .socket .listen (self .flags .backlog )
110143 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 ]
115144
116- def start_workers (self ) -> None :
117- """Start worker processes."""
145+ def _start_acceptors (self ) -> None :
146+ """Start acceptor processes."""
118147 for acceptor_id in range (self .flags .num_workers ):
119148 work_queue = multiprocessing .Pipe ()
120149 acceptor = Acceptor (
@@ -134,26 +163,3 @@ def start_workers(self) -> None:
134163 self .acceptors .append (acceptor )
135164 self .work_queues .append (work_queue [0 ])
136165 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