lace
Normal Python application surface for Lace.
1"""Normal Python application surface for Lace.""" 2 3from .compaction import CompactionReport 4from .connected import ConnectedPeer, PeerProofAssurance, SessionOperand 5from .errors import ( 6 InterlaceError, 7 LaceError, 8 LaceIntegrationError, 9 LaceLimitExceededError, 10 LacePeerProtocolError, 11 LacePolicyError, 12 LaceStoreError, 13 LaceTimeoutError, 14 LaceTransportError, 15) 16from .events import ( 17 Closed, 18 FixedPoint, 19 InterlaceEvent, 20 RecordEvent, 21 RecordSaved, 22 RecordSent, 23) 24from .lace import Cancellation, Lace, LaceHandle, LocalPeer 25from .operations import ( 26 BoundedInterlaceOutcome, 27 Complete, 28 Failed, 29 ForEachStream, 30 GetOutcome, 31 ListOutcome, 32 OpenBoundaryOutcome, 33 OpenInterlace, 34 StoppedByLimit, 35 StoreOutcome, 36 StoreRecordResult, 37 bounded_interlace, 38 open_interlace, 39 read_record_stream, 40) 41from ._porcelain import StoreStream, for_each, get, list_records, store, store_stream 42from .policy import FieldExclusion, PreparedPolicy, all_records, compile, compose_policies 43from .record import MAX_RECORD_BYTES, Record, parse_by 44from . import compaction as _compaction 45 46__all__ = [ 47 "BoundedInterlaceOutcome", 48 "Cancellation", 49 "Closed", 50 "CompactionReport", 51 "Complete", 52 "ConnectedPeer", 53 "Failed", 54 "FixedPoint", 55 "FieldExclusion", 56 "ForEachStream", 57 "GetOutcome", 58 "InterlaceError", 59 "InterlaceEvent", 60 "Lace", 61 "LaceError", 62 "LaceHandle", 63 "LaceIntegrationError", 64 "LaceLimitExceededError", 65 "LacePeerProtocolError", 66 "LacePolicyError", 67 "LaceStoreError", 68 "LaceTimeoutError", 69 "LaceTransportError", 70 "ListOutcome", 71 "LocalPeer", 72 "MAX_RECORD_BYTES", 73 "OpenBoundaryOutcome", 74 "OpenInterlace", 75 "PeerProofAssurance", 76 "PreparedPolicy", 77 "Record", 78 "RecordEvent", 79 "RecordSaved", 80 "RecordSent", 81 "SessionOperand", 82 "StoppedByLimit", 83 "StoreOutcome", 84 "StoreRecordResult", 85 "StoreStream", 86 "all_records", 87 "bounded_interlace", 88 "compile", 89 "compose_policies", 90 "for_each", 91 "get", 92 "list_records", 93 "open_interlace", 94 "parse_by", 95 "read_record_stream", 96 "store", 97 "store_stream", 98] 99 100# Native roots are absent on WASM targets. 101if hasattr(_compaction, "compact_filesystem"): 102 from .compaction import compact_filesystem as compact_filesystem 103 104 __all__ += ["compact_filesystem"] 105 106# Present only with the extension's `lmdb` feature. 107if hasattr(_compaction, "compact_lmdb"): 108 from .compaction import compact_lmdb as compact_lmdb 109 110 __all__ += ["compact_lmdb"]
163@dataclass(frozen=True) 164class BoundedInterlaceOutcome: 165 terminal: Terminal 166 observations: list[InterlaceEvent] 167 peer_by: str | None 168 outgoing_encoding: str | None 169 incoming_encoding: str | None
186class Cancellation: 187 """Thread-safe caller cancellation for bounded operations. 188 189 ``set()`` may be called from any thread. The operation retires its machine 190 and transport, settles started store work through position publication, 191 then raises ``concurrent.futures.CancelledError``. ``set()`` wakes blocked 192 waits directly; nothing polls it. 193 """ 194 195 def __init__(self) -> None: 196 self._lock = threading.Lock() 197 self._set = False 198 self._wakes = _WakeHub() 199 200 def set(self) -> None: 201 with self._lock: 202 if self._set: 203 return 204 self._set = True 205 self._wakes.signal() 206 207 def is_set(self) -> bool: 208 with self._lock: 209 return self._set 210 211 def _subscribe( 212 self, condition: threading.Condition | None = None 213 ) -> _WakeSubscription: 214 """A wake already signalled when cancellation was set before subscribing.""" 215 subscription = self._wakes.subscribe(condition) 216 if self.is_set(): 217 subscription.signal(raise_errors=False) 218 return subscription
Thread-safe caller cancellation for bounded operations.
set() may be called from any thread. The operation retires its machine
and transport, settles started store work through position publication,
then raises concurrent.futures.CancelledError. set() wakes blocked
waits directly; nothing polls it.
15@dataclass(frozen=True) 16class CompactionReport: 17 """What one completed compaction removed and released. 18 19 ``boundary`` is the high-water the retention sweep evaluated. 20 ``bytes_released`` counts arena bytes between the new and previous 21 committed end; ``release`` is ``"unsupported"`` when the filesystem cannot 22 release them, after the root is already consistent. 23 """ 24 25 boundary: int 26 retained: int 27 removed: int 28 bytes_released: int 29 release: Literal["ok", "unsupported"]
What one completed compaction removed and released.
boundary is the high-water the retention sweep evaluated.
bytes_released counts arena bytes between the new and previous
committed end; release is "unsupported" when the filesystem cannot
release them, after the root is already consistent.
158class ConnectedPeer: 159 """One host-owned connected participant over established ILTP byte IO.""" 160 161 def __init__(self) -> None: 162 raise TypeError("ConnectedPeer instances are created by transport factories") 163 164 @classmethod 165 def from_duplex( 166 cls, 167 io: RawIo, 168 *, 169 role: CarrierRole, 170 by_secret: str | None = None, 171 peer_proof: PeerProofAssurance | ProofAssurance = PeerProofAssurance.DISABLED, 172 channel_binding: bytes | bytearray | memoryview | None = None, 173 expected_peer_by: str | None = None, 174 stream_encodings: Sequence[str] | None = None, 175 port: str | None = None, 176 ) -> "ConnectedPeer": 177 """Adopt one duplex POSIX IO owner as a connected peer.""" 178 return cls._from_owners( 179 io, 180 io, 181 role=role, 182 by_secret=by_secret, 183 peer_proof=peer_proof, 184 channel_binding=channel_binding, 185 expected_peer_by=expected_peer_by, 186 stream_encodings=stream_encodings, 187 port=port, 188 duplex=True, 189 ) 190 191 @classmethod 192 def from_split( 193 cls, 194 reader: RawIo, 195 writer: RawIo, 196 *, 197 role: CarrierRole, 198 by_secret: str | None = None, 199 peer_proof: PeerProofAssurance | ProofAssurance = PeerProofAssurance.DISABLED, 200 channel_binding: bytes | bytearray | memoryview | None = None, 201 expected_peer_by: str | None = None, 202 stream_encodings: Sequence[str] | None = None, 203 port: str | None = None, 204 ) -> "ConnectedPeer": 205 """Adopt independent POSIX reader/writer owners as one connected peer.""" 206 return cls._from_owners( 207 reader, 208 writer, 209 role=role, 210 by_secret=by_secret, 211 peer_proof=peer_proof, 212 channel_binding=channel_binding, 213 expected_peer_by=expected_peer_by, 214 stream_encodings=stream_encodings, 215 port=port, 216 duplex=False, 217 ) 218 219 @classmethod 220 def _from_owners( 221 cls, 222 reader: RawIo, 223 writer: RawIo, 224 *, 225 role: CarrierRole, 226 by_secret: str | None, 227 peer_proof: PeerProofAssurance | ProofAssurance, 228 channel_binding: bytes | bytearray | memoryview | None, 229 expected_peer_by: str | None, 230 stream_encodings: Sequence[str] | None, 231 port: str | None, 232 duplex: bool, 233 ) -> "ConnectedPeer": 234 from ._raw_split_transport import _RawTransport 235 236 try: 237 assurance, binding, encodings = cls._validate_inputs( 238 role, by_secret, peer_proof, channel_binding, expected_peer_by, 239 stream_encodings, port, 240 ) 241 transport = _RawTransport(reader, writer, role=role, duplex=duplex) 242 except BaseException as error: 243 owners = (reader,) if reader is writer else (writer, reader) 244 _raise_after_cleanup(error, *(lambda owner=owner: owner.close() for owner in owners)) 245 # The complete transport now owns cleanup, including failed peer setup. 246 return cls._from_transport( 247 transport, by_secret=by_secret, peer_proof=assurance, 248 channel_binding=binding, expected_peer_by=expected_peer_by, 249 stream_encodings=encodings, port=port, 250 ) 251 252 @classmethod 253 def _from_transport( 254 cls, 255 transport: _ConnectedTransport, 256 *, 257 by_secret: str | None, 258 peer_proof: PeerProofAssurance, 259 channel_binding: bytes | None = None, 260 expected_peer_by: str | None = None, 261 stream_encodings: list[str] | None = None, 262 port: str | None = None, 263 ) -> "ConnectedPeer": 264 """Adopt a transport with already validated and snapshotted options.""" 265 try: 266 peer = cls.__new__(cls) 267 peer._initialize( 268 transport, by_secret=by_secret, peer_proof=peer_proof, 269 channel_binding=channel_binding, expected_peer_by=expected_peer_by, 270 stream_encodings=stream_encodings, port=port, 271 ) 272 except BaseException as error: 273 _raise_after_cleanup(error, transport.close) 274 return peer 275 276 @staticmethod 277 def _validate_inputs( 278 role: CarrierRole, 279 by_secret: str | None, 280 peer_proof: PeerProofAssurance | ProofAssurance, 281 channel_binding: bytes | bytearray | memoryview | None, 282 expected_peer_by: str | None, 283 stream_encodings: Sequence[str] | None, 284 port: str | None, 285 ) -> tuple[PeerProofAssurance, bytes | None, list[str] | None]: 286 if role not in {"listener", "connector"}: 287 raise ValueError("transport carrier_role must be listener or connector") 288 if by_secret is not None: 289 _native.Record.by_from_secret(by_secret) 290 if not isinstance(peer_proof, str): 291 raise TypeError("peer_proof must be a PeerProofAssurance or string") 292 assurance = PeerProofAssurance(peer_proof) 293 binding = None 294 if assurance is PeerProofAssurance.CHANNEL_BOUND: 295 if by_secret is None: 296 raise ValueError("channel-bound proof requires by_secret") 297 if channel_binding is None: 298 raise ValueError("channel-bound proof requires channel_binding") 299 if not isinstance(channel_binding, (bytes, bytearray, memoryview)): 300 raise TypeError("channel_binding must be bytes, bytearray, or memoryview") 301 binding = bytes(channel_binding) 302 if len(binding) != 32: 303 raise ValueError("channel_binding must contain exactly 32 bytes") 304 elif channel_binding is not None: 305 raise ValueError("channel_binding requires channel-bound proof") 306 if expected_peer_by is not None: 307 _native.validate_by(expected_peer_by) 308 if assurance is PeerProofAssurance.DISABLED: 309 raise ValueError("expected_peer_by requires enabled peer_proof") 310 encodings = None 311 if stream_encodings is not None: 312 if isinstance(stream_encodings, (str, bytes)) or not isinstance( 313 stream_encodings, Sequence 314 ): 315 raise TypeError("stream_encodings must be a sequence of token strings") 316 encodings = list(stream_encodings) 317 if not all(isinstance(token, str) for token in encodings): 318 raise TypeError("stream_encodings must be a sequence of token strings") 319 _native.validate_stream_encodings(encodings) 320 if port is not None: 321 if not isinstance(port, str): 322 raise TypeError("port must be a string") 323 _native.validate_port(port) 324 return assurance, binding, encodings 325 326 def _initialize( 327 self, 328 transport: _ConnectedTransport, 329 *, 330 by_secret: str | None, 331 peer_proof: PeerProofAssurance, 332 channel_binding: bytes | None, 333 expected_peer_by: str | None, 334 stream_encodings: list[str] | None, 335 port: str | None, 336 ) -> None: 337 nonce_seed = os.urandom(32) 338 if not any(nonce_seed): 339 raise LaceIntegrationError( 340 "OS randomness returned an all-zero carrier nonce seed" 341 ) 342 self._input: _ConnectedInput | None = _ConnectedInput( 343 transport, transport.carrier_role, by_secret, 344 peer_proof, channel_binding, nonce_seed, expected_peer_by, 345 stream_encodings, port, 346 ) 347 self._close_error: BaseException | None = None 348 349 def _claim(self) -> _ConnectedInput: 350 if self._input is None: 351 raise RuntimeError("connected peer is closed or already owned by an interlace") 352 owned, self._input = self._input, None 353 return owned 354 355 def close(self) -> None: 356 if self._input is not None: 357 owned, self._input = self._input, None 358 try: 359 owned.close() 360 except BaseException as error: 361 self._close_error = error 362 if self._close_error is not None: 363 raise self._close_error 364 365 def __enter__(self) -> "ConnectedPeer": 366 return self 367 368 def __exit__( 369 self, 370 exc_type: type[BaseException] | None, 371 exc: BaseException | None, 372 tb: TracebackType | None, 373 ) -> None: 374 try: 375 self.close() 376 except BaseException as cleanup_error: 377 failure = _combine_failures(exc, cleanup_error) 378 assert failure is not None 379 raise failure
One host-owned connected participant over established ILTP byte IO.
164 @classmethod 165 def from_duplex( 166 cls, 167 io: RawIo, 168 *, 169 role: CarrierRole, 170 by_secret: str | None = None, 171 peer_proof: PeerProofAssurance | ProofAssurance = PeerProofAssurance.DISABLED, 172 channel_binding: bytes | bytearray | memoryview | None = None, 173 expected_peer_by: str | None = None, 174 stream_encodings: Sequence[str] | None = None, 175 port: str | None = None, 176 ) -> "ConnectedPeer": 177 """Adopt one duplex POSIX IO owner as a connected peer.""" 178 return cls._from_owners( 179 io, 180 io, 181 role=role, 182 by_secret=by_secret, 183 peer_proof=peer_proof, 184 channel_binding=channel_binding, 185 expected_peer_by=expected_peer_by, 186 stream_encodings=stream_encodings, 187 port=port, 188 duplex=True, 189 )
Adopt one duplex POSIX IO owner as a connected peer.
191 @classmethod 192 def from_split( 193 cls, 194 reader: RawIo, 195 writer: RawIo, 196 *, 197 role: CarrierRole, 198 by_secret: str | None = None, 199 peer_proof: PeerProofAssurance | ProofAssurance = PeerProofAssurance.DISABLED, 200 channel_binding: bytes | bytearray | memoryview | None = None, 201 expected_peer_by: str | None = None, 202 stream_encodings: Sequence[str] | None = None, 203 port: str | None = None, 204 ) -> "ConnectedPeer": 205 """Adopt independent POSIX reader/writer owners as one connected peer.""" 206 return cls._from_owners( 207 reader, 208 writer, 209 role=role, 210 by_secret=by_secret, 211 peer_proof=peer_proof, 212 channel_binding=channel_binding, 213 expected_peer_by=expected_peer_by, 214 stream_encodings=stream_encodings, 215 port=port, 216 duplex=False, 217 )
Adopt independent POSIX reader/writer owners as one connected peer.
125@dataclass(frozen=True) 126class Failed: 127 error: InterlaceError 128 kind: Literal["failed"] = "failed"
52@dataclass(frozen=True) 53class FieldExclusion: 54 """Records carrying a matching ``Field`` occurrence; ``None`` matches anything.""" 55 name: str | None = None 56 index: str | None = None 57 value: str | None = None
Records carrying a matching Field occurrence; None matches anything.
528class ForEachStream: 529 """Observer stream: records saved to the private observer, plus boundaries.""" 530 531 def __init__(self, opened: OpenInterlace) -> None: 532 self._opened = opened 533 534 def close(self) -> None: 535 self._opened.close() 536 537 def next_event(self, *, timeout: float | None = None) -> InterlaceEvent | None: 538 timeout = _validate_timeout(timeout) 539 deadline = None if timeout is None else time.monotonic() + timeout 540 while True: 541 remaining = None if deadline is None else max(0.0, deadline - time.monotonic()) 542 event = self._opened.next_event(timeout=remaining) 543 if ( 544 event is None 545 or isinstance(event, (FixedPoint, Closed)) 546 or (isinstance(event, RecordSaved) and event.position == "first") 547 ): 548 return event 549 550 def __enter__(self) -> "ForEachStream": 551 return self 552 553 def __exit__( 554 self, 555 exc_type: type[BaseException] | None, 556 exc: BaseException | None, 557 tb: TracebackType | None, 558 ) -> None: 559 self._opened.__exit__(exc_type, exc, tb) 560 561 def __iter__(self) -> Iterator[InterlaceEvent]: 562 return self 563 564 def __next__(self) -> InterlaceEvent: 565 observation = self.next_event() 566 if observation is None: 567 raise StopIteration 568 return observation
Observer stream: records saved to the private observer, plus boundaries.
537 def next_event(self, *, timeout: float | None = None) -> InterlaceEvent | None: 538 timeout = _validate_timeout(timeout) 539 deadline = None if timeout is None else time.monotonic() + timeout 540 while True: 541 remaining = None if deadline is None else max(0.0, deadline - time.monotonic()) 542 event = self._opened.next_event(timeout=remaining) 543 if ( 544 event is None 545 or isinstance(event, (FixedPoint, Closed)) 546 or (isinstance(event, RecordSaved) and event.position == "first") 547 ): 548 return event
156@dataclass(frozen=True) 157class GetOutcome: 158 terminal: Terminal 159 observations: list[InterlaceEvent] 160 record: Record | None
36class InterlaceError(LaceError): 37 category: ClassVar[ErrorCategory] = "integration" 38 39 def __init__( 40 self, 41 message: str, 42 *, 43 position: ParticipantPosition | None = None, 44 record_hash: str | None = None, 45 phase: TimeoutPhase | None = None, 46 ) -> None: 47 super().__init__(message) 48 self.message = message 49 self.position = position 50 self.record_hash = record_hash 51 self.phase = phase
Base class for Lace binding errors.
39 def __init__( 40 self, 41 message: str, 42 *, 43 position: ParticipantPosition | None = None, 44 record_hash: str | None = None, 45 phase: TimeoutPhase | None = None, 46 ) -> None: 47 super().__init__(message) 48 self.message = message 49 self.position = position 50 self.record_hash = record_hash 51 self.phase = phase
245class Lace: 246 """Non-clonable Python owner of one adopted store and its publication state.""" 247 248 def __init__( 249 self, 250 store: _StoreOwner, 251 *, 252 position_may_advance_externally: bool, 253 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 254 _adoption_token: object | None = None, 255 ): 256 if _adoption_token is not _ADOPTION_TOKEN: 257 raise TypeError( 258 "construct Lace with a built-in store constructor, or use lace.advanced.with_store" 259 ) 260 if type(position_may_advance_externally) is not bool: 261 raise TypeError("position_may_advance_externally must be a bool") 262 self._store = store 263 self._position_may_advance_externally = position_may_advance_externally 264 try: 265 if isinstance(operand_cache, bool) or not isinstance(operand_cache, int): 266 raise TypeError("operand_cache must be an int") 267 if operand_cache < 0: 268 raise ValueError("operand_cache must not be negative") 269 # Peer operands held by hash, shared by this Lace's connected operations. 270 self._operand_cache = _native.OperandCache(operand_cache) 271 try: 272 initial_position = store.current_position() 273 except Exception as error: 274 if isinstance(error, LaceError): 275 raise 276 raise LaceStoreError(str(error)) from error 277 self._closed = False 278 self._close_complete = False 279 self._close_error: BaseException | None = None 280 # Registration order is close order. 281 self._open_interlaces: dict[OpenInterlace, None] = {} 282 self._mutation_lock = threading.Lock() 283 self._position_lock = threading.Lock() 284 self._latest_store_position = initial_position 285 self._position_subscriptions: set[_StorePositionSubscription] = set() 286 self._work_condition = threading.Condition() 287 self._wakes = _WakeHub() 288 self._active_store_work: set[PendingWork] = set() 289 self._unacknowledged_work_errors: list[BaseException] = [] 290 self._terminal_failure: BaseException | None = None 291 self._next_poll_at = time.monotonic() + _REMOTE_POSITION_POLL_INTERVAL_SECONDS 292 self._poll_in_flight = False 293 except BaseException as error: 294 _raise_after_cleanup(error, self._store_close) 295 296 @classmethod 297 def _adopt( 298 cls, 299 store: _StoreOwner, 300 *, 301 position_may_advance_externally: bool, 302 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 303 ) -> "Lace": 304 return cls( 305 store, 306 position_may_advance_externally=position_may_advance_externally, 307 operand_cache=operand_cache, 308 _adoption_token=_ADOPTION_TOKEN, 309 ) 310 311 @classmethod 312 def memory( 313 cls, 314 records: Record | Iterable[Record] | None = None, 315 *, 316 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 317 ) -> "Lace": 318 return cls._with_initial_records( 319 cls._adopt( 320 _indexed_memory_owner(), 321 position_may_advance_externally=False, 322 operand_cache=operand_cache, 323 ), 324 records, 325 ) 326 327 if hasattr(_native.NativeStore, "filesystem"): 328 @classmethod 329 def filesystem( 330 cls, 331 path: str | os.PathLike[str], 332 records: Record | Iterable[Record] | None = None, 333 *, 334 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 335 ) -> "Lace": 336 return cls._with_initial_records( 337 cls._adopt( 338 _blocking_native_owner(_native.NativeStore.filesystem, path), 339 position_may_advance_externally=False, 340 operand_cache=operand_cache, 341 ), 342 records, 343 ) 344 345 if hasattr(_native.NativeStore, "lmdb"): 346 @classmethod 347 def lmdb( 348 cls, 349 path: str | os.PathLike[str], 350 *, 351 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 352 ) -> "Lace": 353 return cls._adopt( 354 _blocking_native_owner(_native.NativeStore.lmdb, path), 355 position_may_advance_externally=True, 356 operand_cache=operand_cache, 357 ) 358 359 @staticmethod 360 def _with_initial_records( 361 lace: "Lace", records: Record | Iterable[Record] | None 362 ) -> "Lace": 363 if records is None: 364 return lace 365 try: 366 from .operations import Failed, StoppedByLimit 367 from ._porcelain import store 368 369 outcome = store(lace, records) 370 if isinstance(outcome.terminal, Failed): 371 raise outcome.terminal.error 372 if isinstance(outcome.terminal, StoppedByLimit): 373 raise LaceLimitExceededError("initial record admission stopped by a limit") 374 return lace 375 except BaseException as error: 376 _raise_after_cleanup(error, lace.close) 377 378 def handle(self) -> LaceHandle: 379 self._raise_if_unavailable() 380 return LaceHandle(self) 381 382 def __reduce_ex__(self, protocol: SupportsIndex) -> NoReturn: 383 # copy, deepcopy and pickle all reduce through this. 384 raise TypeError("Lace is non-clonable; clone Lace.handle() instead") 385 386 def _register_open_interlace(self, interlace: OpenInterlace) -> None: 387 with self._work_condition: 388 self._raise_if_unavailable_locked() 389 self._open_interlaces[interlace] = None 390 391 def _unregister_open_interlace(self, interlace: OpenInterlace) -> None: 392 with self._work_condition: 393 self._open_interlaces.pop(interlace, None) 394 395 def _subscribe_wakes( 396 self, condition: threading.Condition | None = None 397 ) -> _WakeSubscription: 398 with self._work_condition: 399 self._raise_if_unavailable_locked() 400 return self._wakes.subscribe(condition) 401 402 def _subscribe_store_position( 403 self, condition: threading.Condition 404 ) -> _StorePositionSubscription: 405 subscription = _StorePositionSubscription(self, condition) 406 self._wakes.attach(subscription) 407 with self._work_condition: 408 try: 409 self._raise_if_unavailable_locked() 410 except BaseException: 411 subscription.close() 412 raise 413 with self._position_lock: 414 self._position_subscriptions.add(subscription) 415 return subscription 416 417 def _unsubscribe_store_position( 418 self, subscription: _StorePositionSubscription 419 ) -> None: 420 with self._position_lock: 421 self._position_subscriptions.discard(subscription) 422 423 def _raise_if_unavailable_locked(self) -> None: 424 if self._terminal_failure is not None: 425 raise self._terminal_failure 426 if self._closed: 427 raise RuntimeError("Lace is closed") 428 429 def _raise_if_unavailable(self) -> None: 430 with self._work_condition: 431 self._raise_if_unavailable_locked() 432 433 def _poison(self, error: BaseException) -> BaseException: 434 with self._work_condition: 435 if self._terminal_failure is None: 436 for execution in self._active_store_work: 437 execution.prevent_start(error) 438 self._terminal_failure = error 439 self._poll_in_flight = False 440 retained = self._terminal_failure 441 self._work_condition.notify_all() 442 self._wakes.signal() 443 return retained 444 445 def _latest_position(self) -> int: 446 with self._work_condition: 447 if self._terminal_failure is not None: 448 raise self._terminal_failure 449 with self._position_lock: 450 return self._latest_store_position 451 452 def _publish_store_position(self, position: int) -> None: 453 """Retain and signal one confirmed position while the mutation gate is held.""" 454 with self._work_condition: 455 if self._terminal_failure is not None: 456 raise self._terminal_failure 457 with self._position_lock: 458 if position < self._latest_store_position: 459 failure = LaceIntegrationError( 460 f"store position regressed from {self._latest_store_position} to {position}" 461 ) 462 subscriptions: tuple[_StorePositionSubscription, ...] = () 463 elif position == self._latest_store_position: 464 return 465 else: 466 failure = None 467 self._latest_store_position = position 468 subscriptions = tuple(self._position_subscriptions) 469 470 signal_error: BaseException | None = None 471 for subscription in subscriptions: 472 try: 473 subscription.signal() 474 except BaseException as error: 475 signal_error = _combine_failures(signal_error, error) 476 if signal_error is not None: 477 failure = LaceIntegrationError( 478 f"failed to publish confirmed store position: {signal_error}" 479 ) 480 failure.__cause__ = signal_error 481 if failure is None: 482 return 483 retained = self._poison(failure) 484 raise retained 485 486 def _begin_store_work(self, execution: PendingWork) -> None: 487 with self._work_condition: 488 self._raise_if_unavailable_locked() 489 self._active_store_work.add(execution) 490 491 def _finish_store_work(self, execution: PendingWork) -> None: 492 with self._work_condition: 493 self._active_store_work.discard(execution) 494 self._work_condition.notify_all() 495 496 def _retain_store_work_error(self, error: BaseException) -> None: 497 with self._work_condition: 498 self._unacknowledged_work_errors.append(error) 499 self._work_condition.notify_all() 500 501 def _external_position_poll_deadline(self, now: float) -> float | None: 502 with self._work_condition: 503 self._raise_if_unavailable_locked() 504 if not self._position_may_advance_externally: 505 return None 506 if self._poll_in_flight and self._next_poll_at <= now: 507 return now + _REMOTE_POSITION_POLL_INTERVAL_SECONDS 508 return self._next_poll_at 509 510 def _start_due_external_position_refresh(self, now: float) -> bool: 511 from ._store_execution import PendingWork 512 513 with self._work_condition: 514 self._raise_if_unavailable_locked() 515 if ( 516 not self._position_may_advance_externally 517 or self._poll_in_flight 518 or now < self._next_poll_at 519 ): 520 return False 521 execution = PendingWork.refresh(self) 522 self._poll_in_flight = True 523 self._next_poll_at = now + _REMOTE_POSITION_POLL_INTERVAL_SECONDS 524 self._active_store_work.add(execution) 525 try: 526 execution.start_thread() 527 except BaseException as error: 528 failure = LaceIntegrationError( 529 f"failed to start external store-position probe: {error}" 530 ) 531 failure.__cause__ = error 532 with self._work_condition: 533 # An interrupted start may already have handed the probe to 534 # its worker. Keep that owner until publication/settlement. 535 if execution.channel.claim(): 536 self._active_store_work.discard(execution) 537 self._poll_in_flight = False 538 self._next_poll_at = ( 539 time.monotonic() + _REMOTE_POSITION_POLL_INTERVAL_SECONDS 540 ) 541 if self._terminal_failure is None: 542 for pending in self._active_store_work: 543 pending.prevent_start(failure) 544 self._terminal_failure = failure 545 retained = self._terminal_failure 546 self._work_condition.notify_all() 547 self._wakes.signal() 548 if not isinstance(error, Exception): 549 raise 550 raise retained 551 return True 552 553 def _finish_external_position_refresh( 554 self, 555 execution: PendingWork, 556 failure: BaseException | None, 557 ) -> None: 558 with self._work_condition: 559 self._active_store_work.discard(execution) 560 self._poll_in_flight = False 561 self._next_poll_at = ( 562 time.monotonic() + _REMOTE_POSITION_POLL_INTERVAL_SECONDS 563 ) 564 if failure is not None and self._terminal_failure is None: 565 for pending in self._active_store_work: 566 pending.prevent_start(failure) 567 self._terminal_failure = failure 568 self._work_condition.notify_all() 569 self._wakes.signal() 570 571 def _settle_external_position_poll(self) -> None: 572 """Finish the one Lace-owned probe before an operation releases this Lace.""" 573 subscription = self._wakes.subscribe() 574 failure: BaseException | None = self._terminal_failure 575 try: 576 while True: 577 try: 578 # Publish retired predecessors before waiting on a queued 579 # probe; never consume another live operation's response. 580 self._drain_store_work(wait=False) 581 self._finish_ready_position_refreshes() 582 with self._work_condition: 583 refresh_pending = any( 584 execution.is_refresh for execution in self._active_store_work 585 ) 586 failure = _combine_failures(failure, self._terminal_failure) 587 if not refresh_pending: 588 break 589 subscription.wait() 590 except BaseException as error: 591 failure = _combine_failures(failure, error) 592 finally: 593 try: 594 subscription.close() 595 except BaseException as error: 596 failure = _combine_failures(failure, error) 597 if failure is not None: 598 raise failure 599 600 def _has_ready_store_work(self) -> bool: 601 with self._work_condition: 602 return any( 603 (execution.is_refresh or execution.retired) and execution.ready() 604 for execution in self._active_store_work 605 ) 606 607 def _finish_ready_position_refreshes(self) -> bool: 608 """Publish ready Lace-local refreshes without taking another operation's work.""" 609 with self._work_condition: 610 refreshes = tuple( 611 execution 612 for execution in self._active_store_work 613 if execution.is_refresh 614 ) 615 progressed = False 616 for refresh in refreshes: 617 if refresh.finished or not refresh.ready(): 618 continue 619 refresh.finish(self, wait=False) 620 progressed = refresh.finished or progressed 621 return progressed 622 623 def _drain_store_work( 624 self, 625 *, 626 wait: bool, 627 excluding: set[PendingWork] | None = None, 628 report_errors: bool = False, 629 ) -> None: 630 excluded = excluding or set() 631 while True: 632 with self._work_condition: 633 executions = tuple( 634 execution 635 for execution in self._active_store_work 636 if execution not in excluded 637 ) 638 if not executions: 639 break 640 progressed = False 641 for execution in executions: 642 if not wait and (not execution.retired or not execution.ready()): 643 continue 644 execution.finish(self, wait=wait) 645 progressed = True 646 execution.retain_late_failure(self) 647 if not wait: 648 break 649 if not progressed: 650 raise RuntimeError("active StoreWork could not be drained") 651 if report_errors and self._unacknowledged_work_errors: 652 failure: BaseException | None = None 653 for error in self._unacknowledged_work_errors: 654 failure = _combine_failures(failure, error) 655 self._unacknowledged_work_errors.clear() 656 if failure is not None: 657 raise failure 658 659 def _wait_for_store_work(self) -> None: 660 subscription = self._wakes.subscribe() 661 failure: BaseException | None = None 662 try: 663 while True: 664 try: 665 with self._work_condition: 666 executions = tuple(self._active_store_work) 667 if not executions: 668 break 669 progressed = False 670 for execution in executions: 671 if not execution.ready(): 672 continue 673 try: 674 error = execution.finish(self, wait=False) 675 if execution.executed: 676 failure = _combine_failures(failure, error) 677 finally: 678 if execution.finished: 679 execution.retain_late_failure(self) 680 progressed = True 681 if not progressed: 682 subscription.wait() 683 except BaseException as error: 684 failure = _combine_failures(failure, error) 685 for retained_error in self._unacknowledged_work_errors: 686 failure = _combine_failures(failure, retained_error) 687 self._unacknowledged_work_errors.clear() 688 finally: 689 try: 690 subscription.close() 691 except BaseException as error: 692 failure = _combine_failures(failure, error) 693 if failure is not None: 694 raise failure 695 696 def _store_close(self) -> None: 697 try: 698 self._store.close() 699 except LaceError: 700 raise 701 except BaseException as error: 702 raise LaceStoreError(str(error)) from error 703 704 def close(self) -> None: 705 # Every claimed operation is released here, whatever interrupts close. 706 claimed: list[OpenInterlace] = [] 707 try: 708 self._close_claimed(claimed) 709 finally: 710 for interlace in claimed: 711 interlace._release() 712 713 def _close_claimed(self, claimed: list[OpenInterlace]) -> None: 714 with self._work_condition: 715 if self._closed: 716 while not self._close_complete: 717 try: 718 self._work_condition.wait() 719 except BaseException as error: 720 self._close_error = _combine_failures(self._close_error, error) 721 if self._close_error is not None: 722 raise self._close_error 723 return 724 # Claim every live operation before the participant lifecycle 725 # changes; one that another caller is using makes close fail busy. 726 for interlace in list(self._open_interlaces): 727 interlace._claim() 728 claimed.append(interlace) 729 # Stop queued calls before the close cut; already-executing calls 730 # still own their mandatory refresh and publication interval. 731 for execution in self._active_store_work: 732 execution.prevent_start(RuntimeError("Lace is closed")) 733 # This is the close linearization point. New work and operation 734 # registration fail, while counted work retains the store. 735 self._closed = True 736 failure: BaseException | None = self._terminal_failure 737 try: 738 for interlace in claimed: 739 try: 740 interlace._finish() 741 except BaseException as error: 742 failure = _combine_failures(failure, error) 743 with self._work_condition: 744 self._open_interlaces.clear() 745 try: 746 self._wait_for_store_work() 747 except BaseException as error: 748 failure = _combine_failures(failure, error) 749 failure = _combine_failures(failure, self._terminal_failure) 750 try: 751 self._store_close() 752 except BaseException as error: 753 failure = _combine_failures(failure, error) 754 failure = _combine_failures(failure, self._terminal_failure) 755 finally: 756 with self._work_condition: 757 self._close_error = _combine_failures(failure, self._close_error) 758 self._close_complete = True 759 self._work_condition.notify_all() 760 if self._close_error is not None: 761 raise self._close_error 762 763 def __enter__(self) -> "Lace": 764 return self 765 766 def __exit__( 767 self, 768 exc_type: type[BaseException] | None, 769 exc: BaseException | None, 770 tb: TracebackType | None, 771 ) -> None: 772 try: 773 self.close() 774 except BaseException as cleanup_error: 775 if exc is None: 776 raise 777 _combine_failures(exc, cleanup_error) 778 raise exc
Non-clonable Python owner of one adopted store and its publication state.
248 def __init__( 249 self, 250 store: _StoreOwner, 251 *, 252 position_may_advance_externally: bool, 253 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 254 _adoption_token: object | None = None, 255 ): 256 if _adoption_token is not _ADOPTION_TOKEN: 257 raise TypeError( 258 "construct Lace with a built-in store constructor, or use lace.advanced.with_store" 259 ) 260 if type(position_may_advance_externally) is not bool: 261 raise TypeError("position_may_advance_externally must be a bool") 262 self._store = store 263 self._position_may_advance_externally = position_may_advance_externally 264 try: 265 if isinstance(operand_cache, bool) or not isinstance(operand_cache, int): 266 raise TypeError("operand_cache must be an int") 267 if operand_cache < 0: 268 raise ValueError("operand_cache must not be negative") 269 # Peer operands held by hash, shared by this Lace's connected operations. 270 self._operand_cache = _native.OperandCache(operand_cache) 271 try: 272 initial_position = store.current_position() 273 except Exception as error: 274 if isinstance(error, LaceError): 275 raise 276 raise LaceStoreError(str(error)) from error 277 self._closed = False 278 self._close_complete = False 279 self._close_error: BaseException | None = None 280 # Registration order is close order. 281 self._open_interlaces: dict[OpenInterlace, None] = {} 282 self._mutation_lock = threading.Lock() 283 self._position_lock = threading.Lock() 284 self._latest_store_position = initial_position 285 self._position_subscriptions: set[_StorePositionSubscription] = set() 286 self._work_condition = threading.Condition() 287 self._wakes = _WakeHub() 288 self._active_store_work: set[PendingWork] = set() 289 self._unacknowledged_work_errors: list[BaseException] = [] 290 self._terminal_failure: BaseException | None = None 291 self._next_poll_at = time.monotonic() + _REMOTE_POSITION_POLL_INTERVAL_SECONDS 292 self._poll_in_flight = False 293 except BaseException as error: 294 _raise_after_cleanup(error, self._store_close)
311 @classmethod 312 def memory( 313 cls, 314 records: Record | Iterable[Record] | None = None, 315 *, 316 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 317 ) -> "Lace": 318 return cls._with_initial_records( 319 cls._adopt( 320 _indexed_memory_owner(), 321 position_may_advance_externally=False, 322 operand_cache=operand_cache, 323 ), 324 records, 325 )
328 @classmethod 329 def filesystem( 330 cls, 331 path: str | os.PathLike[str], 332 records: Record | Iterable[Record] | None = None, 333 *, 334 operand_cache: int = DEFAULT_OPERAND_CACHE_ENTRIES, 335 ) -> "Lace": 336 return cls._with_initial_records( 337 cls._adopt( 338 _blocking_native_owner(_native.NativeStore.filesystem, path), 339 position_may_advance_externally=False, 340 operand_cache=operand_cache, 341 ), 342 records, 343 )
Base class for Lace binding errors.
34class LaceHandle: 35 """Clonable opaque capability to one store-backed Lace owner.""" 36 37 def __init__(self, lace: "Lace"): 38 self._lace = lace 39 40 def handle(self) -> "LaceHandle": 41 return self._lace.handle()
Clonable opaque capability to one store-backed Lace owner.
Base class for Lace binding errors.
58class LaceLimitExceededError(InterlaceError): 59 category: ClassVar[ErrorCategory] = "limit-exceeded"
Base class for Lace binding errors.
54class LacePeerProtocolError(InterlaceError): 55 category: ClassVar[ErrorCategory] = "peer-protocol"
Base class for Lace binding errors.
Base class for Lace binding errors.
Base class for Lace binding errors.
62class LaceTimeoutError(InterlaceError): 63 category: ClassVar[ErrorCategory] = "timeout" 64 65 def __init__( 66 self, 67 message: str, 68 *, 69 phase: TimeoutPhase, 70 position: ParticipantPosition | None = None, 71 record_hash: str | None = None, 72 ) -> None: 73 super().__init__(message, position=position, record_hash=record_hash, phase=phase)
Base class for Lace binding errors.
Base class for Lace binding errors.
134@dataclass(frozen=True) 135class ListOutcome: 136 terminal: Terminal 137 observations: list[InterlaceEvent] 138 records: list[Record]
44class LocalPeer: 45 """Reusable local participant that supplies its own prepared operand. 46 47 In an interlace, a bare ``Lace``/``LaceHandle`` position receives the 48 operation's policy and a ``LocalPeer`` position receives this operand. At 49 most one position may be a ``LocalPeer``, and it cannot join a connected peer. 50 """ 51 52 def __init__(self, lace: "Lace | LaceHandle", policy: PolicyInput): 53 if isinstance(lace, LaceHandle): 54 lace = lace._lace 55 elif not isinstance(lace, Lace): 56 raise TypeError("LocalPeer requires a Lace or LaceHandle") 57 self._lace: Lace = lace 58 self._policy: PreparedPolicy = _prepare_policy(policy)
Reusable local participant that supplies its own prepared operand.
In an interlace, a bare Lace/LaceHandle position receives the
operation's policy and a LocalPeer position receives this operand. At
most one position may be a LocalPeer, and it cannot join a connected peer.
52 def __init__(self, lace: "Lace | LaceHandle", policy: PolicyInput): 53 if isinstance(lace, LaceHandle): 54 lace = lace._lace 55 elif not isinstance(lace, Lace): 56 raise TypeError("LocalPeer requires a Lace or LaceHandle") 57 self._lace: Lace = lace 58 self._policy: PreparedPolicy = _prepare_policy(policy)
172@dataclass(frozen=True) 173class OpenBoundaryOutcome: 174 boundary: FixedPoint | Closed 175 observations: list[InterlaceEvent]
319class OpenInterlace: 320 """One open interlace owning one route driver. 321 322 Event reading, policy replacement and close exclude each other: a call made 323 while another owns the interlace fails as busy instead of waiting. 324 """ 325 326 @classmethod 327 def _open( 328 cls, 329 first: _AcceptedParticipant, 330 second: _AcceptedParticipant, 331 first_policy: PreparedPolicy, 332 second_policy: PreparedPolicy, 333 limits: dict[str, int] | None = None, 334 *, 335 private: Lace | None = None, 336 ) -> "OpenInterlace": 337 """Open one route with operands by participant position. 338 339 `private` is a porcelain operation's own first participant, closed with 340 the operation. 341 """ 342 driver = _create_driver(first, second, first_policy, second_policy, limits, None) 343 return cls._own(driver, private=private) 344 345 @classmethod 346 def _own(cls, driver: Driver, *, private: Lace | None = None) -> "OpenInterlace": 347 try: 348 return cls(driver, private=private) 349 except BaseException as error: 350 _raise_after_cleanup(error, driver.close) 351 352 def __init__(self, driver: Driver, *, private: Lace | None = None) -> None: 353 self._busy = threading.Lock() 354 self._driver = driver 355 self._laces = driver.laces 356 # A porcelain operation's private Lace, closed with the operation. 357 self._private_lace = private 358 self._closed = False 359 self._closed_delivered = False 360 self._failure: BaseException | None = None 361 self._close_error: BaseException | None = None 362 self._failure_delivered = False 363 registered: list[Lace] = [] 364 try: 365 for lace in self._laces: 366 lace._register_open_interlace(self) 367 registered.append(lace) 368 except BaseException as error: 369 _raise_after_cleanup( 370 error, 371 *(partial(lace._unregister_open_interlace, self) for lace in registered), 372 ) 373 374 def _claim(self) -> None: 375 if not self._busy.acquire(blocking=False): 376 raise RuntimeError("interlace is busy") 377 378 def _release(self) -> None: 379 self._busy.release() 380 381 def close(self) -> None: 382 self._claim() 383 try: 384 self._finish() 385 finally: 386 self._release() 387 388 def _finish(self) -> None: 389 if self._closed: 390 if self._close_error is not None: 391 raise self._close_error 392 return 393 self._closed = True 394 failure = self._failure 395 cleanup: list[Callable[[], None]] = [ 396 self._driver.close, 397 *(partial(lace._unregister_open_interlace, self) for lace in self._laces), 398 ] 399 if self._private_lace is not None: 400 cleanup.append(self._private_lace.close) 401 for close in cleanup: 402 try: 403 close() 404 except BaseException as error: 405 failure = _combine_failures(failure, error) 406 self._close_error = failure 407 if failure is not None: 408 self._failure = failure 409 raise failure 410 411 def __enter__(self) -> "OpenInterlace": 412 return self 413 414 def __exit__( 415 self, 416 exc_type: type[BaseException] | None, 417 exc: BaseException | None, 418 tb: TracebackType | None, 419 ) -> None: 420 try: 421 self.close() 422 except BaseException as cleanup_error: 423 failure = _combine_failures(exc, cleanup_error) 424 assert failure is not None 425 raise failure 426 427 def __iter__(self) -> Iterator[InterlaceEvent]: 428 return self 429 430 def __next__(self) -> InterlaceEvent: 431 event = self.next_event() 432 if event is None: 433 raise StopIteration 434 return event 435 436 @property 437 def peer_port(self) -> str | None: 438 """The port the connected peer's latest preface sent, for diagnostics; 439 `None` when it sent none or the interlace is direct.""" 440 return self._driver.peer_port 441 442 @property 443 def peer_by(self) -> str | None: 444 """The By the live peer proved in the latest session under 445 confidential-channel or channel-bound assurance, for diagnostics; 446 `None` before that proof validates, without one, or when the interlace 447 is direct.""" 448 return self._driver.peer_by 449 450 @property 451 def outgoing_encoding(self) -> str | None: 452 """The 065 token of the local-to-peer stream encoding the latest 453 preface pair selected, for diagnostics; `None` before the first peer 454 preface or when the interlace is direct.""" 455 return self._driver.outgoing_encoding 456 457 @property 458 def incoming_encoding(self) -> str | None: 459 """The 065 token of the peer-to-local stream encoding, as 460 `outgoing_encoding`.""" 461 return self._driver.incoming_encoding 462 463 def replace_policy(self, next_policy: PolicyInput) -> None: 464 """Stage `next_policy`. An interlace opened with a selector takes each 465 session's policy from it and raises LacePolicyError.""" 466 self._claim() 467 try: 468 if self._failure is not None: 469 raise self._failure 470 if self._closed: 471 raise LacePolicyError("interlace is not open") 472 self._driver.replace(_prepare_policy(next_policy)) 473 finally: 474 self._release() 475 476 def next_event(self, *, timeout: float | None = None) -> InterlaceEvent | None: 477 """One-event reading; `RecordSaved` carries the admitted record.""" 478 timeout = _validate_timeout(timeout) 479 self._claim() 480 try: 481 return self._next_claimed(timeout) 482 finally: 483 self._release() 484 485 def _next_claimed(self, timeout: float | None) -> InterlaceEvent | None: 486 if self._failure is not None: 487 if self._failure_delivered: 488 return None 489 self._failure_delivered = True 490 raise self._failure 491 if self._closed: 492 if self._closed_delivered: 493 return None 494 self._closed_delivered = True 495 return Closed("cancelled") 496 try: 497 poll = self._driver.next_raw(timeout) 498 if poll is None: 499 return None 500 event = event_from_poll(poll, open_stream=True) 501 if isinstance(event, Closed): 502 self._finish() 503 self._closed_delivered = True 504 return event 505 except BaseException as error: 506 self._failure_delivered = True 507 if self._closed: 508 # Closing after `Closed` failed; `_finish` retained that failure. 509 raise 510 failure = error if not isinstance(error, Exception) else _as_interlace_error(error) 511 self._failure = failure 512 self._finish() 513 raise failure 514 515 def run_until_fixed_point(self) -> OpenBoundaryOutcome: 516 observations: list[InterlaceEvent] = [] 517 while True: 518 observation = self.next_event() 519 if observation is None: 520 raise LaceIntegrationError( 521 "interlace event stream exhausted before a boundary" 522 ) 523 observations.append(observation) 524 if isinstance(observation, (FixedPoint, Closed)): 525 return OpenBoundaryOutcome(observation, observations)
One open interlace owning one route driver.
Event reading, policy replacement and close exclude each other: a call made while another owns the interlace fails as busy instead of waiting.
352 def __init__(self, driver: Driver, *, private: Lace | None = None) -> None: 353 self._busy = threading.Lock() 354 self._driver = driver 355 self._laces = driver.laces 356 # A porcelain operation's private Lace, closed with the operation. 357 self._private_lace = private 358 self._closed = False 359 self._closed_delivered = False 360 self._failure: BaseException | None = None 361 self._close_error: BaseException | None = None 362 self._failure_delivered = False 363 registered: list[Lace] = [] 364 try: 365 for lace in self._laces: 366 lace._register_open_interlace(self) 367 registered.append(lace) 368 except BaseException as error: 369 _raise_after_cleanup( 370 error, 371 *(partial(lace._unregister_open_interlace, self) for lace in registered), 372 )
436 @property 437 def peer_port(self) -> str | None: 438 """The port the connected peer's latest preface sent, for diagnostics; 439 `None` when it sent none or the interlace is direct.""" 440 return self._driver.peer_port
The port the connected peer's latest preface sent, for diagnostics;
None when it sent none or the interlace is direct.
442 @property 443 def peer_by(self) -> str | None: 444 """The By the live peer proved in the latest session under 445 confidential-channel or channel-bound assurance, for diagnostics; 446 `None` before that proof validates, without one, or when the interlace 447 is direct.""" 448 return self._driver.peer_by
The By the live peer proved in the latest session under
confidential-channel or channel-bound assurance, for diagnostics;
None before that proof validates, without one, or when the interlace
is direct.
450 @property 451 def outgoing_encoding(self) -> str | None: 452 """The 065 token of the local-to-peer stream encoding the latest 453 preface pair selected, for diagnostics; `None` before the first peer 454 preface or when the interlace is direct.""" 455 return self._driver.outgoing_encoding
The 065 token of the local-to-peer stream encoding the latest
preface pair selected, for diagnostics; None before the first peer
preface or when the interlace is direct.
457 @property 458 def incoming_encoding(self) -> str | None: 459 """The 065 token of the peer-to-local stream encoding, as 460 `outgoing_encoding`.""" 461 return self._driver.incoming_encoding
The 065 token of the peer-to-local stream encoding, as
outgoing_encoding.
463 def replace_policy(self, next_policy: PolicyInput) -> None: 464 """Stage `next_policy`. An interlace opened with a selector takes each 465 session's policy from it and raises LacePolicyError.""" 466 self._claim() 467 try: 468 if self._failure is not None: 469 raise self._failure 470 if self._closed: 471 raise LacePolicyError("interlace is not open") 472 self._driver.replace(_prepare_policy(next_policy)) 473 finally: 474 self._release()
Stage next_policy. An interlace opened with a selector takes each
session's policy from it and raises LacePolicyError.
476 def next_event(self, *, timeout: float | None = None) -> InterlaceEvent | None: 477 """One-event reading; `RecordSaved` carries the admitted record.""" 478 timeout = _validate_timeout(timeout) 479 self._claim() 480 try: 481 return self._next_claimed(timeout) 482 finally: 483 self._release()
One-event reading; RecordSaved carries the admitted record.
515 def run_until_fixed_point(self) -> OpenBoundaryOutcome: 516 observations: list[InterlaceEvent] = [] 517 while True: 518 observation = self.next_event() 519 if observation is None: 520 raise LaceIntegrationError( 521 "interlace event stream exhausted before a boundary" 522 ) 523 observations.append(observation) 524 if isinstance(observation, (FixedPoint, Closed)): 525 return OpenBoundaryOutcome(observation, observations)
27class PeerProofAssurance(str, Enum): 28 """Host assertion about established IO, not transport protection. 29 30 CONFIDENTIAL_CHANNEL asserts confidentiality, integrity, and endpoint 31 confinement for the complete channel, including proxies and backend hops. 32 CHANNEL_BOUND additionally binds both live proofs to the supplied channel. 33 """ 34 35 DISABLED = "disabled" 36 CONFIDENTIAL_CHANNEL = "confidential-channel" 37 CHANNEL_BOUND = "channel-bound"
Host assertion about established IO, not transport protection.
CONFIDENTIAL_CHANNEL asserts confidentiality, integrity, and endpoint confinement for the complete channel, including proxies and backend hops. CHANNEL_BOUND additionally binds both live proofs to the supplied channel.
12@dataclass(frozen=True, init=False) 13class PreparedPolicy: 14 """An independently owned, validated canonical exchange operand.""" 15 _native: _lace_py._PreparedPolicy 16 17 def __new__(cls) -> PreparedPolicy: 18 raise TypeError("use compile or PreparedPolicy.from_canonical_text") 19 20 @classmethod 21 def _from_native(cls, value: _lace_py._PreparedPolicy) -> PreparedPolicy: 22 result = object.__new__(cls) 23 object.__setattr__(result, "_native", value) 24 return result 25 26 @classmethod 27 def from_canonical_text(cls, text: str) -> PreparedPolicy: 28 if not isinstance(text, str): 29 raise TypeError("canonical policy text must be a string") 30 return cls._from_native(_native._PreparedPolicy.from_canonical_text(text)) 31 32 @property 33 def canonical_text(self) -> str: 34 return self._native.canonical_text 35 36 @property 37 def operand_hash(self) -> str: 38 return self._native.operand_hash
An independently owned, validated canonical exchange operand.
The complete canonical bytes, when the record holds them contiguously.
34@dataclass(frozen=True) 35class RecordSaved(RecordEvent): 36 """`record` is the admitted owned record on open streams, `None` in bounded outcomes.""" 37 38 record: Record | None = None
record is the admitted owned record on open streams, None in bounded outcomes.
40@dataclass(frozen=True) 41class SessionOperand: 42 """One logical session's operand, returned by an `open_interlace` selector. 43 44 `policy` is prepared when the selector returns it; `expected_peer_by` 45 rejects every peer proof but that By's, as at connected construction. 46 """ 47 48 policy: PolicyInput 49 expected_peer_by: str | None = None 50 51 def __init__(self, policy: PolicyInput, *, expected_peer_by: str | None = None) -> None: 52 if not isinstance(policy, (str, PreparedPolicy)): 53 raise TypeError("policy must be source text or PreparedPolicy") 54 if expected_peer_by is not None: 55 _native.validate_by(expected_peer_by) 56 object.__setattr__(self, "policy", policy) 57 object.__setattr__(self, "expected_peer_by", expected_peer_by)
One logical session's operand, returned by an open_interlace selector.
policy is prepared when the selector returns it; expected_peer_by
rejects every peer proof but that By's, as at connected construction.
51 def __init__(self, policy: PolicyInput, *, expected_peer_by: str | None = None) -> None: 52 if not isinstance(policy, (str, PreparedPolicy)): 53 raise TypeError("policy must be source text or PreparedPolicy") 54 if expected_peer_by is not None: 55 _native.validate_by(expected_peer_by) 56 object.__setattr__(self, "policy", policy) 57 object.__setattr__(self, "expected_peer_by", expected_peer_by)
149@dataclass(frozen=True) 150class StoreOutcome: 151 terminal: Terminal 152 observations: list[InterlaceEvent] 153 records: list[StoreRecordResult]
141@dataclass(frozen=True) 142class StoreRecordResult: 143 hash: str 144 kind: Literal["saved", "skipped", "unsettled"] 145 provider_sent: bool 146 target_saved: bool
188class StoreStream: 189 """The open counterpart of `store`: offers records one at a time. 190 191 An offer made while another runs fails as busy. An error, including an 192 interrupted offer, ends the stream: later offers raise it, and records 193 saved before it stay saved. 194 """ 195 196 def __init__(self, opened: OpenInterlace, provider: Lace) -> None: 197 self._opened = opened 198 self._provider = provider 199 self._busy = threading.Lock() 200 self._failure: BaseException | None = None 201 self._closed = False 202 203 def offer(self, record: Record) -> Literal["saved", "skipped"]: 204 """Store `record` into the provider and drive to the next fixed point: 205 "saved" when the provider sent it, "skipped" when it did not or the 206 provider already held it.""" 207 if not isinstance(record, Record): 208 raise TypeError("offer accepts a Record; use Record.from_bytes(...) to validate imported bytes first") 209 if not self._busy.acquire(blocking=False): 210 raise RuntimeError("store stream is busy") 211 try: 212 if self._failure is not None: 213 raise self._failure 214 if self._closed: 215 raise RuntimeError("store stream is closed") 216 try: 217 return self._offer(record) 218 except BaseException as error: 219 self._failure = error 220 raise 221 finally: 222 self._busy.release() 223 224 def _offer(self, record: Record) -> Literal["saved", "skipped"]: 225 # The provider admits the record through ordinary interlace; its 226 # published position wakes the open interlace. 227 outcome = store(self._provider, record) 228 if isinstance(outcome.terminal, Failed): 229 raise outcome.terminal.error 230 if not isinstance(outcome.terminal, Complete): 231 raise LaceLimitExceededError("storing the offer into the provider stopped by a limit") 232 if outcome.records[0].kind == "skipped": 233 return "skipped" 234 return "saved" if self._fixed_point(record.hash) else "skipped" 235 236 def _fixed_point(self, record_hash: str | None) -> bool: 237 """Drive to the next fixed point; whether the provider sent `record_hash`.""" 238 sent = False 239 while True: 240 event = self._opened.next_event() 241 if event is None: 242 raise LaceIntegrationError("store stream ended before its fixed point") 243 if isinstance(event, FixedPoint): 244 return sent 245 if isinstance(event, Closed): 246 raise LacePeerProtocolError( 247 f"store stream closed before an offer's fixed point: {event.reason}" 248 ) 249 if isinstance(event, RecordSent) and event.position == "first": 250 sent = sent or event.hash == record_hash 251 252 def close(self) -> None: 253 """Cancel the interlace and close the provider; raise the failure that 254 ended the stream with any cleanup failure.""" 255 if not self._busy.acquire(blocking=False): 256 raise RuntimeError("store stream is busy") 257 try: 258 failure = self._failure 259 if not self._closed: 260 self._closed = True 261 try: 262 self._opened.close() 263 except BaseException as error: 264 if error is not failure: 265 failure = _combine_failures(failure, error) 266 self._failure = failure 267 if failure is not None: 268 raise failure 269 finally: 270 self._busy.release() 271 272 def __enter__(self) -> "StoreStream": 273 return self 274 275 def __exit__( 276 self, 277 exc_type: type[BaseException] | None, 278 exc: BaseException | None, 279 tb: TracebackType | None, 280 ) -> None: 281 try: 282 self.close() 283 except BaseException as cleanup_error: 284 if exc is None or cleanup_error is exc: 285 raise 286 failure = _combine_failures(exc, cleanup_error) 287 assert failure is not None 288 raise failure
The open counterpart of store: offers records one at a time.
An offer made while another runs fails as busy. An error, including an interrupted offer, ends the stream: later offers raise it, and records saved before it stay saved.
203 def offer(self, record: Record) -> Literal["saved", "skipped"]: 204 """Store `record` into the provider and drive to the next fixed point: 205 "saved" when the provider sent it, "skipped" when it did not or the 206 provider already held it.""" 207 if not isinstance(record, Record): 208 raise TypeError("offer accepts a Record; use Record.from_bytes(...) to validate imported bytes first") 209 if not self._busy.acquire(blocking=False): 210 raise RuntimeError("store stream is busy") 211 try: 212 if self._failure is not None: 213 raise self._failure 214 if self._closed: 215 raise RuntimeError("store stream is closed") 216 try: 217 return self._offer(record) 218 except BaseException as error: 219 self._failure = error 220 raise 221 finally: 222 self._busy.release()
Store record into the provider and drive to the next fixed point:
"saved" when the provider sent it, "skipped" when it did not or the
provider already held it.
252 def close(self) -> None: 253 """Cancel the interlace and close the provider; raise the failure that 254 ended the stream with any cleanup failure.""" 255 if not self._busy.acquire(blocking=False): 256 raise RuntimeError("store stream is busy") 257 try: 258 failure = self._failure 259 if not self._closed: 260 self._closed = True 261 try: 262 self._opened.close() 263 except BaseException as error: 264 if error is not failure: 265 failure = _combine_failures(failure, error) 266 self._failure = failure 267 if failure is not None: 268 raise failure 269 finally: 270 self._busy.release()
Cancel the interlace and close the provider; raise the failure that ended the stream with any cleanup failure.
103def all_records() -> PreparedPolicy: 104 """The policy that selects, exposes, and requests every record.""" 105 return PreparedPolicy._from_native(_native.all_records())
The policy that selects, exposes, and requests every record.
628def bounded_interlace( 629 first: Participant, 630 second: Participant, 631 policy: PolicyInput, 632 *, 633 limits: dict[str, int] | None = None, 634 cancellation: Cancellation | None = None, 635) -> BoundedInterlaceOutcome: 636 with _accept_connected_inputs(first, second) as (owned_first, owned_second): 637 cancellation = _cancellation_argument(cancellation) 638 prepared = _prepare_policy(policy) 639 raw = _bounded_any_raw( 640 owned_first, owned_second, prepared, prepared, 641 limits=limits, cancellation=cancellation, 642 ) 643 return BoundedInterlaceOutcome( 644 _terminal(raw), raw.events, raw.peer_by, 645 raw.outgoing_encoding, raw.incoming_encoding, 646 )
41def compile(source: str, *, max_source_bytes: int = 1 << 20) -> PreparedPolicy: 42 """Compile one complete Interlang contribution into an exchange operand.""" 43 if not isinstance(source, str): 44 raise TypeError("policy source must be a string") 45 if isinstance(max_source_bytes, bool) or not isinstance(max_source_bytes, int): 46 raise TypeError("max_source_bytes must be a nonnegative integer") 47 if max_source_bytes < 0: 48 raise ValueError("max_source_bytes must be a nonnegative integer") 49 return PreparedPolicy._from_native(_native.compile_policy(source, max_source_bytes))
Compile one complete Interlang contribution into an exchange operand.
60def compose_policies( 61 policies: Sequence[PreparedPolicy], 62 *, 63 guests: Sequence[PreparedPolicy] = (), 64 exclusions: Sequence[FieldExclusion] = (), 65) -> PreparedPolicy: 66 """Intersect contributed selection/exposure facets and union vetoes. 67 68 Each input retains private helpers. Absent facets add no local constraint; 69 empty ``policies`` is an error. Guest rules see only records the authoring 70 ``policies`` expose. Records matching an exclusion are neither selected, 71 requested nor exposed. All complete sequences are composed in one Rust call. 72 """ 73 return PreparedPolicy._from_native(_native.compose_policies( 74 _prepared_sequence(policies, "policies"), _prepared_sequence(guests, "guests"), 75 _exclusion_sequence(exclusions)))
Intersect contributed selection/exposure facets and union vetoes.
Each input retains private helpers. Absent facets add no local constraint;
empty policies is an error. Guest rules see only records the authoring
policies expose. Records matching an exclusion are neither selected,
requested nor exposed. All complete sequences are composed in one Rust call.
144def for_each( 145 lace: Participant, 146 policy: PolicyInput, 147 *, 148 limits: dict[str, int] | None = None, 149) -> ForEachStream: 150 with _accept_connected_inputs(lace) as (target,): 151 operands = _private_operands(_prepare_policy(policy), _native.tx_disabled()) 152 observer = _private_lace() 153 try: 154 return ForEachStream( 155 OpenInterlace._open(observer, target, *operands, limits, private=observer) 156 ) 157 except BaseException as error: 158 _raise_after_cleanup(error, observer.close)
126def get( 127 lace: Participant, 128 policy: PolicyInput, 129 *, 130 limits: dict[str, int] | None = None, 131 cancellation: Cancellation | None = None, 132) -> GetOutcome: 133 with _accept_connected_inputs(lace) as (target,): 134 result = _capture_records( 135 target, policy, limits=limits, 136 cancellation=_cancellation_argument(cancellation), first_only=True, 137 ) 138 return GetOutcome( 139 result.terminal, result.observations, 140 result.records[0] if result.records else None, 141 )
112def list_records( 113 lace: Participant, 114 policy: PolicyInput, 115 *, 116 limits: dict[str, int] | None = None, 117 cancellation: Cancellation | None = None, 118) -> ListOutcome: 119 with _accept_connected_inputs(lace) as (target,): 120 return _capture_records( 121 target, policy, limits=limits, 122 cancellation=_cancellation_argument(cancellation), first_only=False, 123 )
649def open_interlace( 650 first: Participant, 651 second: Participant, 652 policy: PolicyInput | None = None, 653 *, 654 selector: Selector | None = None, 655 limits: dict[str, int] | None = None, 656) -> OpenInterlace: 657 """Open an interlace under `policy`, or under `selector` between one local 658 Lace and one ConnectedPeer. 659 660 The selector is called once per logical session with the peer preface's 661 port, or `None` when it sent none, and returns that session's 662 `SessionOperand` or `None` to refuse the port, which fails the operation 663 as LacePeerProtocolError("port refused"). 664 """ 665 with _accept_connected_inputs(first, second) as (owned_first, owned_second): 666 if (policy is None) == (selector is None): 667 raise TypeError("open_interlace takes exactly one of policy or selector") 668 if selector is not None: 669 if not callable(selector): 670 raise TypeError("selector must be callable") 671 return OpenInterlace._own( 672 _create_selected_driver(owned_first, owned_second, selector, limits) 673 ) 674 assert policy is not None 675 prepared = _prepare_policy(policy) 676 return OpenInterlace._open(owned_first, owned_second, prepared, prepared, limits)
Open an interlace under policy, or under selector between one local
Lace and one ConnectedPeer.
The selector is called once per logical session with the peer preface's
port, or None when it sent none, and returns that session's
SessionOperand or None to refuse the port, which fails the operation
as LacePeerProtocolError("port refused").
8def parse_by(text: str) -> str: 9 """Parse one canonical public Lace By value and return its canonical text.""" 10 try: 11 return _native.validate_by(text) 12 except Exception as error: 13 raise ValueError(str(error)) from error
Parse one canonical public Lace By value and return its canonical text.
68def read_record_stream( 69 lace: Lace | LaceHandle, reader: BinaryIO, *, max_bytes: int 70) -> Iterator[Record]: 71 """The records of one Lace-010 record stream read from a binary reader. 72 73 Records arrive in stream order, each received into memory once its bytes 74 are validated; nothing is admitted. A record whose canonical length exceeds 75 what remains of `max_bytes` fails at its start with LaceLimitExceededError, 76 before its bytes are held. An invalid record, bytes after a record that do 77 not form one, or a stream ending inside a record raise 78 LacePeerProtocolError, and a failed read LaceTransportError. A failure ends 79 the iterator and discards the partial record. 80 """ 81 owner = lace if isinstance(lace, Lace) else lace._lace if isinstance(lace, LaceHandle) else None 82 if owner is None: 83 raise TypeError("read_record_stream requires a Lace or LaceHandle") 84 owner._raise_if_unavailable() 85 if isinstance(max_bytes, bool) or not isinstance(max_bytes, int) or not 0 <= max_bytes < 1 << 64: 86 raise ValueError("max_bytes must be a u64 integer") 87 return _record_stream(_native.RecordStreamDecoder(max_bytes), reader)
The records of one Lace-010 record stream read from a binary reader.
Records arrive in stream order, each received into memory once its bytes
are validated; nothing is admitted. A record whose canonical length exceeds
what remains of max_bytes fails at its start with LaceLimitExceededError,
before its bytes are held. An invalid record, bytes after a record that do
not form one, or a stream ending inside a record raise
LacePeerProtocolError, and a failed read LaceTransportError. A failure ends
the iterator and discards the partial record.
35def store( 36 lace: Participant, 37 record: Record | Iterable[Record], 38 *, 39 limits: dict[str, int] | None = None, 40 cancellation: Cancellation | None = None, 41) -> StoreOutcome: 42 with _accept_connected_inputs(lace) as (target,): 43 cancellation = _cancellation_argument(cancellation) 44 target_lace = _store_lace(target) 45 if target_lace is None and not isinstance(target, (LocalPeer, _ConnectedInput)): 46 raise TypeError("store target must be a Lace, LaceHandle, LocalPeer, or connected peer") 47 normalized_limits = _interlace_limits(limits) 48 records_input = _normalize_records(record) 49 if not records_input: 50 return StoreOutcome(Complete(), [], []) 51 operands = _private_operands(all_records(), _native.rx_disabled()) 52 source = _private_lace(records_input) 53 try: 54 raw = _bounded_any_raw( 55 source, target, *operands, 56 limits=normalized_limits, cancellation=cancellation, 57 ) 58 except BaseException as error: 59 _raise_after_cleanup(error, source.close) 60 raw.settle(source.close) 61 unique = dict.fromkeys(input_record.hash for input_record in records_input) 62 sent = { 63 event.hash for event in raw.events 64 if isinstance(event, RecordSent) and event.position == "first" 65 } 66 terminal = _terminal(raw) 67 settled = isinstance(terminal, Complete) 68 results = [ 69 StoreRecordResult( 70 hash_text, 71 "saved" if settled and hash_text in sent else "skipped" if settled else "unsettled", 72 hash_text in sent, settled and hash_text in sent, 73 ) 74 for hash_text in unique 75 ] 76 return StoreOutcome(terminal, raw.events, results)
161def store_stream( 162 lace: Participant, 163 *, 164 limits: dict[str, int] | None = None, 165) -> "StoreStream": 166 """Open `store`'s interlace against `lace` with an empty private provider 167 and drive it to its first fixed point; then `offer` each record.""" 168 with _accept_connected_inputs(lace) as (target,): 169 if _store_lace(target) is None and not isinstance(target, (LocalPeer, _ConnectedInput)): 170 raise TypeError("store_stream target must be a Lace, LaceHandle, LocalPeer, or connected peer") 171 operands = _private_operands(all_records(), _native.rx_disabled()) 172 provider = _private_lace() 173 try: 174 opened = OpenInterlace._open(provider, target, *operands, limits, private=provider) 175 except BaseException as error: 176 _raise_after_cleanup(error, provider.close) 177 stream = StoreStream(opened, provider) 178 try: 179 stream._fixed_point(None) 180 except BaseException as error: 181 # Close raises this failure with any cleanup failure. 182 stream._failure = error 183 stream.close() 184 raise 185 return stream
39 def compact_filesystem( 40 root: str | os.PathLike[str], policies: Sequence[PolicyInput] 41 ) -> CompactionReport: 42 """Remove every record of the filesystem root that no policy selects. 43 44 The root must have no open Lace and no live record in any process. 45 A root holding an interrupted compaction resumes it without evaluating 46 ``policies``. ``KeyboardInterrupt`` stops the work at its next store 47 operation or move batch and leaves a root only compaction reopens. 48 """ 49 natives = _native_policies(policies) 50 return CompactionReport(*_native.compact_filesystem(str(Path(root)), natives))
Remove every record of the filesystem root that no policy selects.
The root must have no open Lace and no live record in any process.
A root holding an interrupted compaction resumes it without evaluating
policies. KeyboardInterrupt stops the work at its next store
operation or move batch and leaves a root only compaction reopens.