3
3
import struct
4
4
# noinspection PyUnresolvedReferences
5
5
from typing import Dict # noqa
6
+ from typing import Set # noqa
6
7
from typing import (
7
8
Any ,
8
9
Iterable ,
79
80
80
81
class Path :
81
82
__slots__ = (
83
+ '_pending' ,
82
84
'_initiator' ,
83
85
'_responders' ,
84
86
'log' ,
@@ -93,6 +95,7 @@ def __init__(
93
95
number : int ,
94
96
attached : bool = True ,
95
97
) -> None :
98
+ self ._pending = set () # type: Set[PathClient]
96
99
self ._initiator = None # type: Optional[PathClient]
97
100
self ._responders = {} # type: Dict[ResponderAddress, PathClient]
98
101
self .log = util .get_logger ('path.{}' .format (number ))
@@ -105,7 +108,24 @@ def empty(self) -> bool:
105
108
"""
106
109
Return whether the path is empty.
107
110
"""
108
- return self ._initiator is None and len (self ._responders ) == 0
111
+ return (len (self ._pending ) == 0 and
112
+ self ._initiator is None and
113
+ len (self ._responders ) == 0 )
114
+
115
+ def add_pending (self , client : 'PathClient' ) -> None :
116
+ """
117
+ Add a :class:`PathClient` as pending to the set of pending
118
+ clients.
119
+
120
+ Arguments:
121
+ - `client`: A :class:`PathClient` instance.
122
+
123
+ Raises :exc:`ValueError` in case of a state violation on the
124
+ :class:`PathClient`.
125
+ """
126
+ if not self .attached :
127
+ raise ValueError ('Patch has been detached!' )
128
+ self ._pending .add (client )
109
129
110
130
def has_client (self , client : 'PathClient' ) -> bool :
111
131
"""
@@ -114,7 +134,13 @@ def has_client(self, client: 'PathClient') -> bool:
114
134
115
135
Arguments:
116
136
- `client`: The :class:`PathClient` instance to look for.
137
+
138
+ Raises :exc:`ValueError` in case of a state violation on the
139
+ :class:`PathClient`.
117
140
"""
141
+ if not self .attached :
142
+ raise ValueError ('Patch has been detached!' )
143
+
118
144
# Note: No need to check for an unassigned ID since the server's ID will never
119
145
# be available in the slots.
120
146
id_ = client .id
@@ -137,8 +163,13 @@ def get_initiator(self) -> 'PathClient':
137
163
"""
138
164
Return the initiator's :class:`PathClient` instance.
139
165
140
- Raises :exc:`KeyError` if there is no initiator.
166
+ Raises:
167
+ - :exc:`KeyError` if there is no initiator.
168
+ - :exc:`ValueError` in case of a state violation on the
169
+ :class:`PathClient`.
141
170
"""
171
+ if not self .attached :
172
+ raise ValueError ('Patch has been detached!' )
142
173
if self ._initiator is None :
143
174
raise KeyError ('No initiator present' )
144
175
return self ._initiator
@@ -150,11 +181,21 @@ def set_initiator(self, initiator: 'PathClient') -> Optional['PathClient']:
150
181
Arguments:
151
182
- `initiator`: A :class:`PathClient` instance.
152
183
153
- Raises :exc:`ValueError` in case of a state violation on the
154
- :class:`PathClient`.
184
+ Raises:
185
+ - :exc:`KeyError` in case the initiator was not in the set
186
+ of pending clients.
187
+ - :exc:`ValueError` in case of a state violation on the
188
+ :class:`PathClient`.
155
189
156
190
Return the previously set initiator or `None`.
157
191
"""
192
+ if not self .attached :
193
+ raise ValueError ('Patch has been detached!' )
194
+
195
+ # Remove initiator from 'pending' set
196
+ self ._pending .remove (initiator )
197
+
198
+ # Update initiator
158
199
previous_initiator = self ._initiator
159
200
self ._initiator = initiator
160
201
self .log .debug ('Set initiator {}' , initiator )
@@ -173,15 +214,25 @@ def get_responder(self, id_: ResponderAddress) -> 'PathClient':
173
214
Arguments:
174
215
- `id_`: The identifier of the responder.
175
216
176
- Raises :exc:`KeyError`: If `id_` cannot be associated to a
177
- :class:`PathClient` instance.
217
+ Raises:
218
+ - :exc:`KeyError`: If `id_` cannot be associated to a
219
+ :class:`PathClient` instance.
220
+ - :exc:`ValueError` in case of a state violation on the
221
+ :class:`PathClient`.
178
222
"""
223
+ if not self .attached :
224
+ raise ValueError ('Patch has been detached!' )
179
225
return self ._responders [id_ ]
180
226
181
227
def get_responder_ids (self ) -> Iterable [ResponderAddress ]:
182
228
"""
183
229
Return an iterable of responder identifiers (slots).
230
+
231
+ Raises :exc:`ValueError` in case of a state violation on the
232
+ :class:`PathClient`.
184
233
"""
234
+ if not self .attached :
235
+ raise ValueError ('Patch has been detached!' )
185
236
return self ._responders .keys ()
186
237
187
238
def add_responder (self , responder : 'PathClient' ) -> ResponderAddress :
@@ -193,18 +244,27 @@ def add_responder(self, responder: 'PathClient') -> ResponderAddress:
193
244
194
245
Raises:
195
246
- :exc:`SlotsFullError` if no free slot exists on the path.
247
+ - :exc:`KeyError` in case the responder was not in the set
248
+ of pending clients.
196
249
- :exc:`ValueError` in case of a state violation on the
197
- :class:`PathClient`.
250
+ :class:`PathClient` or in case the responder was not in
251
+ the list of pending clients.
198
252
199
253
Return the assigned slot identifier.
200
254
"""
255
+ if not self .attached :
256
+ raise ValueError ('Patch has been detached!' )
257
+
201
258
# Calculate slot id
202
259
id_ = len (self ._responders ) + 0x02
203
260
try :
204
261
id_ = ResponderAddress (id_ )
205
262
except ValueError as exc :
206
263
raise SlotsFullError ('No free slot on path' ) from exc
207
264
265
+ # Remove responder from 'pending' set
266
+ self ._pending .remove (responder )
267
+
208
268
# Set responder
209
269
self ._responders [id_ ] = responder
210
270
self .log .debug ('Added responder {}' , responder )
@@ -226,13 +286,31 @@ def remove_client(self, client: 'PathClient') -> None:
226
286
Arguments:
227
287
- `client`: The :class:`PathClient` instance.
228
288
229
- Raises :exc:`KeyError` in case the client provided an
230
- invalid slot identifier.
289
+ Raises:
290
+ - :exc:`KeyError` in case the client provided an invalid
291
+ slot identifier, or the client should have been but was
292
+ not in the set of pending clients.
293
+ - :exc:`ValueError` in case of a state violation on the
294
+ :class:`PathClient`.
231
295
"""
296
+ if not self .attached :
297
+ raise ValueError ('Patch has been detached!' )
298
+
232
299
if client .state == ClientState .restricted :
233
- # Client has never been authenticated. Nothing to do.
300
+ # Client has never been authenticated, so it should be in
301
+ # the 'pending' set
302
+ self ._pending .remove (client )
234
303
return
235
304
305
+ # Client should not be in the 'pending' set!
306
+ try :
307
+ self ._pending .remove (client )
308
+ except KeyError :
309
+ pass
310
+ else :
311
+ self .log .error (
312
+ 'Client {} in state {} was in the pending set!' , client , client .state )
313
+
236
314
# Remove initiator or responder
237
315
id_ = client .id
238
316
is_initiator = id_ == INITIATOR_ADDRESS
@@ -250,6 +328,24 @@ def remove_client(self, client: 'PathClient') -> None:
250
328
del self ._responders [id_ ]
251
329
self .log .debug ('Removed {}' , 'initiator' if is_initiator else 'responder' )
252
330
331
+ def clear (self ) -> None :
332
+ """
333
+ Clear an empty path.
334
+ """
335
+ if len (self ._pending ) != 0 :
336
+ self .log .error (
337
+ 'Clearing path that has pending clients: {}' ,
338
+ self ._pending )
339
+ self ._pending .clear ()
340
+ if self ._initiator is not None :
341
+ self .log .error (
342
+ 'Clearing path that has an attached initiator: {}' , self ._initiator )
343
+ self ._initiator = None
344
+ if len (self ._responders ) != 0 :
345
+ self .log .error (
346
+ 'Clearing path that has attached responders: {}' , self ._responders )
347
+ self ._responders .clear ()
348
+
253
349
254
350
class PathClient :
255
351
__slots__ = (
0 commit comments