asyncactor 0.24.0__tar.gz → 0.25.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {asyncactor-0.24.0 → asyncactor-0.25.1}/PKG-INFO +30 -9
- asyncactor-0.25.1/asyncactor/__init__.py +15 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/abc.py +30 -16
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/actor.py +77 -80
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/backend/__init__.py +8 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/backend/moat_kv.py +30 -20
- asyncactor-0.25.1/asyncactor/backend/moat_link.py +77 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/backend/mqtt.py +30 -20
- asyncactor-0.25.1/asyncactor/backend/mqttproto.py +78 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/backend/serf.py +10 -12
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/events.py +61 -51
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/exceptions.py +7 -6
- asyncactor-0.25.1/asyncactor/messages.py +175 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor/nodelist.py +11 -7
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor.egg-info/PKG-INFO +30 -9
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor.egg-info/SOURCES.txt +4 -3
- asyncactor-0.25.1/asyncactor.egg-info/requires.txt +20 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor.egg-info/top_level.txt +0 -1
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/source/conf.py +34 -28
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/source/index.rst +1 -3
- asyncactor-0.25.1/pyproject.toml +123 -0
- asyncactor-0.25.1/setup.cfg +4 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/tests/__init__.py +4 -1
- {asyncactor-0.24.0 → asyncactor-0.25.1}/tests/mock_serf.py +12 -9
- {asyncactor-0.24.0 → asyncactor-0.25.1}/tests/test_actor.py +11 -8
- {asyncactor-0.24.0 → asyncactor-0.25.1}/tests/test_mosquitto.py +8 -5
- asyncactor-0.25.1/tests/test_mqttproto.py +130 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/tests/test_serf.py +7 -5
- asyncactor-0.24.0/asyncactor/__init__.py +0 -9
- asyncactor-0.24.0/asyncactor/backend/distkv.py +0 -55
- asyncactor-0.24.0/asyncactor/messages.py +0 -143
- asyncactor-0.24.0/asyncactor.egg-info/requires.txt +0 -8
- asyncactor-0.24.0/setup.cfg +0 -18
- asyncactor-0.24.0/setup.py +0 -51
- {asyncactor-0.24.0 → asyncactor-0.25.1}/.coveragerc +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/.gitignore +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/.pylintrc +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/.readthedocs.yml +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/.style.yapf +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/.travis.yml +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/CHANGELOG.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/LICENSE +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/Makefile +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/README.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/asyncactor.egg-info/dependency_links.txt +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/ci/rtd-requirements.txt +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/ci/test-requirements.txt +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/ci/travis.sh +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/Makefile +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/make.bat +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/source/_static/.git_empty +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/source/actor.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/source/history.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/docs/source/rationale.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/mktag +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.1}/tox.ini +0 -0
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: asyncactor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.1
|
|
4
4
|
Summary: Async decentralized actor
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
Author-email: Matthias Urlichs <matthias@urlichs.de>
|
|
6
|
+
Project-URL: homepage, https://github.com/smurfix/asyncactor
|
|
7
|
+
Project-URL: repository, https://github.com/smurfix/asyncactor.git
|
|
8
|
+
Keywords: MQTT,orchestration,service discovery,anyio
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Framework :: Trio
|
|
13
|
+
Classifier: Framework :: AnyIO
|
|
14
|
+
Classifier: License :: OSI Approved
|
|
15
|
+
Classifier: Development Status :: 4 - Beta
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/x-rst
|
|
13
18
|
License-File: LICENSE
|
|
19
|
+
Requires-Dist: anyio~=4.2
|
|
20
|
+
Requires-Dist: moat-lib-codec
|
|
21
|
+
Requires-Dist: moat-util~=0.53
|
|
22
|
+
Provides-Extra: moat-kv
|
|
23
|
+
Requires-Dist: moat-kv; extra == "moat-kv"
|
|
24
|
+
Provides-Extra: moat-link
|
|
25
|
+
Requires-Dist: moat-link; extra == "moat-link"
|
|
26
|
+
Provides-Extra: mqtt
|
|
27
|
+
Requires-Dist: mqttproto; extra == "mqtt"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: moat-mqtt; extra == "dev"
|
|
30
|
+
Requires-Dist: mqttproto; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=2.3; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-trio; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest>=2.5.2; extra == "dev"
|
|
34
|
+
Requires-Dist: trio>=0.23; extra == "dev"
|
|
14
35
|
|
|
15
36
|
asyncactor
|
|
16
37
|
==========
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AsyncActor: decentral somewhat-round-robin next-responsible-node selection
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from .actor import * # noqa:F403
|
|
8
|
+
from .actor import __all__ as _actor_all
|
|
9
|
+
from .backend import get_transport
|
|
10
|
+
from .events import * # noqa:F403
|
|
11
|
+
from .events import __all__ as _events_all
|
|
12
|
+
from .nodelist import * # noqa:F403
|
|
13
|
+
from .nodelist import __all__ as _nodelist_all
|
|
14
|
+
|
|
15
|
+
__all__ = _actor_all + _events_all + _nodelist_all + ["get_transport"] # noqa:PLE0605
|
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""
|
|
2
|
+
Abstract classes for the underlying transport
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
4
6
|
|
|
5
7
|
from abc import ABCMeta, abstractmethod
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
import anyio
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING, Union
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from typing import Self
|
|
8
15
|
|
|
9
|
-
Packable = Union[
|
|
10
|
-
str,
|
|
16
|
+
Packable = Union[ # noqa:UP007
|
|
17
|
+
str,
|
|
18
|
+
bytes,
|
|
19
|
+
bool,
|
|
20
|
+
type(None),
|
|
21
|
+
int,
|
|
22
|
+
float,
|
|
23
|
+
dict[str | int, "Packable"],
|
|
24
|
+
tuple["Packable"],
|
|
25
|
+
list["Packable"],
|
|
11
26
|
]
|
|
12
27
|
|
|
13
28
|
|
|
@@ -21,13 +36,20 @@ class Transport(metaclass=ABCMeta):
|
|
|
21
36
|
"""
|
|
22
37
|
|
|
23
38
|
@abstractmethod
|
|
24
|
-
def monitor(self) ->
|
|
39
|
+
def monitor(self) -> MonitorStream:
|
|
25
40
|
"""Return a MonitorStream that async-iterates payloads sent to this channel"""
|
|
26
41
|
|
|
27
42
|
@abstractmethod
|
|
28
43
|
async def send(self, payload: Packable):
|
|
29
44
|
"""send this payload to this channel"""
|
|
30
45
|
|
|
46
|
+
async def receiver(self, *, task_status=anyio.TASK_STATUS_IGNORED):
|
|
47
|
+
"""
|
|
48
|
+
A dummy receiver which the transport may override,
|
|
49
|
+
otherwise it's the client's job
|
|
50
|
+
"""
|
|
51
|
+
task_status.started()
|
|
52
|
+
|
|
31
53
|
|
|
32
54
|
class MonitorStream(metaclass=ABCMeta):
|
|
33
55
|
"""Async context manager and iterator that attaches to a channel and
|
|
@@ -37,15 +59,7 @@ class MonitorStream(metaclass=ABCMeta):
|
|
|
37
59
|
def __init__(self, transport: Transport):
|
|
38
60
|
self.transport = transport
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
async def __aenter__(self):
|
|
42
|
-
return self
|
|
43
|
-
|
|
44
|
-
@abstractmethod
|
|
45
|
-
async def __aexit__(self, *tb):
|
|
46
|
-
pass
|
|
47
|
-
|
|
48
|
-
def __aiter__(self):
|
|
62
|
+
def __aiter__(self) -> Self:
|
|
49
63
|
return self
|
|
50
64
|
|
|
51
65
|
@abstractmethod
|
|
@@ -1,22 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The main Actor module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
1
7
|
import anyio
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
2
10
|
import time
|
|
3
11
|
from random import Random
|
|
4
|
-
import os
|
|
5
|
-
import logging
|
|
6
|
-
from contextlib import asynccontextmanager
|
|
7
12
|
|
|
8
|
-
from .
|
|
9
|
-
from .exceptions import ActorTimeoutError, ActorCollisionError
|
|
10
|
-
from .events import * # pylint: disable=wildcard-import,unused-wildcard-import
|
|
11
|
-
from .nodelist import * # pylint: disable=wildcard-import,unused-wildcard-import
|
|
12
|
-
from .messages import * # pylint: disable=wildcard-import,unused-wildcard-import
|
|
13
|
+
from moat.util import CtxObj, create_queue
|
|
13
14
|
|
|
14
|
-
from
|
|
15
|
+
from .events import (
|
|
16
|
+
DetagEvent,
|
|
17
|
+
GoodNodeEvent,
|
|
18
|
+
PingEvent,
|
|
19
|
+
RawMsgEvent,
|
|
20
|
+
RecoverEvent,
|
|
21
|
+
SetupEvent,
|
|
22
|
+
TagEvent,
|
|
23
|
+
UntagEvent,
|
|
24
|
+
)
|
|
25
|
+
from .exceptions import ActorCollisionError, ActorTimeoutError
|
|
26
|
+
from .messages import HistoryMessage, InitMessage, Message, PingMessage, SetupMessage
|
|
27
|
+
from .nodelist import NodeList
|
|
28
|
+
|
|
29
|
+
from typing import TYPE_CHECKING
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from .abc import Transport
|
|
15
33
|
|
|
16
34
|
__all__ = [
|
|
17
35
|
"Actor",
|
|
18
36
|
]
|
|
19
37
|
|
|
38
|
+
|
|
20
39
|
async def spawn(taskgroup, proc, *args, **kw):
|
|
21
40
|
"""
|
|
22
41
|
Run a task within this object's task group.
|
|
@@ -36,7 +55,7 @@ async def spawn(taskgroup, proc, *args, **kw):
|
|
|
36
55
|
return await taskgroup.start(_run, proc, args, kw)
|
|
37
56
|
|
|
38
57
|
|
|
39
|
-
class Actor:
|
|
58
|
+
class Actor(CtxObj):
|
|
40
59
|
"""
|
|
41
60
|
Some jobs need a single controller so that tasks don't step on each other's
|
|
42
61
|
toes.
|
|
@@ -57,7 +76,7 @@ class Actor:
|
|
|
57
76
|
name (str): This node's name. **Must** be unambiguous.
|
|
58
77
|
cfg (dict): a dict containing additional configuration values.
|
|
59
78
|
enabled (bool): if False, start the actor in passive monitoring mode.
|
|
60
|
-
send_raw (bool): flag whether to
|
|
79
|
+
send_raw (bool): flag whether to forward raw-message events (debugging)
|
|
61
80
|
|
|
62
81
|
The config dict may contain these values.
|
|
63
82
|
|
|
@@ -92,28 +111,21 @@ class Actor:
|
|
|
92
111
|
_tg = None
|
|
93
112
|
_ae = None
|
|
94
113
|
|
|
95
|
-
DEFAULTS = dict(cycle=10, gap=1.5, nodes=5, splits=4, n_hosts=10, version=0, force_in=0)
|
|
96
|
-
|
|
97
114
|
def __init__( # pylint: disable=dangerous-default-value
|
|
98
115
|
self,
|
|
99
116
|
client: Transport,
|
|
100
117
|
name: str,
|
|
101
|
-
cfg: dict =
|
|
118
|
+
cfg: dict | None = None,
|
|
102
119
|
*,
|
|
103
120
|
enabled: bool = True,
|
|
104
121
|
send_raw: bool = False,
|
|
105
122
|
):
|
|
106
123
|
self._client = client
|
|
107
124
|
self._name = name
|
|
108
|
-
self.logger = logging.getLogger("asyncactor
|
|
109
|
-
|
|
110
|
-
self._cfg = {}
|
|
111
|
-
self._cfg.update(self.DEFAULTS)
|
|
112
|
-
self._cfg.update(cfg)
|
|
125
|
+
self.logger = logging.getLogger(f"asyncactor.{self._name}.{client.topic[-1]}")
|
|
113
126
|
|
|
114
|
-
self._version = SetupMessage(node=self._name, **
|
|
127
|
+
self._version = SetupMessage(node=self._name, **(cfg or {}))
|
|
115
128
|
self._version_job = None
|
|
116
|
-
self._version.verify()
|
|
117
129
|
self._off_nodes = self._version.nodes
|
|
118
130
|
self._self_seen = False
|
|
119
131
|
|
|
@@ -128,47 +140,47 @@ class Actor:
|
|
|
128
140
|
self._values = {} # map names to steps
|
|
129
141
|
self._history = NodeList(self._nodes) # those in the loop
|
|
130
142
|
self._prev_history = None
|
|
131
|
-
|
|
132
|
-
if rs is None:
|
|
133
|
-
self._rand = Random()
|
|
134
|
-
else:
|
|
135
|
-
import trio._core._run as tcr
|
|
136
|
-
|
|
137
|
-
self._rand = tcr._r
|
|
143
|
+
self._rand = Random() # noqa:S311
|
|
138
144
|
|
|
139
145
|
self._next_ping_time = 0
|
|
140
146
|
self._recover_pings = {}
|
|
141
147
|
self._count_not_on = 0
|
|
142
148
|
|
|
143
149
|
def __repr__(self):
|
|
144
|
-
return "<Actor
|
|
150
|
+
return f"<Actor {self._name} {self._client!r}>"
|
|
145
151
|
|
|
146
152
|
# Accessors for config values
|
|
147
153
|
|
|
148
154
|
@property
|
|
149
|
-
def _cycle(self):
|
|
155
|
+
def _cycle(self) -> float:
|
|
156
|
+
"'cycle' from config"
|
|
150
157
|
return self._version.cycle
|
|
151
158
|
|
|
152
159
|
@property
|
|
153
|
-
def _gap(self):
|
|
160
|
+
def _gap(self) -> float:
|
|
161
|
+
"'gap' from config"
|
|
154
162
|
return self._version.gap
|
|
155
163
|
|
|
156
164
|
@property
|
|
157
|
-
def _nodes(self):
|
|
165
|
+
def _nodes(self) -> int:
|
|
166
|
+
"'nodes' from config"
|
|
158
167
|
if self._tagged < 0:
|
|
159
168
|
return self._off_nodes
|
|
160
169
|
return self._version.nodes
|
|
161
170
|
|
|
162
171
|
@property
|
|
163
|
-
def _splits(self):
|
|
172
|
+
def _splits(self) -> int:
|
|
173
|
+
"'splits' from config"
|
|
164
174
|
return self._version.splits
|
|
165
175
|
|
|
166
176
|
@property
|
|
167
|
-
def _n_hosts(self):
|
|
177
|
+
def _n_hosts(self) -> int:
|
|
178
|
+
"'n_hosts' from config"
|
|
168
179
|
return self._version.n_hosts
|
|
169
180
|
|
|
170
181
|
@property
|
|
171
|
-
def _force_in(self):
|
|
182
|
+
def _force_in(self) -> bool:
|
|
183
|
+
"'force_in' from config"
|
|
172
184
|
return self._version.force_in
|
|
173
185
|
|
|
174
186
|
@property
|
|
@@ -178,10 +190,12 @@ class Actor:
|
|
|
178
190
|
|
|
179
191
|
@property
|
|
180
192
|
def name(self):
|
|
193
|
+
"my name"
|
|
181
194
|
return self._name
|
|
182
195
|
|
|
183
196
|
@property
|
|
184
197
|
def cycle_time(self):
|
|
198
|
+
"'cycle' from config"
|
|
185
199
|
return self._cycle
|
|
186
200
|
|
|
187
201
|
@property
|
|
@@ -240,30 +254,20 @@ class Actor:
|
|
|
240
254
|
self._valid_pings += 1
|
|
241
255
|
await self.post_event(PingEvent(msg))
|
|
242
256
|
|
|
243
|
-
def
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
finally:
|
|
258
|
-
with anyio.fail_after(2, shield=True):
|
|
259
|
-
self._tg = None
|
|
260
|
-
tg.cancel_scope.cancel()
|
|
261
|
-
|
|
262
|
-
self._ae = work(self)
|
|
263
|
-
return self._ae.__aenter__() # pylint: disable=E1101
|
|
264
|
-
|
|
265
|
-
def __aexit__(self, *tb):
|
|
266
|
-
return self._ae.__aexit__(*tb) # pylint: disable=E1101
|
|
257
|
+
async def _ctx(self):
|
|
258
|
+
async with anyio.create_task_group() as tg:
|
|
259
|
+
try:
|
|
260
|
+
evt = anyio.Event()
|
|
261
|
+
self._tg = tg
|
|
262
|
+
self._reader = await spawn(tg, self.read_task, evt)
|
|
263
|
+
|
|
264
|
+
await evt.wait()
|
|
265
|
+
self._worker = await spawn(tg, self._run)
|
|
266
|
+
self._pinger = await spawn(tg, self._ping)
|
|
267
|
+
yield self
|
|
268
|
+
finally:
|
|
269
|
+
self._tg = None
|
|
270
|
+
tg.cancel_scope.cancel()
|
|
267
271
|
|
|
268
272
|
def __aiter__(self):
|
|
269
273
|
return self
|
|
@@ -309,24 +313,23 @@ class Actor:
|
|
|
309
313
|
"""
|
|
310
314
|
if self._send_raw:
|
|
311
315
|
await self.post_event(RawMsgEvent(msg))
|
|
312
|
-
msg = Message(
|
|
316
|
+
msg = Message.read(msg)
|
|
313
317
|
if type(msg) is InitMessage and msg.node == self._name:
|
|
314
318
|
if self._self_seen:
|
|
315
319
|
# We may see our own Hello only once
|
|
316
|
-
raise ActorCollisionError
|
|
320
|
+
raise ActorCollisionError
|
|
317
321
|
self._self_seen = True
|
|
318
322
|
|
|
319
323
|
await self._rdr_q.put(msg)
|
|
320
324
|
|
|
321
325
|
async def _run(self):
|
|
322
|
-
"""
|
|
323
|
-
"""
|
|
326
|
+
""" """
|
|
324
327
|
await self._send_msg(InitMessage(node=self._name))
|
|
325
328
|
await self._send_msg(self._version)
|
|
326
329
|
|
|
327
330
|
await anyio.sleep((self.random / 2 + 1.5) * self._gap + self._cycle)
|
|
328
331
|
if not self._self_seen:
|
|
329
|
-
raise ActorTimeoutError
|
|
332
|
+
raise ActorTimeoutError
|
|
330
333
|
await self._send_ping()
|
|
331
334
|
|
|
332
335
|
t_dest = 0
|
|
@@ -390,13 +393,9 @@ class Actor:
|
|
|
390
393
|
|
|
391
394
|
It is an error to not supersede the old config.
|
|
392
395
|
"""
|
|
393
|
-
|
|
394
|
-
self._cfg.update(self.DEFAULTS)
|
|
395
|
-
self._cfg.update(cfg)
|
|
396
|
-
|
|
397
|
-
v = SetupMessage(**self._cfg)
|
|
396
|
+
v = SetupMessage(**cfg)
|
|
398
397
|
if v.version <= self._version.version:
|
|
399
|
-
raise ValueError("You need a version >
|
|
398
|
+
raise ValueError(f"You need a version > {self._version.version}")
|
|
400
399
|
self._version = v
|
|
401
400
|
await self._send_msg(v)
|
|
402
401
|
|
|
@@ -407,11 +406,10 @@ class Actor:
|
|
|
407
406
|
Args:
|
|
408
407
|
length (int): New max length of the history. Default: Leave alone.
|
|
409
408
|
"""
|
|
410
|
-
if length is not None:
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
await self._send_msg(self._version)
|
|
409
|
+
if length is not None and self._version.nodes < length:
|
|
410
|
+
self._version.nodes = length
|
|
411
|
+
self._version.version += 1
|
|
412
|
+
await self._send_msg(self._version)
|
|
415
413
|
|
|
416
414
|
if self._tagged != -1:
|
|
417
415
|
return
|
|
@@ -657,7 +655,7 @@ class Actor:
|
|
|
657
655
|
history = self._prev_history = self._history
|
|
658
656
|
self._history += self._name
|
|
659
657
|
self._get_next_ping_time()
|
|
660
|
-
msg.history = history[0 : self._splits]
|
|
658
|
+
msg.history = history[0 : self._splits]
|
|
661
659
|
await self._send_msg(msg)
|
|
662
660
|
|
|
663
661
|
async def _send_msg(self, msg: Message):
|
|
@@ -753,10 +751,9 @@ class Actor:
|
|
|
753
751
|
lv += 1
|
|
754
752
|
if self._nodes > 0 and lv > self._nodes:
|
|
755
753
|
break
|
|
756
|
-
if not self._ready:
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
return 2 + self.random / 3
|
|
754
|
+
if not self._ready and p > lv // 2:
|
|
755
|
+
# No, the first active host is too far back.
|
|
756
|
+
return 2 + self.random / 3
|
|
760
757
|
|
|
761
758
|
return self.ping_delay(
|
|
762
759
|
s - 1,
|
|
@@ -1,42 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"""
|
|
2
|
+
Listener on top of a MoaT-KV transport
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from asyncactor.abc import MonitorStream, Transport
|
|
8
|
+
|
|
9
|
+
from moat.util import CtxObj
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from collections.abc import AsyncIterator
|
|
15
|
+
|
|
16
|
+
from typing import Self
|
|
17
|
+
|
|
5
18
|
import msgpack
|
|
6
19
|
|
|
7
20
|
|
|
8
21
|
class MoatKVTransport(Transport):
|
|
22
|
+
"Transport"
|
|
23
|
+
|
|
9
24
|
def __init__(self, conn, *topic):
|
|
10
25
|
self.conn = conn
|
|
11
26
|
self.topic = topic
|
|
12
27
|
|
|
13
28
|
def monitor(self):
|
|
29
|
+
"monitor"
|
|
14
30
|
return MoatKVMonitor(self)
|
|
15
31
|
|
|
16
32
|
async def send(self, payload):
|
|
33
|
+
"send message"
|
|
17
34
|
payload = msgpack.packb(payload, use_bin_type=True)
|
|
18
35
|
await self.conn.send(*self.topic, payload=payload)
|
|
19
36
|
|
|
20
37
|
def __repr__(self):
|
|
21
|
-
return "<Moat-KV
|
|
38
|
+
return f"<Moat-KV:{self.topic} {self.conn!r}>"
|
|
22
39
|
|
|
23
40
|
|
|
24
|
-
class MoatKVMonitor(MonitorStream):
|
|
25
|
-
|
|
26
|
-
_mon2 = None
|
|
27
|
-
_it = None
|
|
41
|
+
class MoatKVMonitor(MonitorStream, CtxObj):
|
|
42
|
+
"Monitor"
|
|
28
43
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if hasattr(self._mon1, "__aenter__"):
|
|
32
|
-
self._mon2 = await self._mon1.__aenter__()
|
|
33
|
-
else:
|
|
34
|
-
self._mon2 = self._mon1
|
|
35
|
-
return self
|
|
44
|
+
_mon = None
|
|
45
|
+
_it = None
|
|
36
46
|
|
|
37
|
-
async def
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
async def _ctx(self) -> AsyncIterator[Self]:
|
|
48
|
+
async with self.transport.conn.monitor(*self.transport.topic) as self._mon:
|
|
49
|
+
yield self
|
|
40
50
|
|
|
41
51
|
def __aiter__(self):
|
|
42
52
|
self._it = self._mon2.__aiter__()
|
|
@@ -48,7 +58,7 @@ class MoatKVMonitor(MonitorStream):
|
|
|
48
58
|
return msg
|
|
49
59
|
|
|
50
60
|
def __repr__(self):
|
|
51
|
-
return "<Mon
|
|
61
|
+
return f"<Mon:{self.transport}>"
|
|
52
62
|
|
|
53
63
|
|
|
54
64
|
Transport = MoatKVTransport
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Listener on top of an async mqttproto connection
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from asyncactor.abc import MonitorStream, Transport
|
|
8
|
+
|
|
9
|
+
from mqttproto import QoS
|
|
10
|
+
|
|
11
|
+
from moat.util import CtxObj, Path
|
|
12
|
+
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from collections.abc import AsyncIterator, Awaitable
|
|
17
|
+
|
|
18
|
+
from moat.lib.codec import Codec
|
|
19
|
+
from moat.link.client import Link
|
|
20
|
+
|
|
21
|
+
from typing import Self
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MQTTPTransport(Transport):
|
|
25
|
+
"""
|
|
26
|
+
MQTTProto Transport for AsyncActor.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, conn: Link, topic: Path, codec: Codec | str = None):
|
|
30
|
+
self.conn = conn
|
|
31
|
+
self.topic = topic
|
|
32
|
+
self.codec = codec
|
|
33
|
+
|
|
34
|
+
def monitor(self):
|
|
35
|
+
"""
|
|
36
|
+
Accept incoming messages::
|
|
37
|
+
|
|
38
|
+
async with conn.monitor() as m:
|
|
39
|
+
async for msg in m:
|
|
40
|
+
await process(msg)
|
|
41
|
+
"""
|
|
42
|
+
return MQTTMonitor(self)
|
|
43
|
+
|
|
44
|
+
def send(self, payload) -> Awaitable[None]:
|
|
45
|
+
"""
|
|
46
|
+
Send a message.
|
|
47
|
+
"""
|
|
48
|
+
return self.conn.send(self.topic, payload, qos=QoS.AT_LEAST_ONCE, codec=self.codec)
|
|
49
|
+
|
|
50
|
+
def __repr__(self):
|
|
51
|
+
return f"<MQTT:[self.tag] @{self.conn!r}>"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MQTTMonitor(MonitorStream, CtxObj):
|
|
55
|
+
"Monitor"
|
|
56
|
+
|
|
57
|
+
async def _ctx(self) -> AsyncIterator[Self]:
|
|
58
|
+
c = self.transport
|
|
59
|
+
async with c.conn.monitor(c.tag, maximum_qos=QoS.AT_LEAST_ONCE, codec=self.codec) as mon:
|
|
60
|
+
self._it = aiter(mon)
|
|
61
|
+
try:
|
|
62
|
+
yield self
|
|
63
|
+
finally:
|
|
64
|
+
self._it = None
|
|
65
|
+
|
|
66
|
+
def __aiter__(self):
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
async def __anext__(self):
|
|
70
|
+
msg = (await anext(self._it)).data
|
|
71
|
+
return msg
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return f"<Mon:{self.transport}>"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
Transport = MQTTPTransport
|