syndesi 0.4.1__py3-none-any.whl → 0.4.4__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/__init__.py +16 -1
- syndesi/adapters/adapter.py +187 -108
- syndesi/adapters/auto.py +26 -15
- syndesi/adapters/backend/adapter_backend.py +57 -39
- syndesi/adapters/backend/adapter_session.py +30 -32
- syndesi/adapters/backend/backend.py +7 -2
- syndesi/adapters/backend/backend_status.py +0 -0
- syndesi/adapters/backend/backend_tools.py +1 -1
- syndesi/adapters/backend/descriptors.py +1 -1
- syndesi/adapters/backend/ip_backend.py +45 -42
- syndesi/adapters/backend/serialport_backend.py +23 -19
- syndesi/adapters/backend/stop_condition_backend.py +54 -30
- syndesi/adapters/backend/visa_backend.py +5 -5
- syndesi/adapters/ip.py +33 -27
- syndesi/adapters/ip_server.py +9 -3
- syndesi/adapters/serialport.py +20 -7
- syndesi/adapters/stop_condition.py +86 -135
- syndesi/adapters/timeout.py +2 -28
- syndesi/adapters/visa.py +11 -3
- syndesi/cli/backend_status.py +7 -9
- syndesi/cli/console.py +0 -53
- syndesi/cli/shell.py +3 -16
- syndesi/cli/shell_tools.py +0 -5
- syndesi/component.py +79 -0
- syndesi/protocols/delimited.py +7 -22
- syndesi/protocols/modbus.py +9 -8
- syndesi/protocols/protocol.py +7 -1
- syndesi/protocols/raw.py +4 -4
- syndesi/protocols/scpi.py +6 -5
- syndesi/scripts/syndesi.py +2 -4
- syndesi/tools/backend_api.py +6 -32
- syndesi/tools/backend_logger.py +0 -1
- syndesi/tools/errors.py +28 -9
- syndesi/tools/log.py +0 -87
- syndesi/tools/types.py +0 -43
- syndesi/version.py +5 -1
- syndesi-0.4.4.dist-info/METADATA +96 -0
- syndesi-0.4.4.dist-info/RECORD +61 -0
- syndesi-0.4.1.dist-info/METADATA +0 -123
- syndesi-0.4.1.dist-info/RECORD +0 -59
- {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/WHEEL +0 -0
- {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/entry_points.txt +0 -0
- {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/top_level.txt +0 -0
|
@@ -7,19 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import socket
|
|
10
|
+
import time
|
|
10
11
|
from abc import ABC, abstractmethod
|
|
11
12
|
from collections.abc import Generator
|
|
12
13
|
from dataclasses import dataclass
|
|
13
14
|
from enum import Enum
|
|
14
15
|
from multiprocessing.connection import Connection
|
|
15
16
|
from threading import Thread
|
|
16
|
-
import
|
|
17
|
-
from typing import Protocol, cast
|
|
18
|
-
|
|
19
|
-
from syndesi.tools.types import NumberLike
|
|
17
|
+
from typing import Protocol
|
|
20
18
|
|
|
21
19
|
from ...tools.backend_api import AdapterBackendStatus, Fragment
|
|
22
20
|
from ...tools.log_settings import LoggerAlias
|
|
21
|
+
from ..stop_condition import StopConditionType
|
|
23
22
|
from .descriptors import Descriptor
|
|
24
23
|
from .stop_condition_backend import (
|
|
25
24
|
ContinuationBackend,
|
|
@@ -27,8 +26,6 @@ from .stop_condition_backend import (
|
|
|
27
26
|
TotalBackend,
|
|
28
27
|
)
|
|
29
28
|
|
|
30
|
-
from ..stop_condition import Continuation, StopConditionType
|
|
31
|
-
|
|
32
29
|
|
|
33
30
|
class HasFileno(Protocol):
|
|
34
31
|
def fileno(self) -> int:
|
|
@@ -64,7 +61,7 @@ class AdapterSignal:
|
|
|
64
61
|
pass
|
|
65
62
|
|
|
66
63
|
|
|
67
|
-
class
|
|
64
|
+
class AdapterDisconnectedSignal(AdapterSignal):
|
|
68
65
|
def __str__(self) -> str:
|
|
69
66
|
return "Adapter disconnected"
|
|
70
67
|
|
|
@@ -82,12 +79,13 @@ class AdapterDisconnected(AdapterSignal):
|
|
|
82
79
|
# def __repr__(self) -> str:
|
|
83
80
|
# return self.__str__()
|
|
84
81
|
|
|
82
|
+
|
|
85
83
|
@dataclass
|
|
86
84
|
class AdapterResponseTimeout(AdapterSignal):
|
|
87
|
-
identifier
|
|
85
|
+
identifier: int
|
|
88
86
|
|
|
89
87
|
def __str__(self) -> str:
|
|
90
|
-
return
|
|
88
|
+
return "Response timeout"
|
|
91
89
|
|
|
92
90
|
def __repr__(self) -> str:
|
|
93
91
|
return self.__str__()
|
|
@@ -99,9 +97,9 @@ class AdapterReadPayload(AdapterSignal):
|
|
|
99
97
|
stop_timestamp: float
|
|
100
98
|
stop_condition_type: StopConditionType
|
|
101
99
|
previous_read_buffer_used: bool
|
|
102
|
-
response_timestamp
|
|
100
|
+
response_timestamp: float | None
|
|
103
101
|
# Only used by client and set by frontend
|
|
104
|
-
response_delay
|
|
102
|
+
response_delay: float | None = None
|
|
105
103
|
|
|
106
104
|
def data(self) -> bytes:
|
|
107
105
|
return b"".join([f.data for f in self.fragments])
|
|
@@ -118,9 +116,10 @@ class AdapterReadPayload(AdapterSignal):
|
|
|
118
116
|
@dataclass
|
|
119
117
|
class ResponseRequest:
|
|
120
118
|
timestamp: float
|
|
121
|
-
identifier
|
|
119
|
+
identifier: int
|
|
120
|
+
|
|
122
121
|
|
|
123
|
-
def nmin(a
|
|
122
|
+
def nmin(a: float | None, b: float | None) -> float | None:
|
|
124
123
|
if a is None and b is None:
|
|
125
124
|
return None
|
|
126
125
|
elif a is None:
|
|
@@ -130,6 +129,7 @@ def nmin(a : float | None, b : float | None) -> float | None:
|
|
|
130
129
|
else:
|
|
131
130
|
return min(a, b)
|
|
132
131
|
|
|
132
|
+
|
|
133
133
|
class AdapterBackend(ABC):
|
|
134
134
|
class ThreadCommands(Enum):
|
|
135
135
|
STOP = b"0"
|
|
@@ -168,7 +168,7 @@ class AdapterBackend(ABC):
|
|
|
168
168
|
# None : No ask
|
|
169
169
|
# float : Ask for a response to happen at the specified value at max
|
|
170
170
|
self._response_request: ResponseRequest | None = None
|
|
171
|
-
self._response_request_start
|
|
171
|
+
self._response_request_start: float | None = None
|
|
172
172
|
|
|
173
173
|
self._first_fragment = True
|
|
174
174
|
|
|
@@ -211,7 +211,7 @@ class AdapterBackend(ABC):
|
|
|
211
211
|
return self._previous_buffer.data == b""
|
|
212
212
|
|
|
213
213
|
@abstractmethod
|
|
214
|
-
def open(self) ->
|
|
214
|
+
def open(self) -> None:
|
|
215
215
|
"""
|
|
216
216
|
Start communication with the device
|
|
217
217
|
"""
|
|
@@ -246,10 +246,10 @@ class AdapterBackend(ABC):
|
|
|
246
246
|
@abstractmethod
|
|
247
247
|
def _socket_read(self) -> Fragment:
|
|
248
248
|
raise NotImplementedError
|
|
249
|
-
|
|
250
|
-
def _fragments_to_string(self, fragments
|
|
249
|
+
|
|
250
|
+
def _fragments_to_string(self, fragments: list[Fragment]) -> str:
|
|
251
251
|
if len(fragments) > 0:
|
|
252
|
-
return
|
|
252
|
+
return "+".join(repr(f.data) for f in fragments)
|
|
253
253
|
else:
|
|
254
254
|
return str([])
|
|
255
255
|
|
|
@@ -261,9 +261,12 @@ class AdapterBackend(ABC):
|
|
|
261
261
|
fragment_delta_t = float("nan")
|
|
262
262
|
if fragment.data == b"":
|
|
263
263
|
self.close()
|
|
264
|
-
yield
|
|
264
|
+
yield AdapterDisconnectedSignal()
|
|
265
265
|
else:
|
|
266
|
-
self._logger.debug(
|
|
266
|
+
self._logger.debug(
|
|
267
|
+
f"New fragment {fragment_delta_t:+.3f} {fragment}"
|
|
268
|
+
+ (" (first)" if self._first_fragment else "")
|
|
269
|
+
)
|
|
267
270
|
if self._status == AdapterBackendStatus.CONNECTED:
|
|
268
271
|
t = time.time()
|
|
269
272
|
|
|
@@ -272,7 +275,9 @@ class AdapterBackend(ABC):
|
|
|
272
275
|
|
|
273
276
|
if self._response_request is not None:
|
|
274
277
|
for stop_condition in self._stop_conditions:
|
|
275
|
-
if isinstance(
|
|
278
|
+
if isinstance(
|
|
279
|
+
stop_condition, (ContinuationBackend, TotalBackend)
|
|
280
|
+
):
|
|
276
281
|
self._response_request = None
|
|
277
282
|
break
|
|
278
283
|
|
|
@@ -287,15 +292,18 @@ class AdapterBackend(ABC):
|
|
|
287
292
|
kept = fragment
|
|
288
293
|
|
|
289
294
|
# Run each stop condition one after the other, if a stop is reached, stop evaluating
|
|
290
|
-
stop_condition_type
|
|
295
|
+
stop_condition_type: StopConditionType
|
|
291
296
|
for stop_condition in self._stop_conditions:
|
|
292
|
-
|
|
293
|
-
|
|
297
|
+
(
|
|
298
|
+
stop,
|
|
299
|
+
kept,
|
|
300
|
+
self._previous_buffer,
|
|
301
|
+
self._next_timeout_timestamp,
|
|
302
|
+
) = stop_condition.evaluate(kept)
|
|
294
303
|
if stop:
|
|
295
304
|
stop_condition_type = stop_condition.type()
|
|
296
305
|
break
|
|
297
306
|
|
|
298
|
-
|
|
299
307
|
# if kept.data != b'':
|
|
300
308
|
# self.fragments.append(kept)
|
|
301
309
|
|
|
@@ -303,14 +311,22 @@ class AdapterBackend(ABC):
|
|
|
303
311
|
|
|
304
312
|
if stop:
|
|
305
313
|
self._first_fragment = True
|
|
306
|
-
self._logger.debug(
|
|
307
|
-
|
|
314
|
+
self._logger.debug(
|
|
315
|
+
f"Payload {self._fragments_to_string(self.fragments)} ({stop_condition_type.value})"
|
|
316
|
+
)
|
|
317
|
+
if (
|
|
318
|
+
self._response_request_start is None
|
|
319
|
+
or len(self.fragments) == 0
|
|
320
|
+
):
|
|
308
321
|
response_delay = None
|
|
309
322
|
else:
|
|
310
323
|
if self.fragments[0].timestamp is None:
|
|
311
324
|
response_delay = None
|
|
312
325
|
else:
|
|
313
|
-
response_delay =
|
|
326
|
+
response_delay = (
|
|
327
|
+
self.fragments[0].timestamp
|
|
328
|
+
- self._response_request_start
|
|
329
|
+
)
|
|
314
330
|
self._response_request_start = None
|
|
315
331
|
yield AdapterReadPayload(
|
|
316
332
|
fragments=self.fragments,
|
|
@@ -318,9 +334,9 @@ class AdapterBackend(ABC):
|
|
|
318
334
|
stop_condition_type=stop_condition_type,
|
|
319
335
|
previous_read_buffer_used=False,
|
|
320
336
|
response_timestamp=self.fragments[0].timestamp,
|
|
321
|
-
response_delay=response_delay
|
|
337
|
+
response_delay=response_delay,
|
|
322
338
|
)
|
|
323
|
-
self._next_timeout_timestamp = None
|
|
339
|
+
self._next_timeout_timestamp = None # Experiment !
|
|
324
340
|
self.fragments.clear()
|
|
325
341
|
|
|
326
342
|
if len(self._previous_buffer.data) > 0 and stop:
|
|
@@ -341,8 +357,9 @@ class AdapterBackend(ABC):
|
|
|
341
357
|
"""
|
|
342
358
|
self._response_request_start = time.time()
|
|
343
359
|
self._logger.debug(f"Setup read [{identifier}] in {response_time:.3f} s")
|
|
344
|
-
self._response_request = ResponseRequest(
|
|
345
|
-
|
|
360
|
+
self._response_request = ResponseRequest(
|
|
361
|
+
self._response_request_start + response_time, identifier
|
|
362
|
+
)
|
|
346
363
|
|
|
347
364
|
@abstractmethod
|
|
348
365
|
def is_opened(self) -> bool:
|
|
@@ -366,7 +383,9 @@ class AdapterBackend(ABC):
|
|
|
366
383
|
response_delay = None
|
|
367
384
|
else:
|
|
368
385
|
if self.fragments[0].timestamp is not None:
|
|
369
|
-
response_delay =
|
|
386
|
+
response_delay = (
|
|
387
|
+
self.fragments[0].timestamp - self._response_request_start
|
|
388
|
+
)
|
|
370
389
|
else:
|
|
371
390
|
response_delay = None
|
|
372
391
|
self._response_request_start = None
|
|
@@ -376,8 +395,10 @@ class AdapterBackend(ABC):
|
|
|
376
395
|
stop_condition_type=StopConditionType.TIMEOUT,
|
|
377
396
|
previous_read_buffer_used=False,
|
|
378
397
|
fragments=self.fragments,
|
|
379
|
-
response_timestamp=
|
|
380
|
-
|
|
398
|
+
response_timestamp=(
|
|
399
|
+
self.fragments[0].timestamp if len(self.fragments) > 0 else None
|
|
400
|
+
),
|
|
401
|
+
response_delay=response_delay,
|
|
381
402
|
)
|
|
382
403
|
# Reset response request
|
|
383
404
|
if self._response_request is not None:
|
|
@@ -388,8 +409,7 @@ class AdapterBackend(ABC):
|
|
|
388
409
|
return output
|
|
389
410
|
|
|
390
411
|
elif (
|
|
391
|
-
self._next_timeout_origin
|
|
392
|
-
== self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
|
|
412
|
+
self._next_timeout_origin == self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
|
|
393
413
|
):
|
|
394
414
|
if self._response_request is not None:
|
|
395
415
|
signal = AdapterResponseTimeout(self._response_request.identifier)
|
|
@@ -402,8 +422,6 @@ class AdapterBackend(ABC):
|
|
|
402
422
|
min_timestamp = None
|
|
403
423
|
self._next_timeout_origin = None
|
|
404
424
|
|
|
405
|
-
t = time.time()
|
|
406
|
-
|
|
407
425
|
if self._next_timeout_timestamp is not None:
|
|
408
426
|
min_timestamp = self._next_timeout_timestamp
|
|
409
427
|
self._next_timeout_origin = self.AdapterTimeoutEventOrigin.TIMEOUT
|
|
@@ -11,19 +11,18 @@ import threading
|
|
|
11
11
|
import time
|
|
12
12
|
from enum import Enum
|
|
13
13
|
from multiprocessing.connection import Pipe, wait
|
|
14
|
-
from typing import Any
|
|
14
|
+
from typing import Any
|
|
15
15
|
|
|
16
|
-
from syndesi.adapters.backend.stop_condition_backend import
|
|
17
|
-
|
|
16
|
+
from syndesi.adapters.backend.stop_condition_backend import (
|
|
17
|
+
stop_condition_to_backend,
|
|
18
|
+
)
|
|
19
|
+
from syndesi.tools.errors import AdapterError, make_error_description
|
|
18
20
|
from syndesi.tools.types import NumberLike
|
|
19
21
|
|
|
20
22
|
from ...tools.backend_api import Action, frontend_send
|
|
21
23
|
from ...tools.log_settings import LoggerAlias
|
|
22
24
|
from .adapter_backend import (
|
|
23
25
|
AdapterBackend,
|
|
24
|
-
AdapterDisconnected,
|
|
25
|
-
#AdapterReadInit,
|
|
26
|
-
AdapterReadPayload,
|
|
27
26
|
Selectable,
|
|
28
27
|
nmin,
|
|
29
28
|
)
|
|
@@ -37,15 +36,16 @@ from .descriptors import (
|
|
|
37
36
|
)
|
|
38
37
|
from .ip_backend import IPBackend
|
|
39
38
|
from .serialport_backend import SerialPortBackend
|
|
40
|
-
|
|
39
|
+
|
|
40
|
+
# from .stop_condition_backend import stop_condition_from_list
|
|
41
41
|
from .visa_backend import VisaBackend
|
|
42
|
-
from pathlib import Path
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
class TimeoutEvent(Enum):
|
|
46
45
|
MONITORING = 0
|
|
47
46
|
ADAPTER = 1
|
|
48
|
-
#CONNECTIONS = 2
|
|
47
|
+
# CONNECTIONS = 2
|
|
48
|
+
|
|
49
49
|
|
|
50
50
|
def get_adapter(descriptor: Descriptor) -> AdapterBackend:
|
|
51
51
|
# The adapter doesn't exist, create it
|
|
@@ -99,18 +99,12 @@ class AdapterSession(threading.Thread):
|
|
|
99
99
|
self._shutdown_counter_top = None
|
|
100
100
|
self._shutdown_counter = None
|
|
101
101
|
|
|
102
|
-
#self._timeout_events: list[tuple[TimeoutEvent, float]] = []
|
|
103
|
-
|
|
104
102
|
self._read_init_id = 0
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
108
104
|
def add_connection(self, conn: NamedConnection) -> None:
|
|
109
105
|
with self._connections_lock:
|
|
110
106
|
self.connections.append(conn)
|
|
111
|
-
# os.write(self._new_connection_w, b"\x00")
|
|
112
107
|
self._new_connection_w.send(b"\x00")
|
|
113
|
-
self._logger.info(f"New client : {conn.remote()}")
|
|
114
108
|
|
|
115
109
|
def _remove_connection(self, conn: NamedConnection) -> None:
|
|
116
110
|
with self._connections_lock:
|
|
@@ -142,13 +136,12 @@ class AdapterSession(threading.Thread):
|
|
|
142
136
|
except Exception as e:
|
|
143
137
|
error_message = make_error_description(e)
|
|
144
138
|
|
|
145
|
-
|
|
146
139
|
self._logger.critical(
|
|
147
140
|
f"Error in {self._adapter.descriptor} session loop : {error_message}"
|
|
148
141
|
)
|
|
149
142
|
try:
|
|
150
143
|
error_message = make_error_description(e)
|
|
151
|
-
|
|
144
|
+
|
|
152
145
|
for conn in self.connections:
|
|
153
146
|
frontend_send(conn.conn, Action.ERROR_GENERIC, error_message)
|
|
154
147
|
except Exception:
|
|
@@ -167,15 +160,13 @@ class AdapterSession(threading.Thread):
|
|
|
167
160
|
# The wait has a timeout set by the adapter, it corresponds to the current continuation/total timeout
|
|
168
161
|
|
|
169
162
|
# Create a list of what is awaited
|
|
170
|
-
wait_list: list[Selectable] = [
|
|
171
|
-
conn.conn for conn in self.connections
|
|
172
|
-
]
|
|
163
|
+
wait_list: list[Selectable] = [conn.conn for conn in self.connections]
|
|
173
164
|
adapter_fd = self._adapter.selectable()
|
|
174
165
|
if adapter_fd is not None and adapter_fd.fileno() >= 0:
|
|
175
166
|
wait_list.append(adapter_fd)
|
|
176
167
|
|
|
177
168
|
wait_list.append(self._new_connection_r)
|
|
178
|
-
|
|
169
|
+
|
|
179
170
|
timeout_timestamp = None
|
|
180
171
|
event = None
|
|
181
172
|
|
|
@@ -183,8 +174,11 @@ class AdapterSession(threading.Thread):
|
|
|
183
174
|
if adapter_timestamp is not None:
|
|
184
175
|
timeout_timestamp = nmin(timeout_timestamp, adapter_timestamp)
|
|
185
176
|
event = TimeoutEvent.ADAPTER
|
|
186
|
-
|
|
187
|
-
if
|
|
177
|
+
|
|
178
|
+
if (
|
|
179
|
+
timeout_timestamp is None
|
|
180
|
+
or self._next_monitoring_timestamp < timeout_timestamp
|
|
181
|
+
):
|
|
188
182
|
timeout_timestamp = self._next_monitoring_timestamp
|
|
189
183
|
event = TimeoutEvent.MONITORING
|
|
190
184
|
|
|
@@ -253,7 +247,6 @@ class AdapterSession(threading.Thread):
|
|
|
253
247
|
return
|
|
254
248
|
try:
|
|
255
249
|
request = conn.conn.recv()
|
|
256
|
-
request_timestamp = time.time()
|
|
257
250
|
except (EOFError, ConnectionResetError) as e:
|
|
258
251
|
# Probably a ping or an error
|
|
259
252
|
self._logger.warning(
|
|
@@ -275,12 +268,15 @@ class AdapterSession(threading.Thread):
|
|
|
275
268
|
self._adapter.set_stop_conditions(
|
|
276
269
|
[stop_condition_to_backend(sc) for sc in request[1]]
|
|
277
270
|
)
|
|
278
|
-
|
|
271
|
+
try:
|
|
272
|
+
self._adapter.open()
|
|
273
|
+
except AdapterError as e:
|
|
279
274
|
# Success !
|
|
280
|
-
response_action = Action.OPEN
|
|
281
|
-
else:
|
|
282
275
|
response_action = Action.ERROR_FAILED_TO_OPEN
|
|
283
|
-
|
|
276
|
+
extra_arguments = (str(e),)
|
|
277
|
+
else:
|
|
278
|
+
response_action = Action.OPEN
|
|
279
|
+
extra_arguments = ("",)
|
|
284
280
|
case Action.WRITE:
|
|
285
281
|
data = request[1]
|
|
286
282
|
if self._adapter.is_opened():
|
|
@@ -302,9 +298,9 @@ class AdapterSession(threading.Thread):
|
|
|
302
298
|
case Action.PING:
|
|
303
299
|
response_action, extra_arguments = Action.PING, ()
|
|
304
300
|
case Action.SET_STOP_CONDITIONs:
|
|
305
|
-
self._adapter.set_stop_conditions(
|
|
306
|
-
stop_condition_to_backend(sc) for sc in request[1]
|
|
307
|
-
|
|
301
|
+
self._adapter.set_stop_conditions(
|
|
302
|
+
[stop_condition_to_backend(sc) for sc in request[1]]
|
|
303
|
+
)
|
|
308
304
|
response_action, extra_arguments = (
|
|
309
305
|
Action.SET_STOP_CONDITIONs,
|
|
310
306
|
(),
|
|
@@ -315,7 +311,9 @@ class AdapterSession(threading.Thread):
|
|
|
315
311
|
case Action.START_READ:
|
|
316
312
|
response_time = float(request[1])
|
|
317
313
|
self._adapter.start_read(response_time, self._read_init_id)
|
|
318
|
-
response_action, extra_arguments = Action.START_READ, (
|
|
314
|
+
response_action, extra_arguments = Action.START_READ, (
|
|
315
|
+
self._read_init_id,
|
|
316
|
+
)
|
|
319
317
|
self._read_init_id += 1
|
|
320
318
|
|
|
321
319
|
# case Action.GET_BACKEND_TIME:
|
|
@@ -98,6 +98,8 @@ def is_request(x: object) -> TypeGuard[tuple[str, object]]:
|
|
|
98
98
|
|
|
99
99
|
class Backend:
|
|
100
100
|
MONITORING_DELAY = 0.5
|
|
101
|
+
NEW_CLIENT_REQUEST_TIMEOUT = 0.5
|
|
102
|
+
|
|
101
103
|
_session_shutdown_delay: NumberLike | None
|
|
102
104
|
_backend_shutdown_delay: NumberLike | None
|
|
103
105
|
_backend_shutdown_timestamp: NumberLike | None
|
|
@@ -278,7 +280,9 @@ class Backend:
|
|
|
278
280
|
# Wait for adapter
|
|
279
281
|
# ready = wait([client.conn], timeout=0.1)
|
|
280
282
|
# selectors to work on Unix and Windows
|
|
281
|
-
ready, _, _ = select.select(
|
|
283
|
+
ready, _, _ = select.select(
|
|
284
|
+
[client.conn], [], [], self.NEW_CLIENT_REQUEST_TIMEOUT
|
|
285
|
+
)
|
|
282
286
|
if len(ready) == 0:
|
|
283
287
|
client.conn.close()
|
|
284
288
|
return
|
|
@@ -290,6 +294,7 @@ class Backend:
|
|
|
290
294
|
action = Action(adapter_request[0])
|
|
291
295
|
if action == Action.SELECT_ADAPTER:
|
|
292
296
|
adapter_descriptor = adapter_request[1]
|
|
297
|
+
self._logger.info(f"New client for {adapter_descriptor}")
|
|
293
298
|
# If the session exists but it is dead, delete it
|
|
294
299
|
if (
|
|
295
300
|
adapter_descriptor in self.adapter_sessions
|
|
@@ -299,7 +304,7 @@ class Backend:
|
|
|
299
304
|
|
|
300
305
|
if adapter_descriptor not in self.adapter_sessions:
|
|
301
306
|
# Create the adapter backend thread
|
|
302
|
-
self._logger.info(f"Creating adapter session for {adapter_descriptor}")
|
|
307
|
+
# self._logger.info(f"Creating adapter session for {adapter_descriptor}")
|
|
303
308
|
thread = AdapterSession(
|
|
304
309
|
adapter_descriptor, shutdown_delay=self._session_shutdown_delay
|
|
305
310
|
) # TODO : Put another delay here ?
|
|
File without changes
|
|
@@ -10,7 +10,13 @@ from typing import cast
|
|
|
10
10
|
|
|
11
11
|
import _socket
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from syndesi.tools.errors import AdapterConfigurationError, AdapterFailedToOpen
|
|
14
|
+
|
|
15
|
+
from ...tools.backend_api import (
|
|
16
|
+
DEFAULT_ADAPTER_OPEN_TIMEOUT,
|
|
17
|
+
AdapterBackendStatus,
|
|
18
|
+
Fragment,
|
|
19
|
+
)
|
|
14
20
|
from .adapter_backend import AdapterBackend, HasFileno
|
|
15
21
|
from .descriptors import IPDescriptor
|
|
16
22
|
|
|
@@ -55,42 +61,37 @@ class IPBackend(AdapterBackend):
|
|
|
55
61
|
def selectable(self) -> HasFileno | None:
|
|
56
62
|
return self._socket
|
|
57
63
|
|
|
58
|
-
def open(self)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
f"Invalid transport protocol : {self.descriptor.transport}"
|
|
77
|
-
)
|
|
78
|
-
try:
|
|
79
|
-
self._socket.settimeout(
|
|
80
|
-
0.5
|
|
81
|
-
) # TODO : Configure this cleanly, it has to be less than the receive timeout of the frontend
|
|
82
|
-
self._socket.connect((self.descriptor.address, self.descriptor.port))
|
|
83
|
-
except OSError as e: # TODO : Maybe change the exception ?
|
|
84
|
-
self._logger.error(f"Failed to open adapter {self.descriptor} : {e}")
|
|
64
|
+
def open(self):
|
|
65
|
+
if self._status == AdapterBackendStatus.CONNECTED:
|
|
66
|
+
self._logger.warning(f"Adapter {self.descriptor} already openend")
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if self.descriptor.port is None:
|
|
70
|
+
raise AdapterConfigurationError("Cannot open adapter without specifying a port")
|
|
71
|
+
|
|
72
|
+
if self._socket is None:
|
|
73
|
+
if self.descriptor.transport == IPDescriptor.Transport.TCP:
|
|
74
|
+
self._socket = cast(
|
|
75
|
+
_socket.socket,
|
|
76
|
+
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
|
77
|
+
)
|
|
78
|
+
elif self.descriptor.transport == IPDescriptor.Transport.UDP:
|
|
79
|
+
self._socket = cast(
|
|
80
|
+
_socket.socket, socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
81
|
+
)
|
|
85
82
|
else:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
raise AdapterConfigurationError(
|
|
84
|
+
f"Invalid transport protocol : {self.descriptor.transport}"
|
|
85
|
+
)
|
|
86
|
+
try:
|
|
87
|
+
self._socket.settimeout(DEFAULT_ADAPTER_OPEN_TIMEOUT)
|
|
88
|
+
self._socket.connect((self.descriptor.address, self.descriptor.port))
|
|
89
|
+
except OSError as e:
|
|
90
|
+
self._logger.error(f"Failed to open adapter {self.descriptor} : {e}")
|
|
91
|
+
raise AdapterFailedToOpen(str(e))
|
|
89
92
|
else:
|
|
90
|
-
self.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return output
|
|
93
|
+
self._status = AdapterBackendStatus.CONNECTED
|
|
94
|
+
self._logger.info(f"IP Adapter {self.descriptor} opened")
|
|
94
95
|
|
|
95
96
|
def close(self) -> bool:
|
|
96
97
|
super().close()
|
|
@@ -118,14 +119,16 @@ class IPBackend(AdapterBackend):
|
|
|
118
119
|
self._logger.error(f"Cannot write to closed adapter {self.descriptor}")
|
|
119
120
|
return False
|
|
120
121
|
try:
|
|
121
|
-
self._socket.send(data)
|
|
122
|
-
except (BrokenPipeError, OSError)
|
|
122
|
+
ok = self._socket.send(data) == len(data)
|
|
123
|
+
except (BrokenPipeError, OSError):
|
|
123
124
|
# Socket has been disconnected by the remote peer
|
|
124
|
-
|
|
125
|
+
ok = False
|
|
126
|
+
|
|
127
|
+
if not ok:
|
|
128
|
+
self._logger.error(f"Failed to write to adapter {self.descriptor}")
|
|
125
129
|
self.close()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return True
|
|
130
|
+
|
|
131
|
+
return ok
|
|
129
132
|
|
|
130
133
|
def _socket_read(self) -> Fragment:
|
|
131
134
|
# This function is called only if the socket was ready
|
|
@@ -140,7 +143,7 @@ class IPBackend(AdapterBackend):
|
|
|
140
143
|
|
|
141
144
|
if fragment.data == b"":
|
|
142
145
|
# Socket disconnected
|
|
143
|
-
self._logger.debug(
|
|
146
|
+
self._logger.debug("## Socket disconnected")
|
|
144
147
|
self.close()
|
|
145
148
|
|
|
146
149
|
return fragment
|