This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Transport-free partial-read/deadline model for one worker result record."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from phase10aq_worker_result_record import (
|
||||
RECORD_SIZE, WorkerPrecommit, WorkerRecord, WorkerRecordError, parse_record,
|
||||
)
|
||||
|
||||
|
||||
MAX_CHUNKS = 128
|
||||
MAX_TICKS = 256
|
||||
DATA = "DATA"
|
||||
EOF = "EOF"
|
||||
DEADLINE = "DEADLINE"
|
||||
EVENTS = {DATA, EOF, DEADLINE}
|
||||
|
||||
|
||||
class ChannelModelError(RuntimeError):
|
||||
"""The synthetic channel script or boundary is invalid."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FakeReadEvent:
|
||||
kind: str
|
||||
data: bytes = b""
|
||||
ticks: int = 1
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.kind not in EVENTS or type(self.data) is not bytes:
|
||||
raise ChannelModelError("read event is invalid")
|
||||
if (self.kind == DATA) != bool(self.data) or len(self.data) > RECORD_SIZE:
|
||||
raise ChannelModelError("read event payload is invalid")
|
||||
if not isinstance(self.ticks, int) or isinstance(self.ticks, bool) \
|
||||
or not 1 <= self.ticks <= MAX_TICKS:
|
||||
raise ChannelModelError("read event ticks are invalid")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChannelPlan:
|
||||
precommit: WorkerPrecommit
|
||||
writer_pid: int
|
||||
writer_generation: int
|
||||
writer_nonce: bytes
|
||||
exclusive_writer: bool = True
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if type(self.precommit) is not WorkerPrecommit \
|
||||
or self.writer_pid != self.precommit.worker_pid \
|
||||
or self.writer_generation != self.precommit.generation \
|
||||
or self.writer_nonce != self.precommit.nonce \
|
||||
or self.exclusive_writer is not True:
|
||||
raise ChannelModelError("exclusive writer precommit differs")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChannelOutcome:
|
||||
classification: str
|
||||
accepted: bool
|
||||
buffered: int
|
||||
record: WorkerRecord | None
|
||||
containment_required: bool
|
||||
eof_is_success: bool = False
|
||||
live_transport_present: bool = False
|
||||
|
||||
|
||||
def receive_record(plan: ChannelPlan,
|
||||
events: tuple[FakeReadEvent, ...]) -> ChannelOutcome:
|
||||
"""Assemble one exact record from supplied bytes; no I/O is performed."""
|
||||
if type(plan) is not ChannelPlan or not isinstance(events, tuple) \
|
||||
or not 1 <= len(events) <= MAX_CHUNKS \
|
||||
or any(type(item) is not FakeReadEvent for item in events):
|
||||
raise ChannelModelError("channel model boundary is invalid")
|
||||
buffer = bytearray()
|
||||
ticks = 0
|
||||
for index, event in enumerate(events):
|
||||
if ticks + event.ticks > MAX_TICKS:
|
||||
return ChannelOutcome("OFFLINE_CHANNEL_DEADLINE", False,
|
||||
len(buffer), None, True)
|
||||
ticks += event.ticks
|
||||
if event.kind == DEADLINE:
|
||||
return ChannelOutcome("OFFLINE_CHANNEL_DEADLINE", False,
|
||||
len(buffer), None, True)
|
||||
if event.kind == EOF:
|
||||
return ChannelOutcome("OFFLINE_EOF_BEFORE_COMPLETE_RECORD", False,
|
||||
len(buffer), None, True)
|
||||
if len(buffer) + len(event.data) > RECORD_SIZE:
|
||||
return ChannelOutcome("OFFLINE_CHANNEL_OVERFLOW", False,
|
||||
len(buffer), None, True)
|
||||
buffer.extend(event.data)
|
||||
if len(buffer) == RECORD_SIZE:
|
||||
if index != len(events) - 1:
|
||||
raise ChannelModelError("events remain after record boundary")
|
||||
try:
|
||||
record = parse_record(bytes(buffer), plan.precommit)
|
||||
except WorkerRecordError:
|
||||
return ChannelOutcome("OFFLINE_RECORD_REJECTED", False,
|
||||
RECORD_SIZE, None, True)
|
||||
return ChannelOutcome("OFFLINE_EXACT_RECORD_ACCEPTED", True,
|
||||
RECORD_SIZE, record, False)
|
||||
return ChannelOutcome("OFFLINE_INCOMPLETE_WITHOUT_DEADLINE", False,
|
||||
len(buffer), None, True)
|
||||
Reference in New Issue
Block a user