Files
vllm-omni/vllm_omni/experimental/fullduplex/engine/lease.py
T
05b794f29b [Full Duplex] Feat: Support Full-Duplex realtime runtime & add MiniCPM-o 4.5 demo (#3907)
Signed-off-by: Sy03 <1370724210@qq.com>
Signed-off-by: lishunyang12 <lishunyang03@gmail.com>
Signed-off-by: Yueqian Lin <linyueqian@outlook.com>
Co-authored-by: lishunyang12 <lishunyang03@gmail.com>
Co-authored-by: Yueqian Lin <linyueqian@outlook.com>
Co-authored-by: StormMelody <22371184@zju.edu.cn>
Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
2026-07-23 16:45:18 +08:00

130 lines
4.1 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Generic engine lease primitives shared by optional session runtimes."""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from vllm_omni.experimental.fullduplex.engine.messages import DuplexFence
class DuplexLeaseActivity(str, Enum):
APPEND = "append"
SIGNAL = "signal"
PLAYBACK_ACK = "playback_ack"
HEARTBEAT = "heartbeat"
ATTACH = "attach"
DETACH = "detach"
RESUME = "resume"
MODEL_OUTPUT = "model_output"
@dataclass(frozen=True)
class DuplexLeaseConfig:
idle_ttl_s: float | None = 300.0
disconnect_grace_s: float = 30.0
def __post_init__(self) -> None:
if self.idle_ttl_s is not None and self.idle_ttl_s <= 0:
raise ValueError("idle_ttl_s must be positive or null")
if self.disconnect_grace_s <= 0:
raise ValueError("disconnect_grace_s must be positive")
@dataclass
class DuplexLeaseState:
config: DuplexLeaseConfig
generation: int
last_activity: float
detached_at: float | None = None
active_operations: set[str] = field(default_factory=set)
terminal_reason: str | None = None
@property
def expires_at(self) -> float | None:
if self.config.idle_ttl_s is None:
return None
return self.last_activity + self.config.idle_ttl_s
def _require_open(self) -> None:
if self.terminal_reason is not None:
raise RuntimeError(f"duplex lease is terminal: {self.terminal_reason}")
def touch(self, now: float, activity: DuplexLeaseActivity) -> None:
self._require_open()
if not isinstance(activity, DuplexLeaseActivity):
raise TypeError("lease activity must be DuplexLeaseActivity")
self.last_activity = now
def detach(self, now: float) -> None:
self.touch(now, DuplexLeaseActivity.DETACH)
self.detached_at = now
def resume(self, now: float, *, expected_generation: int) -> int:
self._require_open()
if expected_generation != self.generation:
raise ValueError(f"duplex lease generation mismatch: expected {self.generation}, got {expected_generation}")
self.generation += 1
self.detached_at = None
self.last_activity = now
return self.generation
def begin_operation(self, now: float, operation_id: str) -> None:
self._require_open()
if not operation_id:
raise ValueError("duplex lease operation_id must not be empty")
if operation_id in self.active_operations:
raise ValueError(f"duplex lease operation already active: {operation_id}")
self.active_operations.add(operation_id)
self.last_activity = now
def end_operation(self, now: float, operation_id: str) -> None:
self._require_open()
if operation_id not in self.active_operations:
raise KeyError(f"duplex lease operation is not active: {operation_id}")
self.active_operations.remove(operation_id)
self.last_activity = now
def disconnect_grace_expired(self, now: float) -> bool:
return (
self.terminal_reason is None
and self.detached_at is not None
and now >= self.detached_at + self.config.disconnect_grace_s
)
def idle_expired(self, now: float) -> bool:
expires_at = self.expires_at
return (
self.terminal_reason is None and not self.active_operations and expires_at is not None and now >= expires_at
)
def mark_terminal(self, reason: str) -> bool:
if self.terminal_reason is not None:
return False
self.terminal_reason = reason
self.generation += 1
self.detached_at = None
self.active_operations.clear()
return True
@dataclass(frozen=True)
class DuplexSessionExpiry:
session_id: str
fence: DuplexFence
lease_generation: int
reason: str
submitted_request_ids: tuple[str, ...]
reserved_request_ids: tuple[str, ...]
__all__ = [
"DuplexLeaseActivity",
"DuplexLeaseConfig",
"DuplexLeaseState",
"DuplexSessionExpiry",
]