asyncactor 0.24.0__tar.gz → 0.25.0__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.0}/PKG-INFO +29 -9
- asyncactor-0.25.0/asyncactor/__init__.py +15 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/abc.py +30 -16
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/actor.py +78 -75
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/backend/__init__.py +8 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/backend/moat_kv.py +30 -20
- asyncactor-0.25.0/asyncactor/backend/moat_link.py +77 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/backend/mqtt.py +30 -20
- asyncactor-0.25.0/asyncactor/backend/mqttproto.py +78 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/backend/serf.py +10 -12
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/events.py +61 -51
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/exceptions.py +7 -6
- asyncactor-0.25.0/asyncactor/messages.py +175 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor/nodelist.py +11 -7
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor.egg-info/PKG-INFO +29 -9
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor.egg-info/SOURCES.txt +4 -3
- asyncactor-0.25.0/asyncactor.egg-info/requires.txt +19 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor.egg-info/top_level.txt +0 -1
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/source/conf.py +34 -28
- asyncactor-0.25.0/pyproject.toml +123 -0
- asyncactor-0.25.0/setup.cfg +4 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/tests/__init__.py +4 -1
- {asyncactor-0.24.0 → asyncactor-0.25.0}/tests/mock_serf.py +12 -9
- {asyncactor-0.24.0 → asyncactor-0.25.0}/tests/test_actor.py +11 -8
- {asyncactor-0.24.0 → asyncactor-0.25.0}/tests/test_mosquitto.py +8 -5
- asyncactor-0.25.0/tests/test_mqttproto.py +130 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/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.0}/.coveragerc +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/.gitignore +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/.pylintrc +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/.readthedocs.yml +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/.style.yapf +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/.travis.yml +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/CHANGELOG.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/LICENSE +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/Makefile +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/README.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/asyncactor.egg-info/dependency_links.txt +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/ci/rtd-requirements.txt +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/ci/test-requirements.txt +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/ci/travis.sh +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/Makefile +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/make.bat +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/source/_static/.git_empty +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/source/actor.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/source/history.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/source/index.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/docs/source/rationale.rst +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/mktag +0 -0
- {asyncactor-0.24.0 → asyncactor-0.25.0}/tox.ini +0 -0
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: asyncactor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.0
|
|
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>=2.5.2; extra == "dev"
|
|
33
|
+
Requires-Dist: trio>=0.23; extra == "dev"
|
|
14
34
|
|
|
15
35
|
asyncactor
|
|
16
36
|
==========
|
|
@@ -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 trio
|
|
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=trio.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
|
-
|
|
10
|
-
from .events import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
from moat.util import CtxObj, create_queue
|
|
14
|
+
|
|
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
|
|
13
30
|
|
|
14
|
-
|
|
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
|
|
125
|
+
self.logger = logging.getLogger(f"asyncactor.{self._name}.{client.topic[-1]}")
|
|
109
126
|
|
|
110
|
-
self.
|
|
111
|
-
self._cfg.update(self.DEFAULTS)
|
|
112
|
-
self._cfg.update(cfg)
|
|
113
|
-
|
|
114
|
-
self._version = SetupMessage(node=self._name, **self._cfg)
|
|
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
|
|
|
@@ -130,45 +142,51 @@ class Actor:
|
|
|
130
142
|
self._prev_history = None
|
|
131
143
|
rs = os.environ.get("PYTHONHASHSEED", None)
|
|
132
144
|
if rs is None:
|
|
133
|
-
self._rand = Random()
|
|
145
|
+
self._rand = Random() # noqa:S311
|
|
134
146
|
else:
|
|
135
147
|
import trio._core._run as tcr
|
|
136
148
|
|
|
137
|
-
self._rand = tcr._r
|
|
149
|
+
self._rand = tcr._r # noqa:SLF001
|
|
138
150
|
|
|
139
151
|
self._next_ping_time = 0
|
|
140
152
|
self._recover_pings = {}
|
|
141
153
|
self._count_not_on = 0
|
|
142
154
|
|
|
143
155
|
def __repr__(self):
|
|
144
|
-
return "<Actor
|
|
156
|
+
return f"<Actor {self._name} {self._client!r}>"
|
|
145
157
|
|
|
146
158
|
# Accessors for config values
|
|
147
159
|
|
|
148
160
|
@property
|
|
149
|
-
def _cycle(self):
|
|
161
|
+
def _cycle(self) -> float:
|
|
162
|
+
"'cycle' from config"
|
|
150
163
|
return self._version.cycle
|
|
151
164
|
|
|
152
165
|
@property
|
|
153
|
-
def _gap(self):
|
|
166
|
+
def _gap(self) -> float:
|
|
167
|
+
"'gap' from config"
|
|
154
168
|
return self._version.gap
|
|
155
169
|
|
|
156
170
|
@property
|
|
157
|
-
def _nodes(self):
|
|
171
|
+
def _nodes(self) -> int:
|
|
172
|
+
"'nodes' from config"
|
|
158
173
|
if self._tagged < 0:
|
|
159
174
|
return self._off_nodes
|
|
160
175
|
return self._version.nodes
|
|
161
176
|
|
|
162
177
|
@property
|
|
163
|
-
def _splits(self):
|
|
178
|
+
def _splits(self) -> int:
|
|
179
|
+
"'splits' from config"
|
|
164
180
|
return self._version.splits
|
|
165
181
|
|
|
166
182
|
@property
|
|
167
|
-
def _n_hosts(self):
|
|
183
|
+
def _n_hosts(self) -> int:
|
|
184
|
+
"'n_hosts' from config"
|
|
168
185
|
return self._version.n_hosts
|
|
169
186
|
|
|
170
187
|
@property
|
|
171
|
-
def _force_in(self):
|
|
188
|
+
def _force_in(self) -> bool:
|
|
189
|
+
"'force_in' from config"
|
|
172
190
|
return self._version.force_in
|
|
173
191
|
|
|
174
192
|
@property
|
|
@@ -178,10 +196,12 @@ class Actor:
|
|
|
178
196
|
|
|
179
197
|
@property
|
|
180
198
|
def name(self):
|
|
199
|
+
"my name"
|
|
181
200
|
return self._name
|
|
182
201
|
|
|
183
202
|
@property
|
|
184
203
|
def cycle_time(self):
|
|
204
|
+
"'cycle' from config"
|
|
185
205
|
return self._cycle
|
|
186
206
|
|
|
187
207
|
@property
|
|
@@ -240,30 +260,20 @@ class Actor:
|
|
|
240
260
|
self._valid_pings += 1
|
|
241
261
|
await self.post_event(PingEvent(msg))
|
|
242
262
|
|
|
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
|
|
263
|
+
async def _ctx(self):
|
|
264
|
+
async with anyio.create_task_group() as tg:
|
|
265
|
+
try:
|
|
266
|
+
evt = anyio.Event()
|
|
267
|
+
self._tg = tg
|
|
268
|
+
self._reader = await spawn(tg, self.read_task, evt)
|
|
269
|
+
|
|
270
|
+
await evt.wait()
|
|
271
|
+
self._worker = await spawn(tg, self._run)
|
|
272
|
+
self._pinger = await spawn(tg, self._ping)
|
|
273
|
+
yield self
|
|
274
|
+
finally:
|
|
275
|
+
self._tg = None
|
|
276
|
+
tg.cancel_scope.cancel()
|
|
267
277
|
|
|
268
278
|
def __aiter__(self):
|
|
269
279
|
return self
|
|
@@ -309,24 +319,23 @@ class Actor:
|
|
|
309
319
|
"""
|
|
310
320
|
if self._send_raw:
|
|
311
321
|
await self.post_event(RawMsgEvent(msg))
|
|
312
|
-
msg = Message(
|
|
322
|
+
msg = Message.read(msg)
|
|
313
323
|
if type(msg) is InitMessage and msg.node == self._name:
|
|
314
324
|
if self._self_seen:
|
|
315
325
|
# We may see our own Hello only once
|
|
316
|
-
raise ActorCollisionError
|
|
326
|
+
raise ActorCollisionError
|
|
317
327
|
self._self_seen = True
|
|
318
328
|
|
|
319
329
|
await self._rdr_q.put(msg)
|
|
320
330
|
|
|
321
331
|
async def _run(self):
|
|
322
|
-
"""
|
|
323
|
-
"""
|
|
332
|
+
""" """
|
|
324
333
|
await self._send_msg(InitMessage(node=self._name))
|
|
325
334
|
await self._send_msg(self._version)
|
|
326
335
|
|
|
327
336
|
await anyio.sleep((self.random / 2 + 1.5) * self._gap + self._cycle)
|
|
328
337
|
if not self._self_seen:
|
|
329
|
-
raise ActorTimeoutError
|
|
338
|
+
raise ActorTimeoutError
|
|
330
339
|
await self._send_ping()
|
|
331
340
|
|
|
332
341
|
t_dest = 0
|
|
@@ -390,13 +399,9 @@ class Actor:
|
|
|
390
399
|
|
|
391
400
|
It is an error to not supersede the old config.
|
|
392
401
|
"""
|
|
393
|
-
|
|
394
|
-
self._cfg.update(self.DEFAULTS)
|
|
395
|
-
self._cfg.update(cfg)
|
|
396
|
-
|
|
397
|
-
v = SetupMessage(**self._cfg)
|
|
402
|
+
v = SetupMessage(**cfg)
|
|
398
403
|
if v.version <= self._version.version:
|
|
399
|
-
raise ValueError("You need a version >
|
|
404
|
+
raise ValueError(f"You need a version > {self._version.version}")
|
|
400
405
|
self._version = v
|
|
401
406
|
await self._send_msg(v)
|
|
402
407
|
|
|
@@ -407,11 +412,10 @@ class Actor:
|
|
|
407
412
|
Args:
|
|
408
413
|
length (int): New max length of the history. Default: Leave alone.
|
|
409
414
|
"""
|
|
410
|
-
if length is not None:
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
await self._send_msg(self._version)
|
|
415
|
+
if length is not None and self._version.nodes < length:
|
|
416
|
+
self._version.nodes = length
|
|
417
|
+
self._version.version += 1
|
|
418
|
+
await self._send_msg(self._version)
|
|
415
419
|
|
|
416
420
|
if self._tagged != -1:
|
|
417
421
|
return
|
|
@@ -657,7 +661,7 @@ class Actor:
|
|
|
657
661
|
history = self._prev_history = self._history
|
|
658
662
|
self._history += self._name
|
|
659
663
|
self._get_next_ping_time()
|
|
660
|
-
msg.history = history[0 : self._splits]
|
|
664
|
+
msg.history = history[0 : self._splits]
|
|
661
665
|
await self._send_msg(msg)
|
|
662
666
|
|
|
663
667
|
async def _send_msg(self, msg: Message):
|
|
@@ -753,10 +757,9 @@ class Actor:
|
|
|
753
757
|
lv += 1
|
|
754
758
|
if self._nodes > 0 and lv > self._nodes:
|
|
755
759
|
break
|
|
756
|
-
if not self._ready:
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
return 2 + self.random / 3
|
|
760
|
+
if not self._ready and p > lv // 2:
|
|
761
|
+
# No, the first active host is too far back.
|
|
762
|
+
return 2 + self.random / 3
|
|
760
763
|
|
|
761
764
|
return self.ping_delay(
|
|
762
765
|
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
|