koi-net 1.1.0b7__py3-none-any.whl → 1.2.0b1__py3-none-any.whl
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.
Potentially problematic release.
This version of koi-net might be problematic. Click here for more details.
- koi_net/__init__.py +1 -1
- koi_net/behaviors.py +51 -0
- koi_net/cli/__init__.py +1 -0
- koi_net/cli/commands.py +99 -0
- koi_net/cli/models.py +41 -0
- koi_net/config.py +34 -0
- koi_net/context.py +16 -20
- koi_net/core.py +208 -171
- koi_net/default_actions.py +10 -1
- koi_net/effector.py +39 -24
- koi_net/{network/behavior.py → handshaker.py} +17 -20
- koi_net/kobj_worker.py +45 -0
- koi_net/lifecycle.py +71 -41
- koi_net/models.py +14 -0
- koi_net/network/error_handler.py +12 -10
- koi_net/network/event_queue.py +14 -184
- koi_net/network/graph.py +14 -9
- koi_net/network/request_handler.py +31 -19
- koi_net/network/resolver.py +6 -9
- koi_net/network/response_handler.py +5 -6
- koi_net/poll_event_buffer.py +17 -0
- koi_net/poller.py +12 -5
- koi_net/processor/default_handlers.py +84 -35
- koi_net/processor/event_worker.py +121 -0
- koi_net/processor/handler.py +4 -2
- koi_net/processor/knowledge_object.py +19 -7
- koi_net/processor/knowledge_pipeline.py +7 -26
- koi_net/processor/kobj_queue.py +51 -0
- koi_net/protocol/api_models.py +3 -2
- koi_net/protocol/node.py +3 -3
- koi_net/secure.py +28 -8
- koi_net/server.py +25 -9
- koi_net/utils.py +18 -0
- koi_net/worker.py +10 -0
- {koi_net-1.1.0b7.dist-info → koi_net-1.2.0b1.dist-info}/METADATA +7 -3
- koi_net-1.2.0b1.dist-info/RECORD +49 -0
- koi_net-1.2.0b1.dist-info/entry_points.txt +2 -0
- koi_net/processor/interface.py +0 -101
- koi_net-1.1.0b7.dist-info/RECORD +0 -38
- {koi_net-1.1.0b7.dist-info → koi_net-1.2.0b1.dist-info}/WHEEL +0 -0
- {koi_net-1.1.0b7.dist-info → koi_net-1.2.0b1.dist-info}/licenses/LICENSE +0 -0
koi_net/network/resolver.py
CHANGED
|
@@ -12,17 +12,15 @@ from ..protocol.event import Event
|
|
|
12
12
|
from ..protocol.api_models import ErrorResponse
|
|
13
13
|
from ..identity import NodeIdentity
|
|
14
14
|
from ..config import NodeConfig
|
|
15
|
-
from ..effector import Effector
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
class NetworkResolver:
|
|
21
|
-
"""
|
|
20
|
+
"""Handles resolving nodes or knowledge objects from the network."""
|
|
22
21
|
|
|
23
22
|
config: NodeConfig
|
|
24
23
|
identity: NodeIdentity
|
|
25
|
-
effector: Effector
|
|
26
24
|
cache: Cache
|
|
27
25
|
graph: NetworkGraph
|
|
28
26
|
request_handler: RequestHandler
|
|
@@ -32,7 +30,6 @@ class NetworkResolver:
|
|
|
32
30
|
config: NodeConfig,
|
|
33
31
|
cache: Cache,
|
|
34
32
|
identity: NodeIdentity,
|
|
35
|
-
effector: Effector,
|
|
36
33
|
graph: NetworkGraph,
|
|
37
34
|
request_handler: RequestHandler,
|
|
38
35
|
):
|
|
@@ -41,15 +38,14 @@ class NetworkResolver:
|
|
|
41
38
|
self.cache = cache
|
|
42
39
|
self.graph = graph
|
|
43
40
|
self.request_handler = request_handler
|
|
44
|
-
self.effector = effector
|
|
45
41
|
|
|
46
42
|
self.poll_event_queue = dict()
|
|
47
43
|
self.webhook_event_queue = dict()
|
|
48
44
|
|
|
49
45
|
def get_state_providers(self, rid_type: RIDType) -> list[KoiNetNode]:
|
|
50
|
-
"""Returns list of node RIDs which provide state for
|
|
46
|
+
"""Returns list of node RIDs which provide state for specified RID type."""
|
|
51
47
|
|
|
52
|
-
logger.debug(f"Looking for state providers of
|
|
48
|
+
logger.debug(f"Looking for state providers of {rid_type}")
|
|
53
49
|
provider_nodes = []
|
|
54
50
|
for node_rid in self.cache.list_rids(rid_types=[KoiNetNode]):
|
|
55
51
|
if node_rid == self.identity.rid:
|
|
@@ -106,9 +102,10 @@ class NetworkResolver:
|
|
|
106
102
|
return remote_manifest, node_rid
|
|
107
103
|
|
|
108
104
|
def poll_neighbors(self) -> dict[KoiNetNode, list[Event]]:
|
|
109
|
-
"""Polls all
|
|
105
|
+
"""Polls all neighbor nodes and returns compiled list of events.
|
|
110
106
|
|
|
111
|
-
|
|
107
|
+
Neighbor nodes also include the first contact, regardless of
|
|
108
|
+
whether the first contact profile is known to this node.
|
|
112
109
|
"""
|
|
113
110
|
|
|
114
111
|
graph_neighbors = self.graph.get_neighbors()
|
|
@@ -12,7 +12,6 @@ from ..protocol.api_models import (
|
|
|
12
12
|
FetchManifests,
|
|
13
13
|
FetchBundles,
|
|
14
14
|
)
|
|
15
|
-
from ..effector import Effector
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
@@ -21,30 +20,29 @@ class ResponseHandler:
|
|
|
21
20
|
"""Handles generating responses to requests from other KOI nodes."""
|
|
22
21
|
|
|
23
22
|
cache: Cache
|
|
24
|
-
effector: Effector
|
|
25
23
|
|
|
26
24
|
def __init__(
|
|
27
25
|
self,
|
|
28
26
|
cache: Cache,
|
|
29
|
-
effector: Effector,
|
|
30
27
|
):
|
|
31
28
|
self.cache = cache
|
|
32
|
-
self.effector = effector
|
|
33
29
|
|
|
34
30
|
def fetch_rids(self, req: FetchRids, source: KoiNetNode) -> RidsPayload:
|
|
31
|
+
"""Returns response to fetch RIDs request."""
|
|
35
32
|
logger.info(f"Request to fetch rids, allowed types {req.rid_types}")
|
|
36
33
|
rids = self.cache.list_rids(req.rid_types)
|
|
37
34
|
|
|
38
35
|
return RidsPayload(rids=rids)
|
|
39
36
|
|
|
40
37
|
def fetch_manifests(self, req: FetchManifests, source: KoiNetNode) -> ManifestsPayload:
|
|
38
|
+
"""Returns response to fetch manifests request."""
|
|
41
39
|
logger.info(f"Request to fetch manifests, allowed types {req.rid_types}, rids {req.rids}")
|
|
42
40
|
|
|
43
41
|
manifests: list[Manifest] = []
|
|
44
42
|
not_found: list[RID] = []
|
|
45
43
|
|
|
46
44
|
for rid in (req.rids or self.cache.list_rids(req.rid_types)):
|
|
47
|
-
bundle = self.
|
|
45
|
+
bundle = self.cache.read(rid)
|
|
48
46
|
if bundle:
|
|
49
47
|
manifests.append(bundle.manifest)
|
|
50
48
|
else:
|
|
@@ -53,13 +51,14 @@ class ResponseHandler:
|
|
|
53
51
|
return ManifestsPayload(manifests=manifests, not_found=not_found)
|
|
54
52
|
|
|
55
53
|
def fetch_bundles(self, req: FetchBundles, source: KoiNetNode) -> BundlesPayload:
|
|
54
|
+
"""Returns response to fetch bundles request."""
|
|
56
55
|
logger.info(f"Request to fetch bundles, requested rids {req.rids}")
|
|
57
56
|
|
|
58
57
|
bundles: list[Bundle] = []
|
|
59
58
|
not_found: list[RID] = []
|
|
60
59
|
|
|
61
60
|
for rid in req.rids:
|
|
62
|
-
bundle = self.
|
|
61
|
+
bundle = self.cache.read(rid)
|
|
63
62
|
if bundle:
|
|
64
63
|
bundles.append(bundle)
|
|
65
64
|
else:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from rid_lib.types import KoiNetNode
|
|
2
|
+
|
|
3
|
+
from koi_net.protocol.event import Event
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PollEventBuffer:
|
|
7
|
+
buffers: dict[KoiNetNode, list[Event]]
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.buffers = dict()
|
|
11
|
+
|
|
12
|
+
def put(self, node: KoiNetNode, event: Event):
|
|
13
|
+
event_buf = self.buffers.setdefault(node, [])
|
|
14
|
+
event_buf.append(event)
|
|
15
|
+
|
|
16
|
+
def flush(self, node: KoiNetNode):
|
|
17
|
+
return self.buffers.pop(node, [])
|
koi_net/poller.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import time
|
|
3
3
|
import logging
|
|
4
|
-
from .processor.
|
|
4
|
+
from .processor.kobj_queue import KobjQueue
|
|
5
5
|
from .lifecycle import NodeLifecycle
|
|
6
6
|
from .network.resolver import NetworkResolver
|
|
7
7
|
from .config import NodeConfig
|
|
@@ -10,26 +10,33 @@ logger = logging.getLogger(__name__)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class NodePoller:
|
|
13
|
+
"""Manages polling based event loop for partial nodes."""
|
|
14
|
+
kobj_queue: KobjQueue
|
|
15
|
+
lifecycle: NodeLifecycle
|
|
16
|
+
resolver: NetworkResolver
|
|
17
|
+
config: NodeConfig
|
|
18
|
+
|
|
13
19
|
def __init__(
|
|
14
20
|
self,
|
|
15
|
-
|
|
21
|
+
kobj_queue: KobjQueue,
|
|
16
22
|
lifecycle: NodeLifecycle,
|
|
17
23
|
resolver: NetworkResolver,
|
|
18
24
|
config: NodeConfig
|
|
19
25
|
):
|
|
20
|
-
self.
|
|
26
|
+
self.kobj_queue = kobj_queue
|
|
21
27
|
self.lifecycle = lifecycle
|
|
22
28
|
self.resolver = resolver
|
|
23
29
|
self.config = config
|
|
24
30
|
|
|
25
31
|
def poll(self):
|
|
32
|
+
"""Polls neighbors and processes returned events."""
|
|
26
33
|
neighbors = self.resolver.poll_neighbors()
|
|
27
34
|
for node_rid in neighbors:
|
|
28
35
|
for event in neighbors[node_rid]:
|
|
29
|
-
self.
|
|
30
|
-
self.processor.flush_kobj_queue()
|
|
36
|
+
self.kobj_queue.put_kobj(event=event, source=node_rid)
|
|
31
37
|
|
|
32
38
|
def run(self):
|
|
39
|
+
"""Runs polling event loop."""
|
|
33
40
|
with self.lifecycle.run():
|
|
34
41
|
while True:
|
|
35
42
|
start_time = time.time()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Implementation of default knowledge handlers."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from rid_lib.ext import Bundle
|
|
@@ -20,7 +20,8 @@ logger = logging.getLogger(__name__)
|
|
|
20
20
|
def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
21
21
|
"""Default RID handler.
|
|
22
22
|
|
|
23
|
-
Blocks external events about this node. Allows `FORGET` events if
|
|
23
|
+
Blocks external events about this node. Allows `FORGET` events if
|
|
24
|
+
RID is known to this node.
|
|
24
25
|
"""
|
|
25
26
|
if (kobj.rid == ctx.identity.rid and kobj.source):
|
|
26
27
|
logger.debug("Don't let anyone else tell me who I am!")
|
|
@@ -34,9 +35,11 @@ def basic_rid_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
34
35
|
|
|
35
36
|
@KnowledgeHandler.create(HandlerType.Manifest)
|
|
36
37
|
def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
37
|
-
"""
|
|
38
|
+
"""Decider based on incoming manifest and cache state.
|
|
38
39
|
|
|
39
|
-
Blocks manifests
|
|
40
|
+
Blocks manifests which have the same hash, or aren't newer than the
|
|
41
|
+
cached version. Sets the normalized event type to `NEW` or `UPDATE`
|
|
42
|
+
depending on whether the RID was previously known.
|
|
40
43
|
"""
|
|
41
44
|
prev_bundle = ctx.cache.read(kobj.rid)
|
|
42
45
|
|
|
@@ -63,9 +66,14 @@ def basic_manifest_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
63
66
|
@KnowledgeHandler.create(
|
|
64
67
|
handler_type=HandlerType.Bundle,
|
|
65
68
|
rid_types=[KoiNetNode],
|
|
66
|
-
event_types=[EventType.NEW, EventType.UPDATE]
|
|
67
|
-
)
|
|
69
|
+
event_types=[EventType.NEW, EventType.UPDATE])
|
|
68
70
|
def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
71
|
+
"""Maintains security of cached node profiles.
|
|
72
|
+
|
|
73
|
+
Blocks bundles with a mismatching public keys in their node profile
|
|
74
|
+
and RID from continuing through the pipeline.
|
|
75
|
+
"""
|
|
76
|
+
|
|
69
77
|
node_profile = kobj.bundle.validate_contents(NodeProfile)
|
|
70
78
|
node_rid: KoiNetNode = kobj.rid
|
|
71
79
|
|
|
@@ -80,7 +88,10 @@ def secure_profile_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
80
88
|
def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
81
89
|
"""Handles basic edge negotiation process.
|
|
82
90
|
|
|
83
|
-
Automatically approves proposed edges if they request RID types this
|
|
91
|
+
Automatically approves proposed edges if they request RID types this
|
|
92
|
+
node can provide (or KOI nodes/edges). Validates the edge type is
|
|
93
|
+
allowed for the node type (partial nodes cannot use webhooks). If
|
|
94
|
+
edge is invalid, a `FORGET` event is sent to the other node.
|
|
84
95
|
"""
|
|
85
96
|
|
|
86
97
|
# only respond when source is another node
|
|
@@ -96,7 +107,7 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
96
107
|
logger.debug("Handling edge negotiation")
|
|
97
108
|
|
|
98
109
|
peer_rid = edge_profile.target
|
|
99
|
-
peer_bundle = ctx.
|
|
110
|
+
peer_bundle = ctx.cache.read(peer_rid)
|
|
100
111
|
|
|
101
112
|
if not peer_bundle:
|
|
102
113
|
logger.warning(f"Peer {peer_rid!r} unknown to me")
|
|
@@ -132,7 +143,7 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
132
143
|
edge_profile.status = EdgeStatus.APPROVED
|
|
133
144
|
updated_bundle = Bundle.generate(kobj.rid, edge_profile.model_dump())
|
|
134
145
|
|
|
135
|
-
ctx.
|
|
146
|
+
ctx.kobj_queue.put_kobj(bundle=updated_bundle, event_type=EventType.UPDATE)
|
|
136
147
|
return
|
|
137
148
|
|
|
138
149
|
elif edge_profile.target == ctx.identity.rid:
|
|
@@ -143,45 +154,74 @@ def edge_negotiation_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
143
154
|
# Network handlers
|
|
144
155
|
|
|
145
156
|
@KnowledgeHandler.create(HandlerType.Network, rid_types=[KoiNetNode])
|
|
146
|
-
def
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
# looking for event provider of nodes
|
|
150
|
-
if KoiNetNode not in node_profile.provides.event:
|
|
151
|
-
return
|
|
157
|
+
def node_contact_handler(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
158
|
+
"""Makes contact with providers of RID types of interest.
|
|
152
159
|
|
|
153
|
-
|
|
160
|
+
When an incoming node knowledge object is identified as a provider
|
|
161
|
+
of an RID type of interest, this handler will propose a new edge
|
|
162
|
+
subscribing to future node events, and fetch existing nodes to catch
|
|
163
|
+
up to the current state.
|
|
164
|
+
"""
|
|
165
|
+
# prevents nodes from attempting to form a self loop
|
|
154
166
|
if kobj.rid == ctx.identity.rid:
|
|
155
167
|
return
|
|
156
168
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
169
|
+
node_profile = kobj.bundle.validate_contents(NodeProfile)
|
|
170
|
+
|
|
171
|
+
available_rid_types = list(
|
|
172
|
+
set(ctx.config.koi_net.rid_types_of_interest) &
|
|
173
|
+
set(node_profile.provides.event)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if not available_rid_types:
|
|
162
177
|
return
|
|
163
178
|
|
|
164
179
|
logger.info("Identified a coordinator!")
|
|
165
180
|
logger.info("Proposing new edge")
|
|
166
181
|
|
|
167
|
-
|
|
168
|
-
|
|
182
|
+
# already have an edge established
|
|
183
|
+
edge_rid = ctx.graph.get_edge(
|
|
184
|
+
source=kobj.rid,
|
|
185
|
+
target=ctx.identity.rid,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if edge_rid:
|
|
189
|
+
prev_edge_bundle = ctx.cache.read(edge_rid)
|
|
190
|
+
edge_profile = prev_edge_bundle.validate_contents(EdgeProfile)
|
|
191
|
+
|
|
192
|
+
if set(edge_profile.rid_types) == set(available_rid_types):
|
|
193
|
+
# no change in rid types
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
edge_profile.rid_types = available_rid_types
|
|
197
|
+
edge_profile.status = EdgeStatus.PROPOSED
|
|
198
|
+
|
|
169
199
|
else:
|
|
170
|
-
|
|
200
|
+
source = kobj.rid
|
|
201
|
+
target = ctx.identity.rid
|
|
202
|
+
if ctx.identity.profile.node_type == NodeType.FULL:
|
|
203
|
+
edge_type = EdgeType.WEBHOOK
|
|
204
|
+
else:
|
|
205
|
+
edge_type = EdgeType.POLL
|
|
206
|
+
|
|
207
|
+
edge_rid = KoiNetEdge(sha256_hash(str(source) + str(target)))
|
|
208
|
+
edge_profile = EdgeProfile(
|
|
209
|
+
source=source,
|
|
210
|
+
target=target,
|
|
211
|
+
rid_types=available_rid_types,
|
|
212
|
+
edge_type=edge_type,
|
|
213
|
+
status=EdgeStatus.PROPOSED
|
|
214
|
+
)
|
|
171
215
|
|
|
172
216
|
# queued for processing
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
target=ctx.identity.rid,
|
|
176
|
-
edge_type=edge_type,
|
|
177
|
-
rid_types=[KoiNetNode]
|
|
178
|
-
))
|
|
217
|
+
edge_bundle = Bundle.generate(edge_rid, edge_profile.model_dump())
|
|
218
|
+
ctx.kobj_queue.put_kobj(bundle=edge_bundle)
|
|
179
219
|
|
|
180
220
|
logger.info("Catching up on network state")
|
|
181
221
|
|
|
182
222
|
payload = ctx.request_handler.fetch_rids(
|
|
183
223
|
node=kobj.rid,
|
|
184
|
-
rid_types=
|
|
224
|
+
rid_types=available_rid_types
|
|
185
225
|
)
|
|
186
226
|
for rid in payload.rids:
|
|
187
227
|
if rid == ctx.identity.rid:
|
|
@@ -193,15 +233,22 @@ def coordinator_contact(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
193
233
|
|
|
194
234
|
# marked as external since we are handling RIDs from another node
|
|
195
235
|
# will fetch remotely instead of checking local cache
|
|
196
|
-
ctx.
|
|
236
|
+
ctx.kobj_queue.put_kobj(rid=rid, source=kobj.rid)
|
|
197
237
|
logger.info("Done")
|
|
198
238
|
|
|
199
239
|
|
|
200
240
|
@KnowledgeHandler.create(HandlerType.Network)
|
|
201
241
|
def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
202
|
-
"""
|
|
242
|
+
"""Adds subscriber nodes to network targetes.
|
|
203
243
|
|
|
204
|
-
Allows broadcasting of all RID types this node is an event provider
|
|
244
|
+
Allows broadcasting of all RID types this node is an event provider
|
|
245
|
+
for (set in node profile), and other nodes have subscribed to. All
|
|
246
|
+
nodes will also broadcast about their own (internally sourced) KOI
|
|
247
|
+
node, and KOI edges that they are part of, regardless of their node
|
|
248
|
+
profile configuration. Finally, nodes will also broadcast about
|
|
249
|
+
edges to the other node involved (regardless of if they are
|
|
250
|
+
subscribed).
|
|
251
|
+
"""
|
|
205
252
|
|
|
206
253
|
involves_me = False
|
|
207
254
|
if kobj.source is None:
|
|
@@ -236,6 +283,8 @@ def basic_network_output_filter(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
236
283
|
|
|
237
284
|
@KnowledgeHandler.create(HandlerType.Final, rid_types=[KoiNetNode])
|
|
238
285
|
def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
286
|
+
"""Removes edges to forgotten nodes."""
|
|
287
|
+
|
|
239
288
|
if kobj.normalized_event_type != EventType.FORGET:
|
|
240
289
|
return
|
|
241
290
|
|
|
@@ -246,4 +295,4 @@ def forget_edge_on_node_deletion(ctx: HandlerContext, kobj: KnowledgeObject):
|
|
|
246
295
|
|
|
247
296
|
if kobj.rid in (edge_profile.source, edge_profile.target):
|
|
248
297
|
logger.debug("Identified edge with forgotten node")
|
|
249
|
-
ctx.
|
|
298
|
+
ctx.kobj_queue.put_kobj(rid=edge_rid, event_type=EventType.FORGET)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
import traceback
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
from rid_lib.ext import Cache
|
|
7
|
+
from rid_lib.types import KoiNetNode
|
|
8
|
+
|
|
9
|
+
from koi_net.config import NodeConfig
|
|
10
|
+
from koi_net.models import END, QueuedEvent
|
|
11
|
+
from koi_net.network.event_queue import EventQueue
|
|
12
|
+
from koi_net.network.request_handler import RequestHandler
|
|
13
|
+
from koi_net.poll_event_buffer import PollEventBuffer
|
|
14
|
+
from koi_net.protocol.event import Event
|
|
15
|
+
from koi_net.protocol.node import NodeProfile, NodeType
|
|
16
|
+
from koi_net.worker import ThreadWorker
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EventProcessingWorker(ThreadWorker):
|
|
22
|
+
event_buffer: dict[KoiNetNode, list[Event]]
|
|
23
|
+
buffer_times: dict[KoiNetNode, float]
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
event_queue: EventQueue,
|
|
28
|
+
request_handler: RequestHandler,
|
|
29
|
+
config: NodeConfig,
|
|
30
|
+
cache: Cache,
|
|
31
|
+
poll_event_buf: PollEventBuffer,
|
|
32
|
+
queue_timeout: float = 0.1,
|
|
33
|
+
max_buf_len: int = 5,
|
|
34
|
+
max_wait_time: float = 1.0
|
|
35
|
+
):
|
|
36
|
+
self.event_queue = event_queue
|
|
37
|
+
self.request_handler = request_handler
|
|
38
|
+
|
|
39
|
+
self.config = config
|
|
40
|
+
self.cache = cache
|
|
41
|
+
self.poll_event_buf = poll_event_buf
|
|
42
|
+
|
|
43
|
+
self.timeout = queue_timeout
|
|
44
|
+
self.max_buf_len = max_buf_len
|
|
45
|
+
self.max_wait_time = max_wait_time
|
|
46
|
+
|
|
47
|
+
self.event_buffer = dict()
|
|
48
|
+
self.buffer_times = dict()
|
|
49
|
+
|
|
50
|
+
super().__init__()
|
|
51
|
+
|
|
52
|
+
def flush_buffer(self, target: KoiNetNode, buffer: list[Event]):
|
|
53
|
+
try:
|
|
54
|
+
self.request_handler.broadcast_events(target, events=buffer)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
traceback.print_exc()
|
|
57
|
+
|
|
58
|
+
self.event_buffer[target] = []
|
|
59
|
+
self.buffer_times[target] = None
|
|
60
|
+
|
|
61
|
+
def decide_event(self, item: QueuedEvent) -> bool:
|
|
62
|
+
node_bundle = self.cache.read(item.target)
|
|
63
|
+
if node_bundle:
|
|
64
|
+
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
65
|
+
|
|
66
|
+
if node_profile.node_type == NodeType.FULL:
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
elif node_profile.node_type == NodeType.PARTIAL:
|
|
70
|
+
self.poll_event_buf.put(item.target, item.event)
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
elif item.target == self.config.koi_net.first_contact.rid:
|
|
74
|
+
return True
|
|
75
|
+
|
|
76
|
+
else:
|
|
77
|
+
logger.warning(f"Couldn't handle event {item.event!r} in queue, node {item.target!r} unknown to me")
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def run(self):
|
|
82
|
+
logger.info("Started event worker")
|
|
83
|
+
while True:
|
|
84
|
+
now = time.time()
|
|
85
|
+
try:
|
|
86
|
+
item = self.event_queue.q.get(timeout=self.timeout)
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
if item is END:
|
|
90
|
+
logger.info("Received 'END' signal, flushing buffer...")
|
|
91
|
+
for target in self.event_buffer.keys():
|
|
92
|
+
self.flush_buffer(target, self.event_buffer[target])
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
logger.info(f"Dequeued {item.event!r} -> {item.target!r}")
|
|
96
|
+
|
|
97
|
+
if not self.decide_event(item):
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
event_buf = self.event_buffer.setdefault(item.target, [])
|
|
101
|
+
if not event_buf:
|
|
102
|
+
self.buffer_times[item.target] = now
|
|
103
|
+
|
|
104
|
+
event_buf.append(item.event)
|
|
105
|
+
|
|
106
|
+
# When new events are dequeued, check buffer for max length
|
|
107
|
+
if len(event_buf) >= self.max_buf_len:
|
|
108
|
+
self.flush_buffer(item.target, event_buf)
|
|
109
|
+
finally:
|
|
110
|
+
self.event_queue.q.task_done()
|
|
111
|
+
|
|
112
|
+
except queue.Empty:
|
|
113
|
+
# On timeout, check all buffers for max wait time
|
|
114
|
+
for target, event_buf in self.event_buffer.items():
|
|
115
|
+
if (len(event_buf) == 0) or (self.buffer_times.get(target) is None):
|
|
116
|
+
continue
|
|
117
|
+
if (now - self.buffer_times[target]) >= self.max_wait_time:
|
|
118
|
+
self.flush_buffer(target, event_buf)
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
traceback.print_exc()
|
koi_net/processor/handler.py
CHANGED
|
@@ -44,9 +44,11 @@ class KnowledgeHandler:
|
|
|
44
44
|
rid_types: list[RIDType] | None = None,
|
|
45
45
|
event_types: list[EventType | None] | None = None
|
|
46
46
|
):
|
|
47
|
-
"""
|
|
47
|
+
"""Decorator wraps a function, returns a KnowledgeHandler.
|
|
48
48
|
|
|
49
|
-
The function symbol will redefined as a `KnowledgeHandler`,
|
|
49
|
+
The function symbol will redefined as a `KnowledgeHandler`,
|
|
50
|
+
which can be passed into the `ProcessorInterface` constructor.
|
|
51
|
+
This is used to register default handlers.
|
|
50
52
|
"""
|
|
51
53
|
def decorator(func: Callable) -> KnowledgeHandler:
|
|
52
54
|
handler = cls(func, handler_type, rid_types, event_types)
|
|
@@ -9,15 +9,21 @@ from ..protocol.event import Event, EventType
|
|
|
9
9
|
class KnowledgeObject(BaseModel):
|
|
10
10
|
"""A normalized knowledge representation for internal processing.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Represents an RID, manifest, bundle, or event. Contains three fields
|
|
13
|
+
(`normalized_event_type`, `source`, `network_targets`) used for
|
|
14
|
+
decision making in the knowledge processing pipeline. The source
|
|
15
|
+
indicates which node this object originated from, or `None` if it
|
|
16
|
+
was generated by this node.
|
|
13
17
|
|
|
14
|
-
The
|
|
18
|
+
The normalized event type indicates how the knowledge object is
|
|
19
|
+
viewed from the perspective of this node, and what cache actions
|
|
20
|
+
will take place. (`NEW`, `UPDATE`) -> cache write, `FORGET` ->
|
|
21
|
+
cache delete, `None` -> no cache action.
|
|
15
22
|
|
|
16
|
-
The
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Constructors are provided to create a knowledge object from an RID, manifest, bundle, or event.
|
|
23
|
+
The network targets indicate other nodes in the network this
|
|
24
|
+
knowledge object will be sent to. The event sent to them will be
|
|
25
|
+
constructed from this knowledge object's RID, manifest, contents,
|
|
26
|
+
and normalized event type.
|
|
21
27
|
"""
|
|
22
28
|
rid: RID
|
|
23
29
|
manifest: Manifest | None = None
|
|
@@ -37,6 +43,7 @@ class KnowledgeObject(BaseModel):
|
|
|
37
43
|
event_type: EventType | None = None,
|
|
38
44
|
source: KoiNetNode | None = None
|
|
39
45
|
) -> "KnowledgeObject":
|
|
46
|
+
"""Creates a `KnowledgeObject` from an `RID`."""
|
|
40
47
|
return cls(
|
|
41
48
|
rid=rid,
|
|
42
49
|
event_type=event_type,
|
|
@@ -50,6 +57,7 @@ class KnowledgeObject(BaseModel):
|
|
|
50
57
|
event_type: EventType | None = None,
|
|
51
58
|
source: KoiNetNode | None = None
|
|
52
59
|
) -> "KnowledgeObject":
|
|
60
|
+
"""Creates a `KnowledgeObject` from a `Manifest`."""
|
|
53
61
|
return cls(
|
|
54
62
|
rid=manifest.rid,
|
|
55
63
|
manifest=manifest,
|
|
@@ -64,6 +72,7 @@ class KnowledgeObject(BaseModel):
|
|
|
64
72
|
event_type: EventType | None = None,
|
|
65
73
|
source: KoiNetNode | None = None
|
|
66
74
|
) -> "KnowledgeObject":
|
|
75
|
+
"""Creates a `KnowledgeObject` from a `Bundle`."""
|
|
67
76
|
return cls(
|
|
68
77
|
rid=bundle.rid,
|
|
69
78
|
manifest=bundle.manifest,
|
|
@@ -78,6 +87,7 @@ class KnowledgeObject(BaseModel):
|
|
|
78
87
|
event: Event,
|
|
79
88
|
source: KoiNetNode | None = None
|
|
80
89
|
) -> "KnowledgeObject":
|
|
90
|
+
"""Creates a `KnowledgeObject` from an `Event`."""
|
|
81
91
|
return cls(
|
|
82
92
|
rid=event.rid,
|
|
83
93
|
manifest=event.manifest,
|
|
@@ -88,6 +98,7 @@ class KnowledgeObject(BaseModel):
|
|
|
88
98
|
|
|
89
99
|
@property
|
|
90
100
|
def bundle(self):
|
|
101
|
+
"""Bundle representation of knowledge object."""
|
|
91
102
|
if self.manifest is None or self.contents is None:
|
|
92
103
|
return
|
|
93
104
|
|
|
@@ -98,6 +109,7 @@ class KnowledgeObject(BaseModel):
|
|
|
98
109
|
|
|
99
110
|
@property
|
|
100
111
|
def normalized_event(self):
|
|
112
|
+
"""Event representation of knowledge object."""
|
|
101
113
|
if self.normalized_event_type is None:
|
|
102
114
|
raise ValueError("Internal event's normalized event type is None, cannot convert to Event")
|
|
103
115
|
|