syndesi 0.3.2__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- syndesi/adapters/adapter.py +126 -165
- syndesi/adapters/auto.py +1 -1
- syndesi/adapters/backend/adapter_backend.py +96 -63
- syndesi/adapters/backend/adapter_session.py +53 -153
- syndesi/adapters/backend/descriptors.py +3 -2
- syndesi/adapters/backend/ip_backend.py +1 -0
- syndesi/adapters/backend/serialport_backend.py +19 -13
- syndesi/adapters/backend/stop_condition_backend.py +99 -243
- syndesi/adapters/backend/visa_backend.py +7 -7
- syndesi/adapters/ip.py +6 -10
- syndesi/adapters/serialport.py +4 -5
- syndesi/adapters/stop_condition.py +47 -26
- syndesi/adapters/timeout.py +2 -2
- syndesi/adapters/visa.py +2 -2
- syndesi/cli/shell_tools.py +95 -97
- syndesi/protocols/delimited.py +16 -21
- syndesi/protocols/modbus.py +17 -14
- syndesi/protocols/raw.py +20 -16
- syndesi/protocols/scpi.py +17 -15
- syndesi/tools/backend_api.py +15 -16
- syndesi/tools/errors.py +28 -0
- syndesi/version.py +1 -1
- {syndesi-0.3.2.dist-info → syndesi-0.4.1.dist-info}/METADATA +2 -1
- {syndesi-0.3.2.dist-info → syndesi-0.4.1.dist-info}/RECORD +28 -28
- {syndesi-0.3.2.dist-info → syndesi-0.4.1.dist-info}/WHEEL +0 -0
- {syndesi-0.3.2.dist-info → syndesi-0.4.1.dist-info}/entry_points.txt +0 -0
- {syndesi-0.3.2.dist-info → syndesi-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.3.2.dist-info → syndesi-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -7,27 +7,27 @@
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import socket
|
|
10
|
-
import uuid
|
|
11
10
|
from abc import ABC, abstractmethod
|
|
12
11
|
from collections.abc import Generator
|
|
13
12
|
from dataclasses import dataclass
|
|
14
13
|
from enum import Enum
|
|
15
14
|
from multiprocessing.connection import Connection
|
|
16
15
|
from threading import Thread
|
|
17
|
-
|
|
18
|
-
from typing import Protocol
|
|
16
|
+
import time
|
|
17
|
+
from typing import Protocol, cast
|
|
18
|
+
|
|
19
|
+
from syndesi.tools.types import NumberLike
|
|
19
20
|
|
|
20
21
|
from ...tools.backend_api import AdapterBackendStatus, Fragment
|
|
21
22
|
from ...tools.log_settings import LoggerAlias
|
|
22
23
|
from .descriptors import Descriptor
|
|
23
24
|
from .stop_condition_backend import (
|
|
24
|
-
|
|
25
|
+
ContinuationBackend,
|
|
25
26
|
StopConditionBackend,
|
|
26
|
-
|
|
27
|
-
#TimeoutStopConditionBackend,
|
|
27
|
+
TotalBackend,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
|
-
from ..stop_condition import
|
|
30
|
+
from ..stop_condition import Continuation, StopConditionType
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class HasFileno(Protocol):
|
|
@@ -72,13 +72,22 @@ class AdapterDisconnected(AdapterSignal):
|
|
|
72
72
|
return self.__str__()
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
# @dataclass
|
|
76
|
+
# class AdapterReadInit(AdapterSignal):
|
|
77
|
+
# end_delay : float | None
|
|
78
|
+
|
|
79
|
+
# def __str__(self) -> str:
|
|
80
|
+
# return f"Read init (end delay : {self.end_delay})"
|
|
81
|
+
|
|
82
|
+
# def __repr__(self) -> str:
|
|
83
|
+
# return self.__str__()
|
|
84
|
+
|
|
75
85
|
@dataclass
|
|
76
|
-
class
|
|
77
|
-
|
|
78
|
-
uuid: uuid.UUID
|
|
86
|
+
class AdapterResponseTimeout(AdapterSignal):
|
|
87
|
+
identifier : int
|
|
79
88
|
|
|
80
89
|
def __str__(self) -> str:
|
|
81
|
-
return f"
|
|
90
|
+
return f"Response timeout"
|
|
82
91
|
|
|
83
92
|
def __repr__(self) -> str:
|
|
84
93
|
return self.__str__()
|
|
@@ -90,9 +99,9 @@ class AdapterReadPayload(AdapterSignal):
|
|
|
90
99
|
stop_timestamp: float
|
|
91
100
|
stop_condition_type: StopConditionType
|
|
92
101
|
previous_read_buffer_used: bool
|
|
93
|
-
response_timestamp : float
|
|
102
|
+
response_timestamp : float | None
|
|
94
103
|
# Only used by client and set by frontend
|
|
95
|
-
response_delay : float =
|
|
104
|
+
response_delay : float | None = None
|
|
96
105
|
|
|
97
106
|
def data(self) -> bytes:
|
|
98
107
|
return b"".join([f.data for f in self.fragments])
|
|
@@ -109,8 +118,17 @@ class AdapterReadPayload(AdapterSignal):
|
|
|
109
118
|
@dataclass
|
|
110
119
|
class ResponseRequest:
|
|
111
120
|
timestamp: float
|
|
112
|
-
|
|
121
|
+
identifier : int
|
|
113
122
|
|
|
123
|
+
def nmin(a : float | None, b : float | None) -> float | None:
|
|
124
|
+
if a is None and b is None:
|
|
125
|
+
return None
|
|
126
|
+
elif a is None:
|
|
127
|
+
return b
|
|
128
|
+
elif b is None:
|
|
129
|
+
return a
|
|
130
|
+
else:
|
|
131
|
+
return min(a, b)
|
|
114
132
|
|
|
115
133
|
class AdapterBackend(ABC):
|
|
116
134
|
class ThreadCommands(Enum):
|
|
@@ -118,7 +136,7 @@ class AdapterBackend(ABC):
|
|
|
118
136
|
|
|
119
137
|
class AdapterTimeoutEventOrigin(Enum):
|
|
120
138
|
TIMEOUT = 0
|
|
121
|
-
|
|
139
|
+
RESPONSE_REQUEST = 1
|
|
122
140
|
|
|
123
141
|
def __init__(self, descriptor: Descriptor) -> None:
|
|
124
142
|
"""
|
|
@@ -136,22 +154,21 @@ class AdapterBackend(ABC):
|
|
|
136
154
|
super().__init__()
|
|
137
155
|
|
|
138
156
|
# TODO : Switch to multiple stop conditios
|
|
139
|
-
self._stop_conditions: list[StopConditionBackend] = [
|
|
140
|
-
|
|
141
|
-
|
|
157
|
+
self._stop_conditions: list[StopConditionBackend] = [
|
|
158
|
+
ContinuationBackend(time=0.1)
|
|
159
|
+
]
|
|
142
160
|
self.descriptor = descriptor
|
|
143
161
|
self._thread: Thread | None = None
|
|
144
162
|
self._status = AdapterBackendStatus.DISCONNECTED
|
|
145
163
|
self._thread_commands_read, self._thread_commands_write = socket.socketpair()
|
|
146
164
|
self.backend_signal: Connection | None = None
|
|
147
165
|
self.fragments: list[Fragment] = []
|
|
148
|
-
self._start_read_timestamp: float | None = None
|
|
149
|
-
# self._data_out_queue = []
|
|
150
166
|
self._next_timeout_timestamp: float | None = None
|
|
151
167
|
# _response_time indicates if the frontend asked for a read
|
|
152
168
|
# None : No ask
|
|
153
169
|
# float : Ask for a response to happen at the specified value at max
|
|
154
170
|
self._response_request: ResponseRequest | None = None
|
|
171
|
+
self._response_request_start : float | None = None
|
|
155
172
|
|
|
156
173
|
self._first_fragment = True
|
|
157
174
|
|
|
@@ -159,7 +176,7 @@ class AdapterBackend(ABC):
|
|
|
159
176
|
# not used because of termination or length stop condition
|
|
160
177
|
self._previous_buffer = Fragment(b"", None)
|
|
161
178
|
|
|
162
|
-
self._last_write_time = time()
|
|
179
|
+
self._last_write_time = time.time()
|
|
163
180
|
|
|
164
181
|
def set_stop_conditions(self, stop_conditions: list[StopConditionBackend]) -> None:
|
|
165
182
|
"""
|
|
@@ -204,14 +221,6 @@ class AdapterBackend(ABC):
|
|
|
204
221
|
"""
|
|
205
222
|
Stop communication with the device
|
|
206
223
|
"""
|
|
207
|
-
self._logger.debug("Closing adapter and stopping read thread")
|
|
208
|
-
# self._thread_commands_write.send(self.ThreadCommands.STOP.value)
|
|
209
|
-
# if self._thread is not None and self._thread.is_alive():
|
|
210
|
-
# try:
|
|
211
|
-
# self._thread.join()
|
|
212
|
-
# except RuntimeError:
|
|
213
|
-
# # If the thread cannot be joined, then so be it
|
|
214
|
-
# pass
|
|
215
224
|
self._status = AdapterBackendStatus.DISCONNECTED
|
|
216
225
|
return True
|
|
217
226
|
|
|
@@ -223,7 +232,7 @@ class AdapterBackend(ABC):
|
|
|
223
232
|
----------
|
|
224
233
|
data : bytes or str
|
|
225
234
|
"""
|
|
226
|
-
self._last_write_time = time()
|
|
235
|
+
self._last_write_time = time.time()
|
|
227
236
|
self._logger.debug(f"Write {repr(data)}")
|
|
228
237
|
return True
|
|
229
238
|
|
|
@@ -238,9 +247,9 @@ class AdapterBackend(ABC):
|
|
|
238
247
|
def _socket_read(self) -> Fragment:
|
|
239
248
|
raise NotImplementedError
|
|
240
249
|
|
|
241
|
-
def _fragments_to_string(self, fragments : list[
|
|
250
|
+
def _fragments_to_string(self, fragments : list[Fragment]) -> str:
|
|
242
251
|
if len(fragments) > 0:
|
|
243
|
-
return '+'.join(repr(f) for f in fragments)
|
|
252
|
+
return '+'.join(repr(f.data) for f in fragments)
|
|
244
253
|
else:
|
|
245
254
|
return str([])
|
|
246
255
|
|
|
@@ -256,28 +265,26 @@ class AdapterBackend(ABC):
|
|
|
256
265
|
else:
|
|
257
266
|
self._logger.debug(f"New fragment {fragment_delta_t:+.3f} {fragment}" + (" (first)" if self._first_fragment else ""))
|
|
258
267
|
if self._status == AdapterBackendStatus.CONNECTED:
|
|
259
|
-
t = time()
|
|
268
|
+
t = time.time()
|
|
269
|
+
|
|
270
|
+
# If there's a response request, disable it if there's a timeout stop condition
|
|
271
|
+
# The stop-condition will do the job
|
|
272
|
+
|
|
273
|
+
if self._response_request is not None:
|
|
274
|
+
for stop_condition in self._stop_conditions:
|
|
275
|
+
if isinstance(stop_condition, (ContinuationBackend, TotalBackend)):
|
|
276
|
+
self._response_request = None
|
|
277
|
+
break
|
|
260
278
|
|
|
261
279
|
while True:
|
|
262
280
|
if self._first_fragment:
|
|
263
|
-
self.
|
|
281
|
+
self._first_fragment = False
|
|
282
|
+
self._read_start_timestamp = t
|
|
264
283
|
for stop_condition in self._stop_conditions:
|
|
265
284
|
stop_condition.initiate_read()
|
|
266
|
-
self._first_fragment = False
|
|
267
|
-
if self._response_request is not None:
|
|
268
|
-
received_response_in_time = (
|
|
269
|
-
t < self._response_request.timestamp
|
|
270
|
-
)
|
|
271
|
-
# The frontend asked for a response, tell it
|
|
272
|
-
yield AdapterReadInit(
|
|
273
|
-
received_response_in_time, self._response_request.uuid
|
|
274
|
-
)
|
|
275
|
-
self._response_request = None
|
|
276
|
-
|
|
277
285
|
|
|
278
286
|
stop = False
|
|
279
287
|
kept = fragment
|
|
280
|
-
self._next_timeout_timestamp
|
|
281
288
|
|
|
282
289
|
# Run each stop condition one after the other, if a stop is reached, stop evaluating
|
|
283
290
|
stop_condition_type : StopConditionType
|
|
@@ -285,23 +292,35 @@ class AdapterBackend(ABC):
|
|
|
285
292
|
stop, kept, self._previous_buffer, self._next_timeout_timestamp = \
|
|
286
293
|
stop_condition.evaluate(kept)
|
|
287
294
|
if stop:
|
|
288
|
-
stop_condition_type = stop_condition.
|
|
295
|
+
stop_condition_type = stop_condition.type()
|
|
289
296
|
break
|
|
290
297
|
|
|
291
298
|
|
|
292
|
-
if kept.data != b'':
|
|
293
|
-
|
|
299
|
+
# if kept.data != b'':
|
|
300
|
+
# self.fragments.append(kept)
|
|
301
|
+
|
|
302
|
+
self.fragments.append(kept)
|
|
294
303
|
|
|
295
304
|
if stop:
|
|
296
305
|
self._first_fragment = True
|
|
297
306
|
self._logger.debug(f"Payload {self._fragments_to_string(self.fragments)} ({stop_condition_type.value})")
|
|
307
|
+
if self._response_request_start is None or len(self.fragments) == 0:
|
|
308
|
+
response_delay = None
|
|
309
|
+
else:
|
|
310
|
+
if self.fragments[0].timestamp is None:
|
|
311
|
+
response_delay = None
|
|
312
|
+
else:
|
|
313
|
+
response_delay = self.fragments[0].timestamp - self._response_request_start
|
|
314
|
+
self._response_request_start = None
|
|
298
315
|
yield AdapterReadPayload(
|
|
299
316
|
fragments=self.fragments,
|
|
300
317
|
stop_timestamp=t,
|
|
301
318
|
stop_condition_type=stop_condition_type,
|
|
302
319
|
previous_read_buffer_used=False,
|
|
303
|
-
response_timestamp=self.fragments[0].timestamp
|
|
320
|
+
response_timestamp=self.fragments[0].timestamp,
|
|
321
|
+
response_delay=response_delay
|
|
304
322
|
)
|
|
323
|
+
self._next_timeout_timestamp = None # Experiment !
|
|
305
324
|
self.fragments.clear()
|
|
306
325
|
|
|
307
326
|
if len(self._previous_buffer.data) > 0 and stop:
|
|
@@ -315,15 +334,15 @@ class AdapterBackend(ABC):
|
|
|
315
334
|
|
|
316
335
|
return None
|
|
317
336
|
|
|
318
|
-
def start_read(self, response_time: float,
|
|
337
|
+
def start_read(self, response_time: float, identifier: int) -> None:
|
|
319
338
|
"""
|
|
320
339
|
Start a read operation. This is a signal from the frontend. The only goal is to set the response time
|
|
321
340
|
and tell the frontend if nothing arrives within a set time
|
|
322
341
|
"""
|
|
323
|
-
|
|
324
|
-
self.
|
|
325
|
-
self.
|
|
326
|
-
|
|
342
|
+
self._response_request_start = time.time()
|
|
343
|
+
self._logger.debug(f"Setup read [{identifier}] in {response_time:.3f} s")
|
|
344
|
+
self._response_request = ResponseRequest(self._response_request_start + response_time, identifier)
|
|
345
|
+
|
|
327
346
|
|
|
328
347
|
@abstractmethod
|
|
329
348
|
def is_opened(self) -> bool:
|
|
@@ -338,31 +357,44 @@ class AdapterBackend(ABC):
|
|
|
338
357
|
return self.__str__()
|
|
339
358
|
|
|
340
359
|
def on_timeout_event(self) -> AdapterSignal | None:
|
|
341
|
-
t = time()
|
|
360
|
+
t = time.time()
|
|
342
361
|
|
|
343
362
|
if self._next_timeout_origin == self.AdapterTimeoutEventOrigin.TIMEOUT:
|
|
344
363
|
self._next_timeout_timestamp = None
|
|
345
364
|
self._first_fragment = True
|
|
346
|
-
self.
|
|
365
|
+
if self._response_request_start is None or len(self.fragments) == 0:
|
|
366
|
+
response_delay = None
|
|
367
|
+
else:
|
|
368
|
+
if self.fragments[0].timestamp is not None:
|
|
369
|
+
response_delay = self.fragments[0].timestamp - self._response_request_start
|
|
370
|
+
else:
|
|
371
|
+
response_delay = None
|
|
372
|
+
self._response_request_start = None
|
|
373
|
+
|
|
347
374
|
output = AdapterReadPayload(
|
|
348
375
|
stop_timestamp=t,
|
|
349
376
|
stop_condition_type=StopConditionType.TIMEOUT,
|
|
350
377
|
previous_read_buffer_used=False,
|
|
351
378
|
fragments=self.fragments,
|
|
352
|
-
response_timestamp=self.fragments[0].timestamp
|
|
379
|
+
response_timestamp=self.fragments[0].timestamp if len(self.fragments) > 0 else None,
|
|
380
|
+
response_delay=response_delay
|
|
353
381
|
)
|
|
382
|
+
# Reset response request
|
|
383
|
+
if self._response_request is not None:
|
|
384
|
+
self._response_request = None
|
|
385
|
+
self._response_request_start = None
|
|
354
386
|
# Clear all of the fragments
|
|
355
387
|
self.fragments = []
|
|
356
388
|
return output
|
|
357
389
|
|
|
358
390
|
elif (
|
|
359
391
|
self._next_timeout_origin
|
|
360
|
-
== self.AdapterTimeoutEventOrigin.
|
|
392
|
+
== self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
|
|
361
393
|
):
|
|
362
394
|
if self._response_request is not None:
|
|
363
|
-
|
|
395
|
+
signal = AdapterResponseTimeout(self._response_request.identifier)
|
|
364
396
|
self._response_request = None
|
|
365
|
-
return
|
|
397
|
+
return signal
|
|
366
398
|
|
|
367
399
|
return None
|
|
368
400
|
|
|
@@ -370,6 +402,8 @@ class AdapterBackend(ABC):
|
|
|
370
402
|
min_timestamp = None
|
|
371
403
|
self._next_timeout_origin = None
|
|
372
404
|
|
|
405
|
+
t = time.time()
|
|
406
|
+
|
|
373
407
|
if self._next_timeout_timestamp is not None:
|
|
374
408
|
min_timestamp = self._next_timeout_timestamp
|
|
375
409
|
self._next_timeout_origin = self.AdapterTimeoutEventOrigin.TIMEOUT
|
|
@@ -381,7 +415,6 @@ class AdapterBackend(ABC):
|
|
|
381
415
|
):
|
|
382
416
|
min_timestamp = self._response_request.timestamp
|
|
383
417
|
self._next_timeout_origin = (
|
|
384
|
-
self.AdapterTimeoutEventOrigin.
|
|
418
|
+
self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
|
|
385
419
|
)
|
|
386
|
-
|
|
387
420
|
return min_timestamp
|
|
@@ -13,7 +13,8 @@ from enum import Enum
|
|
|
13
13
|
from multiprocessing.connection import Pipe, wait
|
|
14
14
|
from typing import Any, Tuple
|
|
15
15
|
|
|
16
|
-
from syndesi.adapters.backend.stop_condition_backend import StopConditionBackend
|
|
16
|
+
from syndesi.adapters.backend.stop_condition_backend import StopConditionBackend, stop_condition_to_backend
|
|
17
|
+
from syndesi.tools.errors import make_error_description
|
|
17
18
|
from syndesi.tools.types import NumberLike
|
|
18
19
|
|
|
19
20
|
from ...tools.backend_api import Action, frontend_send
|
|
@@ -21,9 +22,10 @@ from ...tools.log_settings import LoggerAlias
|
|
|
21
22
|
from .adapter_backend import (
|
|
22
23
|
AdapterBackend,
|
|
23
24
|
AdapterDisconnected,
|
|
24
|
-
AdapterReadInit,
|
|
25
|
+
#AdapterReadInit,
|
|
25
26
|
AdapterReadPayload,
|
|
26
27
|
Selectable,
|
|
28
|
+
nmin,
|
|
27
29
|
)
|
|
28
30
|
from .backend_tools import NamedConnection
|
|
29
31
|
from .descriptors import (
|
|
@@ -37,52 +39,13 @@ from .ip_backend import IPBackend
|
|
|
37
39
|
from .serialport_backend import SerialPortBackend
|
|
38
40
|
#from .stop_condition_backend import stop_condition_from_list
|
|
39
41
|
from .visa_backend import VisaBackend
|
|
40
|
-
|
|
42
|
+
from pathlib import Path
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
class TimeoutEvent(Enum):
|
|
44
46
|
MONITORING = 0
|
|
45
47
|
ADAPTER = 1
|
|
46
|
-
CONNECTIONS = 2
|
|
47
|
-
|
|
48
|
-
class TimeoutManager:
|
|
49
|
-
def __init__(self, monitoring_delay : float) -> None:
|
|
50
|
-
self._monitoring_delay = monitoring_delay
|
|
51
|
-
self._timeouts : list[Tuple[TimeoutEvent, float]] = []
|
|
52
|
-
self._monitoring_timestamp = time.time()
|
|
53
|
-
|
|
54
|
-
def get_next_timeout(self) -> Tuple[TimeoutEvent, float]:
|
|
55
|
-
t = time.time()
|
|
56
|
-
# Sort the list
|
|
57
|
-
self._timeouts.sort(key = lambda x : x[1], reverse=True)
|
|
58
|
-
# Check if the first element of the list is first or if it's the monitoring timeout
|
|
59
|
-
if len(self._timeouts) > 0 and self._timeouts[0][1] < self._monitoring_timestamp:
|
|
60
|
-
# A timeout is first
|
|
61
|
-
event, timestamp = self._timeouts.pop(0)
|
|
62
|
-
delta = max(0, timestamp - t)
|
|
63
|
-
return event, delta
|
|
64
|
-
else:
|
|
65
|
-
# Monitoring is first
|
|
66
|
-
self._set_next_monitoring_delay()
|
|
67
|
-
delta = max(0, self._monitoring_timestamp - t)
|
|
68
|
-
return TimeoutEvent.MONITORING, delta
|
|
69
|
-
|
|
70
|
-
def _set_next_monitoring_delay(self):
|
|
71
|
-
t = time.time()
|
|
72
|
-
self._monitoring_timestamp = t + self._monitoring_delay
|
|
73
|
-
|
|
74
|
-
def add_timeout_absolute(self, event : TimeoutEvent, timestamp : float):
|
|
75
|
-
self._timeouts.append((event, timestamp))
|
|
76
|
-
|
|
77
|
-
def add_timeout_relative(self, event : TimeoutEvent, delay : float):
|
|
78
|
-
self.add_timeout_absolute(event, time.time() + delay)
|
|
79
|
-
|
|
80
|
-
def has_event(self, event : TimeoutEvent) -> bool:
|
|
81
|
-
for _event, _ in self._timeouts:
|
|
82
|
-
if _event == event:
|
|
83
|
-
return True
|
|
84
|
-
return False
|
|
85
|
-
|
|
48
|
+
#CONNECTIONS = 2
|
|
86
49
|
|
|
87
50
|
def get_adapter(descriptor: Descriptor) -> AdapterBackend:
|
|
88
51
|
# The adapter doesn't exist, create it
|
|
@@ -109,6 +72,7 @@ class AdapterSession(threading.Thread):
|
|
|
109
72
|
self._logger = logging.getLogger(LoggerAlias.ADAPTER_BACKEND.value)
|
|
110
73
|
self._logger.setLevel("DEBUG")
|
|
111
74
|
self._role = None
|
|
75
|
+
self._next_monitoring_timestamp = time.time() + self.MONITORING_DELAY
|
|
112
76
|
|
|
113
77
|
# self._stop_flag = False
|
|
114
78
|
self._connections_lock = threading.Lock()
|
|
@@ -137,7 +101,8 @@ class AdapterSession(threading.Thread):
|
|
|
137
101
|
|
|
138
102
|
#self._timeout_events: list[tuple[TimeoutEvent, float]] = []
|
|
139
103
|
|
|
140
|
-
self.
|
|
104
|
+
self._read_init_id = 0
|
|
105
|
+
|
|
141
106
|
|
|
142
107
|
|
|
143
108
|
def add_connection(self, conn: NamedConnection) -> None:
|
|
@@ -151,40 +116,8 @@ class AdapterSession(threading.Thread):
|
|
|
151
116
|
with self._connections_lock:
|
|
152
117
|
if conn in self.connections:
|
|
153
118
|
conn.conn.close()
|
|
154
|
-
# self.connection_names.pop(id(conn), None)
|
|
155
119
|
self.connections.remove(conn)
|
|
156
120
|
|
|
157
|
-
# def _pop_next_timeout_event(self) -> tuple[TimeoutEvent | None, float | None]:
|
|
158
|
-
# if len(self._timeout_events) > 0:
|
|
159
|
-
# self._timeout_events.sort(key=lambda x: x[1], reverse=True)
|
|
160
|
-
# return self._timeout_events.pop(0)
|
|
161
|
-
# else:
|
|
162
|
-
# return None, None
|
|
163
|
-
|
|
164
|
-
# def _has_timeout_event(self, event: TimeoutEvent) -> bool:
|
|
165
|
-
# for event, _ in self._timeout_events:
|
|
166
|
-
# if event == event:
|
|
167
|
-
# return True
|
|
168
|
-
# return False
|
|
169
|
-
|
|
170
|
-
# def _add_timeout_event(self, event: TimeoutEvent, timestamp: float) -> None:
|
|
171
|
-
# self._timeout_events.append((event, timestamp))
|
|
172
|
-
|
|
173
|
-
# key_timeouts = [
|
|
174
|
-
# (TimeoutEvent.MONITORING, self._timeout_events[TimeoutEvent.MONITORING]),
|
|
175
|
-
# (TimeoutEvent.ADAPTER, self._timeout_events[TimeoutEvent.ADAPTER]),
|
|
176
|
-
# ] + list(self._timeout_events[TimeoutEvent.CONNECTIONS].items())
|
|
177
|
-
|
|
178
|
-
# key, timeout = None, None
|
|
179
|
-
# for k, t in key_timeouts:
|
|
180
|
-
# if t is not Ellipsis:
|
|
181
|
-
# if isinstance(t, float) or isinstance(t, int):
|
|
182
|
-
# if timeout is None or t < timeout:
|
|
183
|
-
# timeout = t
|
|
184
|
-
# key = k
|
|
185
|
-
|
|
186
|
-
# return key, timeout
|
|
187
|
-
|
|
188
121
|
def send(self, conn: NamedConnection, action: Action, *args: Any) -> None:
|
|
189
122
|
if not frontend_send(conn.conn, action, *args):
|
|
190
123
|
self._logger.warning(f"Failed to send to {conn.remote()}")
|
|
@@ -201,35 +134,23 @@ class AdapterSession(threading.Thread):
|
|
|
201
134
|
return self._adapter.is_opened()
|
|
202
135
|
|
|
203
136
|
def run(self) -> None:
|
|
204
|
-
|
|
205
137
|
while True:
|
|
206
138
|
try:
|
|
207
139
|
stop = self.loop()
|
|
208
140
|
if stop:
|
|
209
141
|
break
|
|
210
142
|
except Exception as e:
|
|
211
|
-
|
|
212
|
-
if tb is None:
|
|
213
|
-
error_message = ""
|
|
214
|
-
else:
|
|
215
|
-
while tb.tb_next is not None:
|
|
216
|
-
tb = tb.tb_next
|
|
217
|
-
_type = type(e)
|
|
218
|
-
extra_arguments = (str(e),)
|
|
219
|
-
line_no = tb.tb_lineno
|
|
220
|
-
frame = tb.tb_frame
|
|
221
|
-
filename = frame.f_code.co_filename
|
|
222
|
-
error_message = (
|
|
223
|
-
f"{_type} : {extra_arguments} {filename}:{line_no}"
|
|
224
|
-
)
|
|
143
|
+
error_message = make_error_description(e)
|
|
225
144
|
|
|
226
145
|
|
|
227
146
|
self._logger.critical(
|
|
228
147
|
f"Error in {self._adapter.descriptor} session loop : {error_message}"
|
|
229
148
|
)
|
|
230
149
|
try:
|
|
150
|
+
error_message = make_error_description(e)
|
|
151
|
+
|
|
231
152
|
for conn in self.connections:
|
|
232
|
-
frontend_send(conn.conn, Action.ERROR_GENERIC,
|
|
153
|
+
frontend_send(conn.conn, Action.ERROR_GENERIC, error_message)
|
|
233
154
|
except Exception:
|
|
234
155
|
break
|
|
235
156
|
self._logger.info(f"Exit {self._adapter.descriptor} session loop")
|
|
@@ -253,30 +174,30 @@ class AdapterSession(threading.Thread):
|
|
|
253
174
|
if adapter_fd is not None and adapter_fd.fileno() >= 0:
|
|
254
175
|
wait_list.append(adapter_fd)
|
|
255
176
|
|
|
256
|
-
next_adapter_timeout = self._adapter.get_next_timeout()
|
|
257
|
-
if (
|
|
258
|
-
not self._timeout_manager.has_event(TimeoutEvent.ADAPTER)
|
|
259
|
-
and next_adapter_timeout is not None
|
|
260
|
-
):
|
|
261
|
-
self._timeout_manager.add_timeout_absolute(TimeoutEvent.ADAPTER, next_adapter_timeout)
|
|
262
|
-
|
|
263
177
|
wait_list.append(self._new_connection_r)
|
|
178
|
+
|
|
179
|
+
timeout_timestamp = None
|
|
180
|
+
event = None
|
|
181
|
+
|
|
182
|
+
adapter_timestamp = self._adapter.get_next_timeout()
|
|
183
|
+
if adapter_timestamp is not None:
|
|
184
|
+
timeout_timestamp = nmin(timeout_timestamp, adapter_timestamp)
|
|
185
|
+
event = TimeoutEvent.ADAPTER
|
|
264
186
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
# else:
|
|
269
|
-
# timeout = timeout_timestamp - time.time()
|
|
270
|
-
|
|
187
|
+
if timeout_timestamp is None or self._next_monitoring_timestamp < timeout_timestamp:
|
|
188
|
+
timeout_timestamp = self._next_monitoring_timestamp
|
|
189
|
+
event = TimeoutEvent.MONITORING
|
|
271
190
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
191
|
+
if timeout_timestamp is None:
|
|
192
|
+
timeout = None
|
|
193
|
+
else:
|
|
194
|
+
timeout = timeout_timestamp - time.time()
|
|
275
195
|
ready = wait(wait_list, timeout=timeout) # type: ignore
|
|
276
|
-
|
|
196
|
+
t = time.time()
|
|
277
197
|
if len(ready) == 0:
|
|
278
198
|
# Timeout event
|
|
279
199
|
if event == TimeoutEvent.MONITORING:
|
|
200
|
+
self._next_monitoring_timestamp = t + self.MONITORING_DELAY
|
|
280
201
|
stop = self._monitor()
|
|
281
202
|
if stop:
|
|
282
203
|
return True
|
|
@@ -285,26 +206,17 @@ class AdapterSession(threading.Thread):
|
|
|
285
206
|
if signal is not None:
|
|
286
207
|
# The signal can be none if it has been disabled in the meantime
|
|
287
208
|
self._logger.debug(f"Adapter signal (timeout) : {signal}")
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
elif isinstance(signal, AdapterReadInit):
|
|
291
|
-
self.send_to_all(Action.ADAPTER_EVENT_READ_INIT, signal)
|
|
209
|
+
self.send_to_all(Action.ADAPTER_SIGNAL, signal)
|
|
210
|
+
|
|
292
211
|
# Main adapter loop
|
|
293
212
|
if self._new_connection_r in ready:
|
|
294
213
|
# New connection event
|
|
295
|
-
# os.read(self._new_connection_r, 1)
|
|
296
214
|
self._new_connection_r.recv()
|
|
297
215
|
# Adapter event
|
|
298
216
|
if self._adapter.selectable() in ready:
|
|
299
217
|
for signal in self._adapter.on_socket_ready():
|
|
300
218
|
self._logger.debug(f"Adapter signal (selectable) : {signal}")
|
|
301
|
-
|
|
302
|
-
# TODO : Maybe use Action.EVENT only and the signal specifies which one it is
|
|
303
|
-
self.send_to_all(Action.ADAPTER_EVENT_DISCONNECTED, signal)
|
|
304
|
-
elif isinstance(signal, AdapterReadInit):
|
|
305
|
-
self.send_to_all(Action.ADAPTER_EVENT_READ_INIT, signal)
|
|
306
|
-
elif isinstance(signal, AdapterReadPayload):
|
|
307
|
-
self.send_to_all(Action.ADAPTER_EVENT_DATA_READY, signal)
|
|
219
|
+
self.send_to_all(Action.ADAPTER_SIGNAL, signal)
|
|
308
220
|
|
|
309
221
|
for conn in self.connections:
|
|
310
222
|
if conn.conn in ready:
|
|
@@ -361,18 +273,14 @@ class AdapterSession(threading.Thread):
|
|
|
361
273
|
match action:
|
|
362
274
|
case Action.OPEN:
|
|
363
275
|
self._adapter.set_stop_conditions(
|
|
364
|
-
[
|
|
276
|
+
[stop_condition_to_backend(sc) for sc in request[1]]
|
|
365
277
|
)
|
|
366
278
|
if self._adapter.open():
|
|
367
279
|
# Success !
|
|
368
280
|
response_action = Action.OPEN
|
|
369
281
|
else:
|
|
370
282
|
response_action = Action.ERROR_FAILED_TO_OPEN
|
|
371
|
-
extra_arguments = ("",)
|
|
372
|
-
case Action.FORCE_CLOSE:
|
|
373
|
-
self._adapter.close()
|
|
374
|
-
remove_after_response = True
|
|
375
|
-
response_action, extra_arguments = Action.FORCE_CLOSE, ()
|
|
283
|
+
extra_arguments = ("",)
|
|
376
284
|
case Action.WRITE:
|
|
377
285
|
data = request[1]
|
|
378
286
|
if self._adapter.is_opened():
|
|
@@ -393,48 +301,40 @@ class AdapterSession(threading.Thread):
|
|
|
393
301
|
self._logger.error("Could not write, adapter is closed")
|
|
394
302
|
case Action.PING:
|
|
395
303
|
response_action, extra_arguments = Action.PING, ()
|
|
396
|
-
case Action.
|
|
397
|
-
|
|
398
|
-
|
|
304
|
+
case Action.SET_STOP_CONDITIONs:
|
|
305
|
+
self._adapter.set_stop_conditions([
|
|
306
|
+
stop_condition_to_backend(sc) for sc in request[1]
|
|
307
|
+
])
|
|
399
308
|
response_action, extra_arguments = (
|
|
400
|
-
Action.
|
|
309
|
+
Action.SET_STOP_CONDITIONs,
|
|
401
310
|
(),
|
|
402
311
|
)
|
|
403
312
|
case Action.FLUSHREAD:
|
|
404
313
|
self._adapter.flush_read()
|
|
405
314
|
response_action, extra_arguments = Action.FLUSHREAD, ()
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
315
|
+
case Action.START_READ:
|
|
316
|
+
response_time = float(request[1])
|
|
317
|
+
self._adapter.start_read(response_time, self._read_init_id)
|
|
318
|
+
response_action, extra_arguments = Action.START_READ, (self._read_init_id,)
|
|
319
|
+
self._read_init_id += 1
|
|
320
|
+
|
|
321
|
+
# case Action.GET_BACKEND_TIME:
|
|
322
|
+
# response_action = Action.GET_BACKEND_TIME
|
|
323
|
+
# extra_arguments = (request_timestamp, )
|
|
414
324
|
case Action.CLOSE:
|
|
325
|
+
force = request[1]
|
|
415
326
|
# Close this connection
|
|
416
327
|
remove_after_response = True
|
|
417
328
|
response_action, extra_arguments = Action.CLOSE, ()
|
|
329
|
+
if force:
|
|
330
|
+
self._adapter.close()
|
|
418
331
|
case _:
|
|
419
332
|
response_action, extra_arguments = (
|
|
420
333
|
Action.ERROR_UNKNOWN_ACTION,
|
|
421
334
|
(f"{action}",),
|
|
422
335
|
)
|
|
423
336
|
except Exception as e:
|
|
424
|
-
|
|
425
|
-
if tb is None:
|
|
426
|
-
error_message = ""
|
|
427
|
-
else:
|
|
428
|
-
while tb.tb_next is not None:
|
|
429
|
-
tb = tb.tb_next
|
|
430
|
-
_type = type(e)
|
|
431
|
-
extra_arguments = (str(e),)
|
|
432
|
-
line_no = tb.tb_lineno
|
|
433
|
-
frame = tb.tb_frame
|
|
434
|
-
filename = frame.f_code.co_filename
|
|
435
|
-
error_message = (
|
|
436
|
-
f"{_type} : {extra_arguments} {filename}:{line_no}"
|
|
437
|
-
)
|
|
337
|
+
error_message = make_error_description(e)
|
|
438
338
|
|
|
439
339
|
response_action, extra_arguments = (
|
|
440
340
|
Action.ERROR_GENERIC,
|