koi-net 1.2.4__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 -0
- koi_net/behaviors/handshaker.py +68 -0
- koi_net/behaviors/profile_monitor.py +23 -0
- koi_net/behaviors/sync_manager.py +68 -0
- koi_net/build/artifact.py +209 -0
- koi_net/build/assembler.py +60 -0
- koi_net/build/comp_order.py +6 -0
- koi_net/build/comp_type.py +7 -0
- koi_net/build/consts.py +18 -0
- koi_net/build/container.py +46 -0
- koi_net/cache.py +81 -0
- koi_net/config/core.py +113 -0
- koi_net/config/full_node.py +45 -0
- koi_net/config/loader.py +60 -0
- koi_net/config/partial_node.py +26 -0
- koi_net/config/proxy.py +20 -0
- koi_net/core.py +78 -0
- koi_net/effector.py +147 -0
- koi_net/entrypoints/__init__.py +2 -0
- koi_net/entrypoints/base.py +8 -0
- koi_net/entrypoints/poller.py +43 -0
- koi_net/entrypoints/server.py +85 -0
- koi_net/exceptions.py +107 -0
- koi_net/identity.py +20 -0
- koi_net/log_system.py +133 -0
- koi_net/network/__init__.py +0 -0
- koi_net/network/error_handler.py +63 -0
- koi_net/network/event_buffer.py +91 -0
- koi_net/network/event_queue.py +31 -0
- koi_net/network/graph.py +123 -0
- koi_net/network/request_handler.py +244 -0
- koi_net/network/resolver.py +152 -0
- koi_net/network/response_handler.py +130 -0
- koi_net/processor/__init__.py +0 -0
- koi_net/processor/context.py +36 -0
- koi_net/processor/handler.py +61 -0
- koi_net/processor/knowledge_handlers.py +302 -0
- koi_net/processor/knowledge_object.py +135 -0
- koi_net/processor/kobj_queue.py +51 -0
- koi_net/processor/pipeline.py +222 -0
- koi_net/protocol/__init__.py +0 -0
- koi_net/protocol/api_models.py +67 -0
- koi_net/protocol/consts.py +7 -0
- koi_net/protocol/edge.py +50 -0
- koi_net/protocol/envelope.py +65 -0
- koi_net/protocol/errors.py +24 -0
- koi_net/protocol/event.py +51 -0
- koi_net/protocol/model_map.py +62 -0
- koi_net/protocol/node.py +18 -0
- koi_net/protocol/secure.py +167 -0
- koi_net/secure_manager.py +115 -0
- koi_net/workers/__init__.py +2 -0
- koi_net/workers/base.py +26 -0
- koi_net/workers/event_worker.py +111 -0
- koi_net/workers/kobj_worker.py +51 -0
- koi_net-1.2.4.dist-info/METADATA +485 -0
- koi_net-1.2.4.dist-info/RECORD +59 -0
- koi_net-1.2.4.dist-info/WHEEL +4 -0
- koi_net-1.2.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from rid_lib.types import KoiNetNode
|
|
2
|
+
import structlog
|
|
3
|
+
from base64 import b64decode, b64encode
|
|
4
|
+
from cryptography.hazmat.primitives import hashes
|
|
5
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
6
|
+
from cryptography.hazmat.primitives import serialization
|
|
7
|
+
from rid_lib.ext.utils import sha256_hash
|
|
8
|
+
from cryptography.hazmat.primitives.asymmetric.utils import (
|
|
9
|
+
decode_dss_signature,
|
|
10
|
+
encode_dss_signature
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
log = structlog.stdlib.get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def der_to_raw_signature(der_signature: bytes, curve=ec.SECP256R1()) -> bytes:
|
|
17
|
+
"""Converts a DER-encoded signature to raw r||s format."""
|
|
18
|
+
|
|
19
|
+
# Decode the DER signature to get r and s
|
|
20
|
+
r, s = decode_dss_signature(der_signature)
|
|
21
|
+
|
|
22
|
+
# Determine byte length based on curve bit size
|
|
23
|
+
byte_length = (curve.key_size + 7) // 8
|
|
24
|
+
|
|
25
|
+
# Convert r and s to big-endian byte arrays of fixed length
|
|
26
|
+
r_bytes = r.to_bytes(byte_length, byteorder='big')
|
|
27
|
+
s_bytes = s.to_bytes(byte_length, byteorder='big')
|
|
28
|
+
|
|
29
|
+
# Concatenate r and s
|
|
30
|
+
return r_bytes + s_bytes
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def raw_to_der_signature(raw_signature: bytes, curve=ec.SECP256R1()) -> bytes:
|
|
34
|
+
"""Converts a raw r||s signature to DER format."""
|
|
35
|
+
|
|
36
|
+
# Determine byte length based on curve bit size
|
|
37
|
+
byte_length = (curve.key_size + 7) // 8
|
|
38
|
+
|
|
39
|
+
# Split the raw signature into r and s components
|
|
40
|
+
if len(raw_signature) != 2 * byte_length:
|
|
41
|
+
raise ValueError(f"Raw signature must be {2 * byte_length} bytes for {curve.name}")
|
|
42
|
+
|
|
43
|
+
r_bytes = raw_signature[:byte_length]
|
|
44
|
+
s_bytes = raw_signature[byte_length:]
|
|
45
|
+
|
|
46
|
+
# Convert bytes to integers
|
|
47
|
+
r = int.from_bytes(r_bytes, byteorder='big')
|
|
48
|
+
s = int.from_bytes(s_bytes, byteorder='big')
|
|
49
|
+
|
|
50
|
+
# Encode as DER
|
|
51
|
+
return encode_dss_signature(r, s)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PrivateKey:
|
|
55
|
+
priv_key: ec.EllipticCurvePrivateKey
|
|
56
|
+
|
|
57
|
+
def __init__(self, priv_key):
|
|
58
|
+
self.priv_key = priv_key
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def generate(cls):
|
|
62
|
+
"""Generates a new `Private Key`."""
|
|
63
|
+
return cls(priv_key=ec.generate_private_key(ec.SECP256R1()))
|
|
64
|
+
|
|
65
|
+
def public_key(self) -> "PublicKey":
|
|
66
|
+
"""Returns instance of `PublicKey` dervied from this private key."""
|
|
67
|
+
return PublicKey(self.priv_key.public_key())
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_pem(cls, priv_key_pem: str, password: str):
|
|
71
|
+
"""Loads `PrivateKey` from encrypted PEM string."""
|
|
72
|
+
return cls(
|
|
73
|
+
priv_key=serialization.load_pem_private_key(
|
|
74
|
+
data=priv_key_pem.encode(),
|
|
75
|
+
password=password.encode()
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def to_pem(self, password: str) -> str:
|
|
80
|
+
"""Saves `PrivateKey` to encrypted PEM string."""
|
|
81
|
+
return self.priv_key.private_bytes(
|
|
82
|
+
encoding=serialization.Encoding.PEM,
|
|
83
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
84
|
+
encryption_algorithm=serialization.BestAvailableEncryption(password.encode())
|
|
85
|
+
).decode()
|
|
86
|
+
|
|
87
|
+
def sign(self, message: bytes) -> str:
|
|
88
|
+
"""Returns base64 encoded raw signature bytes of the form r||s."""
|
|
89
|
+
hashed_message = sha256_hash(message.decode())
|
|
90
|
+
|
|
91
|
+
der_signature_bytes = self.priv_key.sign(
|
|
92
|
+
data=message,
|
|
93
|
+
signature_algorithm=ec.ECDSA(hashes.SHA256())
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
raw_signature_bytes = der_to_raw_signature(der_signature_bytes)
|
|
97
|
+
|
|
98
|
+
signature = b64encode(raw_signature_bytes).decode()
|
|
99
|
+
|
|
100
|
+
log.debug(f"Signing message with [{self.public_key().to_der()}]")
|
|
101
|
+
log.debug(f"hash: {hashed_message}")
|
|
102
|
+
log.debug(f"signature: {signature}")
|
|
103
|
+
|
|
104
|
+
return signature
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class PublicKey:
|
|
108
|
+
pub_key: ec.EllipticCurvePublicKey
|
|
109
|
+
|
|
110
|
+
def __init__(self, pub_key):
|
|
111
|
+
self.pub_key = pub_key
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_pem(cls, pub_key_pem: str):
|
|
115
|
+
"""Loads `PublicKey` from PEM string."""
|
|
116
|
+
return cls(
|
|
117
|
+
pub_key=serialization.load_pem_public_key(
|
|
118
|
+
data=pub_key_pem.encode()
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def to_pem(self) -> str:
|
|
123
|
+
"""Saves `PublicKey` to PEM string."""
|
|
124
|
+
return self.pub_key.public_bytes(
|
|
125
|
+
encoding=serialization.Encoding.PEM,
|
|
126
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
127
|
+
).decode()
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def from_der(cls, pub_key_der: str):
|
|
131
|
+
"""Loads `PublicKey` from base64 encoded DER string."""
|
|
132
|
+
return cls(
|
|
133
|
+
pub_key=serialization.load_der_public_key(
|
|
134
|
+
data=b64decode(pub_key_der)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def to_der(self) -> str:
|
|
139
|
+
"""Saves `PublicKey` to base64 encoded DER string."""
|
|
140
|
+
return b64encode(
|
|
141
|
+
self.pub_key.public_bytes(
|
|
142
|
+
encoding=serialization.Encoding.DER,
|
|
143
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
144
|
+
)
|
|
145
|
+
).decode()
|
|
146
|
+
|
|
147
|
+
def to_node_rid(self, name) -> KoiNetNode:
|
|
148
|
+
"""Returns an orn:koi-net.node RID from hashed DER string."""
|
|
149
|
+
return KoiNetNode(
|
|
150
|
+
name=name,
|
|
151
|
+
hash=sha256_hash(self.to_der())
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def verify(self, signature: str, message: bytes):
|
|
155
|
+
"""Verifies a signature for a message.
|
|
156
|
+
|
|
157
|
+
Raises `cryptography.exceptions.InvalidSignature` on failure.
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
raw_signature_bytes = b64decode(signature)
|
|
161
|
+
der_signature_bytes = raw_to_der_signature(raw_signature_bytes)
|
|
162
|
+
|
|
163
|
+
self.pub_key.verify(
|
|
164
|
+
signature=der_signature_bytes,
|
|
165
|
+
data=message,
|
|
166
|
+
signature_algorithm=ec.ECDSA(hashes.SHA256())
|
|
167
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import structlog
|
|
2
|
+
import cryptography.exceptions
|
|
3
|
+
from rid_lib.ext import Bundle, Cache
|
|
4
|
+
from rid_lib.ext.utils import sha256_hash
|
|
5
|
+
from rid_lib.types import KoiNetNode
|
|
6
|
+
from .identity import NodeIdentity
|
|
7
|
+
from .protocol.envelope import UnsignedEnvelope, SignedEnvelope
|
|
8
|
+
from .protocol.secure import PublicKey
|
|
9
|
+
from .protocol.api_models import ApiModels, EventsPayload
|
|
10
|
+
from .protocol.event import EventType
|
|
11
|
+
from .protocol.node import NodeProfile
|
|
12
|
+
from .protocol.secure import PrivateKey
|
|
13
|
+
from .exceptions import (
|
|
14
|
+
UnknownNodeError,
|
|
15
|
+
InvalidKeyError,
|
|
16
|
+
InvalidSignatureError,
|
|
17
|
+
InvalidTargetError
|
|
18
|
+
)
|
|
19
|
+
from .config.core import NodeConfig
|
|
20
|
+
|
|
21
|
+
log = structlog.stdlib.get_logger()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SecureManager:
|
|
25
|
+
"""Subsystem handling secure protocol logic."""
|
|
26
|
+
identity: NodeIdentity
|
|
27
|
+
cache: Cache
|
|
28
|
+
config: NodeConfig
|
|
29
|
+
priv_key: PrivateKey
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
identity: NodeIdentity,
|
|
34
|
+
cache: Cache,
|
|
35
|
+
config: NodeConfig
|
|
36
|
+
):
|
|
37
|
+
self.identity = identity
|
|
38
|
+
self.cache = cache
|
|
39
|
+
self.config = config
|
|
40
|
+
|
|
41
|
+
def start(self):
|
|
42
|
+
self.load_priv_key()
|
|
43
|
+
|
|
44
|
+
def load_priv_key(self) -> PrivateKey:
|
|
45
|
+
"""Loads private key from PEM file path in config."""
|
|
46
|
+
|
|
47
|
+
# TODO: handle missing private key
|
|
48
|
+
with open(self.config.koi_net.private_key_pem_path, "r") as f:
|
|
49
|
+
priv_key_pem = f.read()
|
|
50
|
+
|
|
51
|
+
self.priv_key = PrivateKey.from_pem(
|
|
52
|
+
priv_key_pem=priv_key_pem,
|
|
53
|
+
password=self.config.env.priv_key_password
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def handle_unknown_node(self, envelope: SignedEnvelope) -> Bundle | None:
|
|
57
|
+
"""Attempts to find node profile in proided envelope.
|
|
58
|
+
|
|
59
|
+
If an unknown node sends an envelope, it may still be able to be
|
|
60
|
+
validated if that envelope contains their node profile. This is
|
|
61
|
+
essential for allowing unknown nodes to handshake and introduce
|
|
62
|
+
themselves. Only an `EventsPayload` contain a `NEW` event for a
|
|
63
|
+
node profile for the source node is permissible.
|
|
64
|
+
"""
|
|
65
|
+
if type(envelope.payload) != EventsPayload:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
for event in envelope.payload.events:
|
|
69
|
+
# must be NEW event for bundle of source node's profile
|
|
70
|
+
if event.rid != envelope.source_node:
|
|
71
|
+
continue
|
|
72
|
+
if event.event_type != EventType.NEW:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
return event.bundle
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def create_envelope(
|
|
79
|
+
self, payload: ApiModels, target: KoiNetNode
|
|
80
|
+
) -> SignedEnvelope:
|
|
81
|
+
"""Returns signed envelope to target from provided payload."""
|
|
82
|
+
return UnsignedEnvelope(
|
|
83
|
+
payload=payload,
|
|
84
|
+
source_node=self.identity.rid,
|
|
85
|
+
target_node=target
|
|
86
|
+
).sign_with(self.priv_key)
|
|
87
|
+
|
|
88
|
+
def validate_envelope(self, envelope: SignedEnvelope):
|
|
89
|
+
"""Validates signed envelope from another node."""
|
|
90
|
+
|
|
91
|
+
node_bundle = (
|
|
92
|
+
self.cache.read(envelope.source_node) or
|
|
93
|
+
self.handle_unknown_node(envelope)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if not node_bundle:
|
|
97
|
+
raise UnknownNodeError(f"Couldn't resolve {envelope.source_node}")
|
|
98
|
+
|
|
99
|
+
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
100
|
+
|
|
101
|
+
# check that public key matches source node RID
|
|
102
|
+
if envelope.source_node.hash != sha256_hash(node_profile.public_key):
|
|
103
|
+
raise InvalidKeyError("Invalid public key on new node!")
|
|
104
|
+
|
|
105
|
+
# check envelope signed by validated public key
|
|
106
|
+
pub_key = PublicKey.from_der(node_profile.public_key)
|
|
107
|
+
try:
|
|
108
|
+
envelope.verify_with(pub_key)
|
|
109
|
+
except cryptography.exceptions.InvalidSignature:
|
|
110
|
+
raise InvalidSignatureError(f"Signature {envelope.signature} is invalid.")
|
|
111
|
+
|
|
112
|
+
# check that this node is the target of the envelope
|
|
113
|
+
if envelope.target_node != self.identity.rid:
|
|
114
|
+
raise InvalidTargetError(f"Envelope target {envelope.target_node!r} is not me")
|
|
115
|
+
|
koi_net/workers/base.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
from ..build import comp_order
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class End:
|
|
7
|
+
"""Class for STOP_WORKER sentinel pushed to worker queues."""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
STOP_WORKER = End()
|
|
11
|
+
|
|
12
|
+
@comp_order.worker
|
|
13
|
+
class ThreadWorker:
|
|
14
|
+
"""Base class for thread workers."""
|
|
15
|
+
|
|
16
|
+
thread: threading.Thread
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.thread = threading.Thread(target=self.run)
|
|
20
|
+
|
|
21
|
+
def start(self):
|
|
22
|
+
self.thread.start()
|
|
23
|
+
|
|
24
|
+
def run(self):
|
|
25
|
+
"""Processing loop for thread."""
|
|
26
|
+
pass
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
import traceback
|
|
3
|
+
import time
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
from rid_lib.ext import Cache
|
|
7
|
+
from rid_lib.types import KoiNetNode
|
|
8
|
+
|
|
9
|
+
from ..config.core import NodeConfig
|
|
10
|
+
from ..network.event_queue import EventQueue
|
|
11
|
+
from ..network.request_handler import RequestHandler
|
|
12
|
+
from ..network.event_buffer import EventBuffer
|
|
13
|
+
from ..protocol.node import NodeProfile, NodeType
|
|
14
|
+
from ..exceptions import RequestError
|
|
15
|
+
from .base import ThreadWorker, STOP_WORKER
|
|
16
|
+
|
|
17
|
+
log = structlog.stdlib.get_logger()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EventProcessingWorker(ThreadWorker):
|
|
21
|
+
"""Thread worker that processes the `event_queue`."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
config: NodeConfig,
|
|
26
|
+
cache: Cache,
|
|
27
|
+
event_queue: EventQueue,
|
|
28
|
+
request_handler: RequestHandler,
|
|
29
|
+
poll_event_buf: EventBuffer,
|
|
30
|
+
broadcast_event_buf: EventBuffer
|
|
31
|
+
):
|
|
32
|
+
self.event_queue = event_queue
|
|
33
|
+
self.request_handler = request_handler
|
|
34
|
+
|
|
35
|
+
self.config = config
|
|
36
|
+
self.cache = cache
|
|
37
|
+
self.poll_event_buf = poll_event_buf
|
|
38
|
+
self.broadcast_event_buf = broadcast_event_buf
|
|
39
|
+
|
|
40
|
+
super().__init__()
|
|
41
|
+
|
|
42
|
+
def flush_and_broadcast(self, target: KoiNetNode, force_flush: bool = False):
|
|
43
|
+
"""Broadcasts all events to target in event buffer."""
|
|
44
|
+
|
|
45
|
+
# TODO: deal with automated retries when unreachable node's buffer is full
|
|
46
|
+
try:
|
|
47
|
+
with self.broadcast_event_buf.safe_flush(target, force_flush) as events:
|
|
48
|
+
self.request_handler.broadcast_events(target, events=events)
|
|
49
|
+
except RequestError:
|
|
50
|
+
log.warning("Failed to reach target, event buffer reset")
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def stop(self):
|
|
54
|
+
self.event_queue.q.put(STOP_WORKER)
|
|
55
|
+
|
|
56
|
+
def run(self):
|
|
57
|
+
while True:
|
|
58
|
+
try:
|
|
59
|
+
item = self.event_queue.q.get(
|
|
60
|
+
timeout=self.config.koi_net.event_worker.queue_timeout)
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if item is STOP_WORKER:
|
|
64
|
+
log.info(f"Received 'STOP_WORKER' signal, flushing all buffers...")
|
|
65
|
+
for target in list(self.broadcast_event_buf.buffers.keys()):
|
|
66
|
+
self.flush_and_broadcast(target, force_flush=True)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
log.info(f"Dequeued {item.event!r} -> {item.target!r}")
|
|
70
|
+
|
|
71
|
+
# determines which buffer to push event to based on target node type
|
|
72
|
+
node_bundle = self.cache.read(item.target)
|
|
73
|
+
if node_bundle:
|
|
74
|
+
node_profile = node_bundle.validate_contents(NodeProfile)
|
|
75
|
+
|
|
76
|
+
if node_profile.node_type == NodeType.FULL:
|
|
77
|
+
self.broadcast_event_buf.push(item.target, item.event)
|
|
78
|
+
|
|
79
|
+
elif node_profile.node_type == NodeType.PARTIAL:
|
|
80
|
+
self.poll_event_buf.push(item.target, item.event)
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
elif item.target == self.config.koi_net.first_contact.rid:
|
|
84
|
+
self.broadcast_event_buf.push(item.target, item.event)
|
|
85
|
+
|
|
86
|
+
else:
|
|
87
|
+
log.warning(f"Couldn't handle event {item.event!r} in queue, node {item.target!r} unknown to me")
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
buf_len = self.broadcast_event_buf.buf_len(item.target)
|
|
91
|
+
if buf_len > self.config.koi_net.event_worker.max_buf_len:
|
|
92
|
+
self.flush_and_broadcast(target)
|
|
93
|
+
|
|
94
|
+
finally:
|
|
95
|
+
self.event_queue.q.task_done()
|
|
96
|
+
|
|
97
|
+
except queue.Empty:
|
|
98
|
+
# On timeout, check all buffers for max wait time
|
|
99
|
+
for target in list(self.broadcast_event_buf.buffers):
|
|
100
|
+
start_time = self.broadcast_event_buf.start_time.get(target)
|
|
101
|
+
|
|
102
|
+
if (start_time is None) or (self.broadcast_event_buf.buf_len(target) == 0):
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
now = time.time()
|
|
106
|
+
if (now - start_time) >= self.config.koi_net.event_worker.max_wait_time:
|
|
107
|
+
self.flush_and_broadcast(target)
|
|
108
|
+
|
|
109
|
+
except Exception:
|
|
110
|
+
traceback.print_exc()
|
|
111
|
+
continue
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
import traceback
|
|
3
|
+
import structlog
|
|
4
|
+
|
|
5
|
+
from ..config.core import NodeConfig
|
|
6
|
+
from ..processor.pipeline import KnowledgePipeline
|
|
7
|
+
from ..processor.kobj_queue import KobjQueue
|
|
8
|
+
from .base import ThreadWorker, STOP_WORKER
|
|
9
|
+
|
|
10
|
+
log = structlog.stdlib.get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class KnowledgeProcessingWorker(ThreadWorker):
|
|
14
|
+
"""Thread worker that processes the `kobj_queue`."""
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
config: NodeConfig,
|
|
19
|
+
kobj_queue: KobjQueue,
|
|
20
|
+
pipeline: KnowledgePipeline
|
|
21
|
+
):
|
|
22
|
+
self.config = config
|
|
23
|
+
self.kobj_queue = kobj_queue
|
|
24
|
+
self.pipeline = pipeline
|
|
25
|
+
|
|
26
|
+
super().__init__()
|
|
27
|
+
|
|
28
|
+
def stop(self):
|
|
29
|
+
self.kobj_queue.q.put(STOP_WORKER)
|
|
30
|
+
|
|
31
|
+
def run(self):
|
|
32
|
+
while True:
|
|
33
|
+
try:
|
|
34
|
+
item = self.kobj_queue.q.get(timeout=self.config.koi_net.kobj_worker.queue_timeout)
|
|
35
|
+
try:
|
|
36
|
+
if item is STOP_WORKER:
|
|
37
|
+
log.info("Received 'STOP_WORKER' signal, shutting down...")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
log.info(f"Dequeued {item!r}")
|
|
41
|
+
|
|
42
|
+
self.pipeline.process(item)
|
|
43
|
+
finally:
|
|
44
|
+
self.kobj_queue.q.task_done()
|
|
45
|
+
|
|
46
|
+
except queue.Empty:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
except Exception:
|
|
50
|
+
traceback.print_exc()
|
|
51
|
+
continue
|