diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b8010ac9..897af4a0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -46,7 +46,7 @@ jobs: - run: mk python-sa-types - - run: mk docs version=5.17.7 + - run: mk docs version=5.17.8 if: | matrix.python-version == '3.14' && matrix.system == 'ubuntu-latest' @@ -78,7 +78,7 @@ jobs: - run: | mk python-release owner=libre-embedded \ - repo=runtimepy version=5.17.7 + repo=runtimepy version=5.17.8 if: | matrix.python-version == '3.14' && matrix.system == 'ubuntu-latest' diff --git a/README.md b/README.md index 68d5451c..65f5bad9 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ===================================== generator=datazen version=3.2.4 - hash=463f15ff45a85cd1551cbc27cd64c6aa + hash=2b117d95609e4be28ce040a384008fbc ===================================== --> -# runtimepy ([5.17.7](https://pypi.org/project/runtimepy/)) +# runtimepy ([5.17.8](https://pypi.org/project/runtimepy/)) [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/) ![Build Status](https://github.com/libre-embedded/runtimepy/workflows/Python%20Package/badge.svg) diff --git a/local/variables/package.yaml b/local/variables/package.yaml index 37825bf9..8672bd28 100644 --- a/local/variables/package.yaml +++ b/local/variables/package.yaml @@ -1,5 +1,5 @@ --- major: 5 minor: 17 -patch: 7 +patch: 8 entry: runtimepy diff --git a/pyproject.toml b/pyproject.toml index 25ea8f4b..c25014b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__" [project] name = "runtimepy" -version = "5.17.7" +version = "5.17.8" description = "A framework for implementing Python services." readme = "README.md" requires-python = ">=3.13" diff --git a/runtimepy/__init__.py b/runtimepy/__init__.py index de6cf216..18b194a5 100644 --- a/runtimepy/__init__.py +++ b/runtimepy/__init__.py @@ -1,7 +1,7 @@ # ===================================== # generator=datazen # version=3.2.4 -# hash=afdb221becccb19dc15b98597cb5727b +# hash=94b66e105ca78fb078bfe8aeae461106 # ===================================== """ @@ -10,7 +10,7 @@ DESCRIPTION = "A framework for implementing Python services." PKG_NAME = "runtimepy" -VERSION = "5.17.7" +VERSION = "5.17.8" # runtimepy-specific content. METRICS_NAME = "metrics" diff --git a/runtimepy/message/interface.py b/runtimepy/message/interface.py index 7143eb93..86461904 100644 --- a/runtimepy/message/interface.py +++ b/runtimepy/message/interface.py @@ -8,6 +8,7 @@ from io import BytesIO import logging from typing import Any, Optional, Union +from uuid import uuid4 # third-party from vcorelib.dict.codec import JsonCodec @@ -40,7 +41,6 @@ T, TypedHandler, ) -from runtimepy.util import Identifier class JsonMessageInterface: @@ -71,10 +71,8 @@ def __init__(self) -> None: "kind": type(self).__name__, } - self.curr_id = Identifier() - - self.ids_waiting: dict[int, asyncio.Event] = {} - self.id_responses: dict[int, JsonMessage] = {} + self.ids_waiting: dict[str, asyncio.Event] = {} + self.id_responses: dict[str, JsonMessage] = {} # Standard handlers. self.basic_handler("loopback") @@ -97,14 +95,15 @@ async def bus_ro_handler( async def bus_handler(outbox: JsonMessage, inbox: JsonMessage) -> None: """Handle read-only bus message requests.""" - outbox.update( - await BUS.send( - inbox["key"], - inbox.get("data", {}), - send_ro=inbox.get("send_ro", True), - null_ok=inbox.get("null_ok", False), + if "key" in inbox: + outbox.update( + await BUS.send( + inbox["key"], + inbox.get("data", {}), + send_ro=inbox.get("send_ro", True), + null_ok=inbox.get("null_ok", False), + ) ) - ) self.basic_handler("bus", bus_handler) @@ -284,7 +283,7 @@ async def wait_json( data = copy(data) assert "__id__" not in data, data - ident = self.curr_id() + ident = str(uuid4()) data["__id__"] = ident got_response = asyncio.Event() diff --git a/runtimepy/net/backoff.py b/runtimepy/net/backoff.py index aa8ca4b0..84b1b9e0 100644 --- a/runtimepy/net/backoff.py +++ b/runtimepy/net/backoff.py @@ -5,6 +5,9 @@ # built-in import asyncio +# third-party +from vcorelib.math.time import LoggerType + class ExponentialBackoff: """A class implementing a simple exponential-backoff handler.""" @@ -38,9 +41,11 @@ def give_up(self) -> bool: """ return self.attempt >= self.max_tries - async def sleep(self) -> None: + async def sleep(self, logger: LoggerType = None) -> None: """Sleep for the correct amount of time.""" + if logger: + logger.warning("[%d] %fs sleep", self.attempt, self.wait) await asyncio.sleep(self.wait) self.wait = min((2 ^ self.attempt) * self.interval, self.max_sleep) self.attempt += 1 diff --git a/runtimepy/net/connection.py b/runtimepy/net/connection.py index 87cfaa1f..c6ecb84e 100644 --- a/runtimepy/net/connection.py +++ b/runtimepy/net/connection.py @@ -248,7 +248,7 @@ async def _handle_restart( and not backoff.give_up and (stop_sig is None or not stop_sig.is_set()) ): - await backoff.sleep() + await backoff.sleep(logger=self.logger) if await self.restart(): self._set_enabled(True) self._restarts.increment() diff --git a/runtimepy/net/tcp/connection.py b/runtimepy/net/tcp/connection.py index 0c24a7a8..49a1d50b 100644 --- a/runtimepy/net/tcp/connection.py +++ b/runtimepy/net/tcp/connection.py @@ -199,6 +199,10 @@ async def app( def app_cb(conn: T) -> None: """Call the appication callback and enqueue the new connection.""" + + # Turn off connection restarts for server-side connections. + conn.env["auto_restart"] = False + if callback is not None: callback(conn) assert manager is not None diff --git a/runtimepy/net/websocket/connection.py b/runtimepy/net/websocket/connection.py index aebdcb03..ee774d04 100644 --- a/runtimepy/net/websocket/connection.py +++ b/runtimepy/net/websocket/connection.py @@ -178,6 +178,10 @@ async def _handler(protocol: _ServerConnection) -> None: """A handler that runs the callers initialization function.""" conn = cls(protocol) + + # Turn off connection restarts for server-side connections. + conn.env["auto_restart"] = False + if init is None or await init(conn): if manager is not None: # Allow the connection manager to process this connection. diff --git a/runtimepy/subprocess/peer.py b/runtimepy/subprocess/peer.py index 0e5522f0..28930772 100644 --- a/runtimepy/subprocess/peer.py +++ b/runtimepy/subprocess/peer.py @@ -46,9 +46,6 @@ def __init__( super().__init__(name, config, markdown=markdown) self.protocol = protocol - # Offset message identifiers. - self.curr_id.curr_id += 1 - async def _poll(self) -> None: """Poll input queues."""