1
1
# ruff: noqa: S101
2
2
"""PyFSD client protocol."""
3
3
4
- from asyncio import Lock , create_task
4
+ from asyncio import Queue , create_task
5
5
from asyncio import sleep as asleep
6
6
from collections .abc import Awaitable
7
7
from inspect import isawaitable
34
34
break_packet ,
35
35
make_packet ,
36
36
)
37
- from pyfsd .define .utils import is_callsign_valid , str_to_float , str_to_int
37
+ from pyfsd .define .utils import (
38
+ is_callsign_valid ,
39
+ mustdone_task_keeper ,
40
+ str_to_float ,
41
+ str_to_int ,
42
+ )
38
43
from pyfsd .object .client import Client , ClientType
39
44
40
45
from . import LineProtocol
@@ -142,28 +147,123 @@ class ClientProtocol(LineProtocol):
142
147
143
148
factory : "ClientFactory"
144
149
timeout_killer_task : Optional ["Task[None]" ]
150
+ worker_task : Optional ["Task[None]" ]
151
+ worker_queue : Queue [bytes ]
145
152
transport : "Transport"
146
- tasks : set ["Task" ]
147
153
client : Optional [Client ]
148
- lock = Lock ()
149
154
150
155
def __init__ (self , factory : "ClientFactory" ) -> None :
151
156
"""Create a ClientProtocol instance."""
152
157
self .factory = factory
153
- self .tasks = set ()
154
158
self .client = None
155
159
self .timeout_killer_task = None
156
- # timeout_killer_task and transport will be initialized in connection_made.
160
+ self .worker_task = None
161
+ self .worker_queue = Queue ()
162
+ super ().__init__ ()
163
+ # timeout_killer_task and worker_task and transport will be
164
+ # initialized in connection_made.
165
+
166
+ async def handle_line_worker_func (self ) -> None :
167
+ """Worker processes line."""
168
+ result : "PyFSDHandledEventResult | PluginHandledEventResult" # noqa: UP037
169
+
170
+ while True :
171
+ line = await self .worker_queue .get ()
172
+
173
+ # First try to let plugins to process
174
+ plugin_result = await self .factory .plugin_manager .trigger_event_handlers (
175
+ "line_received_from_client" ,
176
+ (self , line ),
177
+ {},
178
+ )
179
+ if plugin_result is None : # Not handled by plugin
180
+ packet_ok , has_result = await self .handle_line (line )
181
+ result = cast (
182
+ "PyFSDHandledEventResult" ,
183
+ {
184
+ "handled_by_plugin" : False ,
185
+ "success" : packet_ok and has_result ,
186
+ "packet" : line ,
187
+ "packet_ok" : packet_ok ,
188
+ "has_result" : has_result ,
189
+ },
190
+ )
191
+ else :
192
+ result = plugin_result
193
+
194
+ self .factory .plugin_manager .trigger_event_auditers_nonblock (
195
+ "line_received_from_client" ,
196
+ (self , line , result ),
197
+ {},
198
+ )
199
+
200
+ def connection_made (self , transport : "Transport" ) -> None : # type: ignore[override]
201
+ """Initialize something after the connection is made."""
202
+ super ().connection_made (transport )
203
+ ip = self .transport .get_extra_info ("peername" )[0 ]
204
+ if ip in self .factory .blacklist :
205
+ logger .info ("Kicking %s: blacklist" , ip )
206
+ self .transport .close ()
207
+ return
208
+
209
+ self .worker_task = create_task (self .handle_line_worker_func ())
210
+ self .reset_timeout_killer ()
211
+ logger .info ("New connection from %s." , ip )
212
+ self .factory .plugin_manager .trigger_event_auditers_nonblock (
213
+ "new_connection_established" , (self ,), {}
214
+ )
215
+
216
+ def line_received (self , line : bytes ) -> None :
217
+ """Handle a line."""
218
+ self .reset_timeout_killer ()
219
+ self .worker_queue .put_nowait (line )
157
220
158
- def max_length_exceed (self , length : int ) -> None :
159
- """Called when line length exceed max length."""
160
- logger .info ("Kicking %s: max length exceeded" , self .get_description ())
161
- return super ().max_length_exceed (length )
221
+ def connection_lost (self , exc : Optional [BaseException ] = None ) -> None :
222
+ """Handle connection lost."""
223
+ if self .timeout_killer_task :
224
+ self .timeout_killer_task .cancel ()
225
+ self .timeout_killer_task = None
226
+ if self .worker_task :
227
+ self .worker_task .cancel ()
228
+ self .worker_task = None
162
229
163
- def add_task (self , task : "Task" ) -> None :
164
- """Store a task's strong reference to keep it away from disappear."""
165
- self .tasks .add (task )
166
- task .add_done_callback (self .tasks .discard )
230
+ client = None
231
+ if self .client is not None :
232
+ self .factory .broadcast (
233
+ make_packet (
234
+ (
235
+ FSDClientCommand .REMOVE_ATC
236
+ if self .client .type == "ATC"
237
+ else FSDClientCommand .REMOVE_PILOT
238
+ )
239
+ + self .client .callsign ,
240
+ self .client .cid .encode (),
241
+ ),
242
+ from_client = self .client ,
243
+ )
244
+ del self .factory .clients [self .client .callsign ]
245
+ client = self .client
246
+ logger .info (
247
+ "%s disconnected%s." ,
248
+ self .get_description (),
249
+ f" due to { exc } " if exc else "" ,
250
+ )
251
+ self .client = None
252
+
253
+ self .factory .plugin_manager .trigger_event_auditers_nonblock (
254
+ "client_disconnected" ,
255
+ (self , client ),
256
+ {},
257
+ )
258
+
259
+ def buffer_size_exceed (self , length : int ) -> None :
260
+ """Called when client exceed max buffer size."""
261
+ logger .info (
262
+ "Kicking %s: buffer size exceeded (%d)" ,
263
+ self .get_description (),
264
+ length ,
265
+ )
266
+ return super ().buffer_size_exceed (length )
167
267
168
268
def kill_after_1sec (self ) -> None :
169
269
"""Kill this client after 1 second by kill_func."""
@@ -172,7 +272,17 @@ async def kill() -> None:
172
272
await asleep (1 )
173
273
self .transport .close ()
174
274
175
- self .add_task (create_task (kill ()))
275
+ mustdone_task_keeper .add (create_task (kill ()))
276
+
277
+ def get_description (self ) -> str :
278
+ """Get text description of this client."""
279
+ if self .client is not None :
280
+ return (
281
+ cast ("str" , self .transport .get_extra_info ("peername" )[0 ])
282
+ + f" ({ self .client .callsign .decode (errors = 'replace' )} )"
283
+ )
284
+
285
+ return cast ("str" , self .transport .get_extra_info ("peername" )[0 ])
176
286
177
287
def reset_timeout_killer (self ) -> None :
178
288
"""Reset timeout killer."""
@@ -187,21 +297,6 @@ async def timeout_killer() -> None:
187
297
self .timeout_killer_task .cancel ()
188
298
self .timeout_killer_task = create_task (timeout_killer ())
189
299
190
- def connection_made (self , transport : "Transport" ) -> None : # type: ignore[override]
191
- """Initialize something after the connection is made."""
192
- super ().connection_made (transport )
193
- ip = self .transport .get_extra_info ("peername" )[0 ]
194
- if ip in self .factory .blacklist :
195
- logger .info ("Kicking %s: blacklist" , ip )
196
- self .transport .close ()
197
- return
198
-
199
- self .reset_timeout_killer ()
200
- logger .info ("New connection from %s." , ip )
201
- self .factory .plugin_manager .trigger_event_auditers_nonblock (
202
- "new_connection_established" , (self ,), {}
203
- )
204
-
205
300
def send_error (
206
301
self , errno : FSDClientError , * , env : bytes = b"" , fatal : bool = False
207
302
) -> None :
@@ -904,7 +999,7 @@ async def killer() -> None:
904
999
await asleep (1 )
905
1000
transport_to_kill .close ()
906
1001
907
- self . add_task (create_task (killer ()))
1002
+ mustdone_task_keeper . add (create_task (killer ()))
908
1003
909
1004
logger .info (
910
1005
"Kicking %s: killed by %s" ,
@@ -914,46 +1009,6 @@ async def killer() -> None:
914
1009
kill_it ()
915
1010
return True , True
916
1011
917
- def line_received (self , line : bytes ) -> None :
918
- """Handle a line."""
919
- self .reset_timeout_killer ()
920
-
921
- async def handle () -> None :
922
- result : "PyFSDHandledEventResult | PluginHandledEventResult" # noqa: UP037
923
- # First try to let plugins to process
924
- plugin_result = await self .factory .plugin_manager .trigger_event_handlers (
925
- "line_received_from_client" ,
926
- (self , line ),
927
- {},
928
- )
929
- if plugin_result is None : # Not handled by plugin
930
- packet_ok , has_result = await self .handle_line (line )
931
- result = cast (
932
- "PyFSDHandledEventResult" ,
933
- {
934
- "handled_by_plugin" : False ,
935
- "success" : packet_ok and has_result ,
936
- "packet" : line ,
937
- "packet_ok" : packet_ok ,
938
- "has_result" : has_result ,
939
- },
940
- )
941
- else :
942
- result = plugin_result
943
-
944
- self .factory .plugin_manager .trigger_event_auditers_nonblock (
945
- "line_received_from_client" ,
946
- (self , line , result ),
947
- {},
948
- )
949
-
950
- async def do_after_before_done () -> None :
951
- """Wait last task done then handle this."""
952
- async with self .lock :
953
- await handle ()
954
-
955
- self .add_task (create_task (do_after_before_done ()))
956
-
957
1012
async def handle_line (
958
1013
self ,
959
1014
byte_line : bytes ,
@@ -1054,49 +1109,3 @@ async def handle_line(
1054
1109
return await self .handle_kill (packet )
1055
1110
self .send_error (FSDClientError .SYNTAX )
1056
1111
return False , False
1057
-
1058
- def get_description (self ) -> str :
1059
- """Get text description of this client."""
1060
- if self .client is not None :
1061
- return (
1062
- cast ("str" , self .transport .get_extra_info ("peername" )[0 ])
1063
- + f" ({ self .client .callsign .decode (errors = 'replace' )} )"
1064
- )
1065
-
1066
- return cast ("str" , self .transport .get_extra_info ("peername" )[0 ])
1067
-
1068
- def connection_lost (self , exc : Optional [BaseException ] = None ) -> None :
1069
- """Handle connection lost."""
1070
- if self .timeout_killer_task :
1071
- self .timeout_killer_task .cancel ()
1072
- self .timeout_killer_task = None
1073
- for pending_task in self .tasks :
1074
- pending_task .cancel ()
1075
- client = None
1076
- if self .client is not None :
1077
- self .factory .broadcast (
1078
- make_packet (
1079
- (
1080
- FSDClientCommand .REMOVE_ATC
1081
- if self .client .type == "ATC"
1082
- else FSDClientCommand .REMOVE_PILOT
1083
- )
1084
- + self .client .callsign ,
1085
- self .client .cid .encode (),
1086
- ),
1087
- from_client = self .client ,
1088
- )
1089
- del self .factory .clients [self .client .callsign ]
1090
- client = self .client
1091
- logger .info (
1092
- "%s disconnected%s." ,
1093
- self .get_description (),
1094
- f" due to { exc } " if exc else "" ,
1095
- )
1096
- self .client = None
1097
-
1098
- self .factory .plugin_manager .trigger_event_auditers_nonblock (
1099
- "client_disconnected" ,
1100
- (self , client ),
1101
- {},
1102
- )
0 commit comments