pynenc.orchestrator.atomic_service

Atomic service coordination for distributed Pynenc runners.

Pure scheduling logic: no I/O, no orchestrator dependencies. The single entry point is :func:decide_atomic_service_claim which returns one

class:

AtomicServiceClaim describing what the caller should do next.

Module Contents

Classes

AtomicServiceId

Identifies one atomic-service run.

AtomicServiceRun

Identifies one atomic-service cycle claimed by a runner.

AtomicServiceDecisionReason

Outcome labels produced by the scheduler claim decision.

ActiveRunnerInfo

Information about an active runner including heartbeat tracking.

AtomicServiceExecutionStatus

Lifecycle status of a recorded atomic-service execution attempt.

AtomicServiceExecution

A recorded atomic-service execution attempt by one runner.

AtomicServiceClaim

Final scheduling result. The single object callers consume.

_ClaimBuilder

Mutable scratch-pad. The scheduler mutates this object and then calls one of the named terminal methods, each of which calls _build() exactly once with the right reason.

Functions

_slot_window

(start, end) for this position, or None when the margin is too wide.

_is_in_grace

_late_start_fraction

decide_atomic_service_claim

Decide whether runner_id should start the atomic service now.

Data

API

class pynenc.orchestrator.atomic_service.AtomicServiceId[source]

Identifies one atomic-service run.

runner_id: str

None

atomic_service_run_id: str

‘field(…)’

__str__() str[source]
class pynenc.orchestrator.atomic_service.AtomicServiceRun[source]

Identifies one atomic-service cycle claimed by a runner.

Accepts either a composed atomic_service_id or the flat runner_id / atomic_service_run_id pair so downstream call sites can construct runs without first building an AtomicServiceId.

atomic_service_id: pynenc.orchestrator.atomic_service.AtomicServiceId

None

cycle_start: datetime.datetime | None

None

slot_start: datetime.datetime | None

None

slot_end: datetime.datetime | None

None

started_at: datetime.datetime | None

None

runner_id: str | None

None

atomic_service_run_id: str | None

None

__post_init__() None[source]
__str__() str[source]
class pynenc.orchestrator.atomic_service.AtomicServiceDecisionReason[source]

Bases: enum.StrEnum

Outcome labels produced by the scheduler claim decision.

Initialization

Initialize self. See help(type(self)) for accurate signature.

ASSIGNED

‘auto(…)’

NOT_ASSIGNED_SLOT

‘auto(…)’

NO_STABLE_RUNNERS

‘auto(…)’

SCHEDULED_RUNNER_IN_GRACE

‘auto(…)’

SLOT_WINDOW_INVALID

‘auto(…)’

LATE_START

‘auto(…)’

is_persisted() bool[source]

Whether this reason should be persisted as a BLOCKED execution.

pynenc.orchestrator.atomic_service._PERSISTED_REASONS

‘frozenset(…)’

class pynenc.orchestrator.atomic_service.ActiveRunnerInfo[source]

Bases: typing.NamedTuple

Information about an active runner including heartbeat tracking.

runner_id: str

None

creation_time: datetime.datetime

None

last_heartbeat: datetime.datetime

None

allow_to_run_atomic_service: bool

False

class pynenc.orchestrator.atomic_service.AtomicServiceExecutionStatus[source]

Bases: enum.StrEnum

Lifecycle status of a recorded atomic-service execution attempt.

Initialization

Initialize self. See help(type(self)) for accurate signature.

RUNNING

‘running’

COMPLETED

‘completed’

ABANDONED

‘abandoned’

BLOCKED

‘blocked’

class pynenc.orchestrator.atomic_service.AtomicServiceExecution[source]

Bases: typing.NamedTuple

A recorded atomic-service execution attempt by one runner.

atomic_service_id: pynenc.orchestrator.atomic_service.AtomicServiceId

None

start_time: datetime.datetime

None

end_time: datetime.datetime | None

None

status: pynenc.orchestrator.atomic_service.AtomicServiceExecutionStatus

None

reason: str = <Multiline-String>
classmethod from_raw(runner_id: str, atomic_service_run_id: str, start_time: datetime.datetime, end_time: datetime.datetime | None, status: pynenc.orchestrator.atomic_service.AtomicServiceExecutionStatus = AtomicServiceExecutionStatus.RUNNING, reason: str = '') pynenc.orchestrator.atomic_service.AtomicServiceExecution[source]
property runner_id: str
property atomic_service_run_id: str
property atomic_service_run: pynenc.orchestrator.atomic_service.AtomicServiceRun
property is_active: bool
property duration_seconds: float
class pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]

Final scheduling result. The single object callers consume.

Carries everything an orchestrator or UI needs: the reason, the slot layout, the assigned runner, the optional AtomicServiceRun when the runner is cleared to start, and the optional skip_message / late_start_fraction when the slot is held reserved.

runner_id: str

None

reason: pynenc.orchestrator.atomic_service.AtomicServiceDecisionReason

None

cycle_start: datetime.datetime

None

assigned_runner_id: str | None

None

runner_position: int | None

None

stable_runner_count: int

0

slot_start: datetime.datetime | None

None

slot_end: datetime.datetime | None

None

is_slot_owner_now: bool

False

atomic_service_run: pynenc.orchestrator.atomic_service.AtomicServiceRun | None

None

skip_message: str = <Multiline-String>
late_start_fraction: float | None

None

property should_try_start: bool
property skip_reason: pynenc.orchestrator.atomic_service.AtomicServiceDecisionReason | None
property decision: pynenc.orchestrator.atomic_service.AtomicServiceClaim
class pynenc.orchestrator.atomic_service._ClaimBuilder[source]

Mutable scratch-pad. The scheduler mutates this object and then calls one of the named terminal methods, each of which calls _build() exactly once with the right reason.

Defaults represent the “skip, nothing to report” outcome — every branch only sets the fields that differ.

runner_id: str

None

started_at: datetime.datetime

None

current_time: float

None

interval_seconds: float

None

spread_minutes: float

None

stabilization_seconds: float

None

cycle_start: datetime.datetime

‘field(…)’

stable_runner_count: int

0

assigned_runner_id: str | None

None

runner_position: int | None

None

slot_start: datetime.datetime | None

None

slot_end: datetime.datetime | None

None

is_slot_owner_now: bool

False

__post_init__() None[source]
use_runners(total: int, assigned_runner_id: str) None[source]
set_position(position: int) None[source]
set_slot(window: tuple[datetime.datetime, datetime.datetime], *, is_owner: bool) None[source]
no_runners() pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
not_in_stable_set() pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
not_in_slot() pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
invalid_window() pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
in_grace() pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
late_start(fraction: float, max_fraction: float) pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
cleared_to_start() pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
_build(reason: pynenc.orchestrator.atomic_service.AtomicServiceDecisionReason, *, atomic_service_run: pynenc.orchestrator.atomic_service.AtomicServiceRun | None = None, skip_message: str = '', late_start_fraction: float | None = None) pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]
pynenc.orchestrator.atomic_service._slot_window(cycle_start_ts: float, position: int, slot_size: float, spread_seconds: float, *, single_runner: bool) tuple[datetime.datetime, datetime.datetime] | None[source]

(start, end) for this position, or None when the margin is too wide.

pynenc.orchestrator.atomic_service._is_in_grace(runner: pynenc.orchestrator.atomic_service.ActiveRunnerInfo, now: float, stabilization_seconds: float) bool[source]
pynenc.orchestrator.atomic_service._late_start_fraction(started_at: datetime.datetime, slot_start: datetime.datetime | None, slot_end: datetime.datetime | None, max_fraction: float) float | None[source]
pynenc.orchestrator.atomic_service.decide_atomic_service_claim(*, runner_id: str, active_runners: list[pynenc.orchestrator.atomic_service.ActiveRunnerInfo], current_time: float, service_interval_minutes: float, spread_margin_minutes: float, membership_stabilization_seconds: float, max_start_slot_fraction: float) pynenc.orchestrator.atomic_service.AtomicServiceClaim[source]

Decide whether runner_id should start the atomic service now.

Returns a single :class:AtomicServiceClaim describing the outcome. Callers inspect claim.atomic_service_run (non-None means start), claim.skip_reason (non-None means persist as BLOCKED), and claim.reason for diagnostics.