Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
OpenDAS
dynamo
Commits
9210a26d
Unverified
Commit
9210a26d
authored
May 30, 2025
by
jthomson04
Committed by
GitHub
May 30, 2025
Browse files
refactor: Refactor kv event publishers (#1287)
parent
39dcdf1f
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
203 additions
and
186 deletions
+203
-186
lib/bindings/c/src/lib.rs
lib/bindings/c/src/lib.rs
+1
-1
lib/bindings/python/rust/llm/kv.rs
lib/bindings/python/rust/llm/kv.rs
+12
-8
lib/llm/src/kv_router/publisher.rs
lib/llm/src/kv_router/publisher.rs
+190
-177
No files found.
lib/bindings/c/src/lib.rs
View file @
9210a26d
...
@@ -148,7 +148,7 @@ fn dynamo_create_kv_publisher(
...
@@ -148,7 +148,7 @@ fn dynamo_create_kv_publisher(
{
{
Ok
(
drt
)
=>
{
Ok
(
drt
)
=>
{
let
backend
=
drt
.namespace
(
namespace
)
?
.component
(
component
)
?
;
let
backend
=
drt
.namespace
(
namespace
)
?
.component
(
component
)
?
;
KvEventPublisher
::
new
(
backend
,
worker_id
,
kv_block_size
)
KvEventPublisher
::
new
(
backend
,
worker_id
,
kv_block_size
,
None
)
}
}
Err
(
e
)
=>
Err
(
e
),
Err
(
e
)
=>
Err
(
e
),
}
}
...
...
lib/bindings/python/rust/llm/kv.rs
View file @
9210a26d
...
@@ -22,7 +22,7 @@ use rs::traits::events::EventSubscriber;
...
@@ -22,7 +22,7 @@ use rs::traits::events::EventSubscriber;
use
tracing
;
use
tracing
;
use
llm_rs
::
kv_router
::
protocols
::
*
;
use
llm_rs
::
kv_router
::
protocols
::
*
;
use
llm_rs
::
kv_router
::
publisher
::
create_stored_blocks
;
use
llm_rs
::
kv_router
::
publisher
::
{
create_stored_blocks
,
KvEventSourceConfig
}
;
#[pyclass]
#[pyclass]
pub
(
crate
)
struct
KvRouter
{
pub
(
crate
)
struct
KvRouter
{
...
@@ -165,21 +165,23 @@ impl ZmqKvEventPublisherConfig {
...
@@ -165,21 +165,23 @@ impl ZmqKvEventPublisherConfig {
#[pyclass]
#[pyclass]
pub
(
crate
)
struct
ZmqKvEventPublisher
{
pub
(
crate
)
struct
ZmqKvEventPublisher
{
inner
:
llm_rs
::
kv_router
::
publisher
::
Zmq
KvEventPublisher
,
inner
:
llm_rs
::
kv_router
::
publisher
::
KvEventPublisher
,
}
}
#[pymethods]
#[pymethods]
impl
ZmqKvEventPublisher
{
impl
ZmqKvEventPublisher
{
#[new]
#[new]
fn
new
(
component
:
Component
,
config
:
ZmqKvEventPublisherConfig
)
->
PyResult
<
Self
>
{
fn
new
(
component
:
Component
,
config
:
ZmqKvEventPublisherConfig
)
->
PyResult
<
Self
>
{
let
mut
inner
=
let
inner
=
llm_rs
::
kv_router
::
publisher
::
KvEventPublisher
::
new
(
llm_rs
::
kv_router
::
publisher
::
ZmqKvEventPublisher
::
new
(
config
.kv_block_size
);
inner
.start_background_task
(
component
.inner
,
component
.inner
,
config
.worker_id
,
config
.worker_id
,
config
.zmq_endpoint
,
config
.kv_block_size
,
config
.zmq_topic
,
Some
(
KvEventSourceConfig
::
Zmq
{
);
endpoint
:
config
.zmq_endpoint
,
topic
:
config
.zmq_topic
,
}),
)
.map_err
(
to_pyerr
)
?
;
Ok
(
Self
{
inner
})
Ok
(
Self
{
inner
})
}
}
...
@@ -203,8 +205,10 @@ impl KvEventPublisher {
...
@@ -203,8 +205,10 @@ impl KvEventPublisher {
component
.inner
,
component
.inner
,
worker_id
,
worker_id
,
kv_block_size
,
kv_block_size
,
None
,
)
)
.map_err
(
to_pyerr
)
?
;
.map_err
(
to_pyerr
)
?
;
Ok
(
Self
{
Ok
(
Self
{
inner
:
inner
.into
(),
inner
:
inner
.into
(),
kv_block_size
,
kv_block_size
,
...
...
lib/llm/src/kv_router/publisher.rs
View file @
9210a26d
...
@@ -19,7 +19,7 @@ use crate::kv_router::{
...
@@ -19,7 +19,7 @@ use crate::kv_router::{
KV_EVENT_SUBJECT
,
KV_METRICS_ENDPOINT
,
KV_EVENT_SUBJECT
,
KV_METRICS_ENDPOINT
,
};
};
use
async_trait
::
async_trait
;
use
async_trait
::
async_trait
;
use
dynamo_runtime
::
traits
::{
events
::
EventPublisher
,
DistributedRuntimeProvider
,
RuntimeProvider
};
use
dynamo_runtime
::
traits
::{
events
::
EventPublisher
,
DistributedRuntimeProvider
};
use
dynamo_runtime
::{
use
dynamo_runtime
::{
component
::
Component
,
component
::
Component
,
pipeline
::{
pipeline
::{
...
@@ -32,6 +32,7 @@ use dynamo_runtime::{
...
@@ -32,6 +32,7 @@ use dynamo_runtime::{
use
futures
::
stream
;
use
futures
::
stream
;
use
std
::
sync
::
Arc
;
use
std
::
sync
::
Arc
;
use
tokio
::
sync
::
mpsc
;
use
tokio
::
sync
::
mpsc
;
use
tokio_util
::
sync
::
CancellationToken
;
use
rmp_serde
as
rmps
;
use
rmp_serde
as
rmps
;
use
serde
::
Deserialize
;
use
serde
::
Deserialize
;
...
@@ -44,18 +45,109 @@ use zeromq::{Socket, SocketRecv, SubSocket};
...
@@ -44,18 +45,109 @@ use zeromq::{Socket, SocketRecv, SubSocket};
// KV Event Publishers -----------------------------------------------------
// KV Event Publishers -----------------------------------------------------
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
/// Configure the source of KV events.
/// Currently, only ZMQ is supported.
pub
enum
KvEventSourceConfig
{
Zmq
{
endpoint
:
String
,
topic
:
String
},
}
/// The source of KV events.
enum
KvEventSource
{
Zmq
{
zmq_handle
:
tokio
::
task
::
JoinHandle
<
()
>
,
},
}
impl
KvEventSource
{
/// Start the event source from a [`KvEventSourceConfig`].
fn
start
(
component
:
Component
,
kv_block_size
:
usize
,
source_config
:
KvEventSourceConfig
,
cancellation_token
:
CancellationToken
,
tx
:
mpsc
::
UnboundedSender
<
KvCacheEvent
>
,
)
->
Result
<
Self
>
{
match
source_config
{
KvEventSourceConfig
::
Zmq
{
endpoint
,
topic
}
=>
{
let
zmq_handle
=
component
.drt
()
.runtime
()
.secondary
()
.spawn
(
start_zmq_listener
(
endpoint
,
topic
,
tx
,
cancellation_token
.clone
(),
kv_block_size
,
));
Ok
(
KvEventSource
::
Zmq
{
zmq_handle
})
}
}
}
fn
shutdown
(
&
self
)
{
match
self
{
KvEventSource
::
Zmq
{
zmq_handle
}
=>
{
zmq_handle
.abort
();
}
}
}
}
/// A publisher of KV events.
pub
struct
KvEventPublisher
{
pub
struct
KvEventPublisher
{
/// The size of the KV block.
kv_block_size
:
usize
,
kv_block_size
:
usize
,
/// The source of KV events.
/// Can be `None` if all events provided through [`KvEventPublisher::publish`].
source
:
Option
<
KvEventSource
>
,
/// The cancellation token.
cancellation_token
:
CancellationToken
,
/// The channel to send events to.
tx
:
mpsc
::
UnboundedSender
<
KvCacheEvent
>
,
tx
:
mpsc
::
UnboundedSender
<
KvCacheEvent
>
,
}
}
impl
KvEventPublisher
{
impl
KvEventPublisher
{
pub
fn
new
(
component
:
Component
,
worker_id
:
i64
,
kv_block_size
:
usize
)
->
Result
<
Self
>
{
pub
fn
new
(
component
:
Component
,
worker_id
:
i64
,
kv_block_size
:
usize
,
source_config
:
Option
<
KvEventSourceConfig
>
,
)
->
Result
<
Self
>
{
let
cancellation_token
=
CancellationToken
::
new
();
let
(
tx
,
rx
)
=
mpsc
::
unbounded_channel
::
<
KvCacheEvent
>
();
let
(
tx
,
rx
)
=
mpsc
::
unbounded_channel
::
<
KvCacheEvent
>
();
let
p
=
KvEventPublisher
{
tx
,
kv_block_size
};
start_publish_task
(
component
,
worker_id
,
rx
);
// Create our event source (if any)
Ok
(
p
)
let
mut
source
=
None
;
if
let
Some
(
config
)
=
source_config
{
source
=
Some
(
KvEventSource
::
start
(
component
.clone
(),
kv_block_size
,
config
,
cancellation_token
.clone
(),
tx
.clone
(),
)
?
);
}
component
.drt
()
.runtime
()
.secondary
()
.spawn
(
start_event_processor
(
component
,
worker_id
,
cancellation_token
.clone
(),
rx
,
));
Ok
(
Self
{
kv_block_size
,
source
,
cancellation_token
,
tx
,
})
}
}
pub
fn
publish
(
&
self
,
event
:
KvCacheEvent
)
->
Result
<
(),
mpsc
::
error
::
SendError
<
KvCacheEvent
>>
{
pub
fn
publish
(
&
self
,
event
:
KvCacheEvent
)
->
Result
<
(),
mpsc
::
error
::
SendError
<
KvCacheEvent
>>
{
...
@@ -66,151 +158,50 @@ impl KvEventPublisher {
...
@@ -66,151 +158,50 @@ impl KvEventPublisher {
pub
fn
kv_block_size
(
&
self
)
->
usize
{
pub
fn
kv_block_size
(
&
self
)
->
usize
{
self
.kv_block_size
self
.kv_block_size
}
}
}
fn
start_publish_task
(
pub
fn
shutdown
(
&
mut
self
)
{
component
:
Component
,
if
!
self
.cancellation_token
.is_cancelled
()
{
worker_id
:
i64
,
self
.cancellation_token
.cancel
();
mut
rx
:
mpsc
::
UnboundedReceiver
<
KvCacheEvent
>
,
)
{
let
component_clone
=
component
.clone
();
tracing
::
info!
(
"Publishing KV Events to subject: {}"
,
KV_EVENT_SUBJECT
);
_
=
component
.drt
()
.runtime
()
.secondary
()
.spawn
(
async
move
{
while
let
Some
(
event
)
=
rx
.recv
()
.await
{
let
router_event
=
RouterEvent
::
new
(
worker_id
,
event
);
component_clone
.publish
(
KV_EVENT_SUBJECT
,
&
router_event
)
.await
.unwrap
();
}
}
});
}
// vLLM and SGLang use multi-processing to launch engine-core processes
// We use zmq to publish events from these processes to a socket
// For more info on zmq: https://zeromq.org/
// This publisher reads those events and publishes them to NATS
// The indexer will get the events from NATS and put them in the global prefix tree.
pub
struct
ZmqKvEventPublisher
{
kv_block_size
:
usize
,
processor_handle
:
Option
<
tokio
::
task
::
JoinHandle
<
()
>>
,
zmq_handle
:
Option
<
tokio
::
task
::
JoinHandle
<
()
>>
,
zmq_token
:
Option
<
dynamo_runtime
::
CancellationToken
>
,
warning_count
:
Arc
<
AtomicU32
>
,
}
impl
ZmqKvEventPublisher
{
if
let
Some
(
source
)
=
self
.source
.take
()
{
pub
fn
new
(
kv_block_size
:
usize
)
->
Self
{
source
.shutdown
();
Self
{
kv_block_size
,
processor_handle
:
None
,
zmq_handle
:
None
,
zmq_token
:
None
,
warning_count
:
Arc
::
new
(
AtomicU32
::
new
(
0
)),
}
}
}
}
}
pub
fn
start_background_task
(
impl
Drop
for
KvEventPublisher
{
&
mut
self
,
fn
drop
(
&
mut
self
)
{
component
:
Component
,
self
.shutdown
();
worker_id
:
i64
,
zmq_endpoint
:
String
,
zmq_topic
:
String
,
)
{
let
kv_block_size
=
self
.kv_block_size
;
let
warning_count
=
self
.warning_count
.clone
();
let
(
raw_tx
,
raw_rx
)
=
mpsc
::
unbounded_channel
::
<
(
u64
,
Vec
<
u8
>
)
>
();
let
zmq_token
=
component
.rt
()
.child_token
();
self
.zmq_token
=
Some
(
zmq_token
.clone
());
// Spawn async ZMQ listener
self
.zmq_handle
=
Some
(
component
.drt
()
.runtime
()
.secondary
()
.spawn
(
start_zmq_listener
(
zmq_endpoint
,
zmq_topic
,
raw_tx
,
zmq_token
.clone
(),
)),
);
self
.processor_handle
=
Some
(
component
.drt
()
.runtime
()
.secondary
()
.spawn
(
start_event_processor
(
raw_rx
,
component
,
worker_id
,
kv_block_size
,
warning_count
,
zmq_token
,
),
));
}
pub
fn
shutdown
(
&
mut
self
)
{
if
let
Some
(
token
)
=
self
.zmq_token
.take
()
{
token
.cancel
();
}
if
let
Some
(
handle
)
=
self
.zmq_handle
.take
()
{
handle
.abort
();
}
if
let
Some
(
handle
)
=
self
.processor_handle
.take
()
{
handle
.abort
();
}
}
}
}
}
async
fn
start_event_processor
<
P
:
EventPublisher
>
(
async
fn
start_event_processor
<
P
:
EventPublisher
+
Send
+
Sync
+
'static
>
(
mut
raw_rx
:
mpsc
::
UnboundedReceiver
<
(
u64
,
Vec
<
u8
>
)
>
,
publisher
:
P
,
component
:
P
,
worker_id
:
i64
,
worker_id
:
i64
,
kv_block_size
:
usize
,
cancellation_token
:
CancellationToken
,
warning_count
:
Arc
<
AtomicU32
>
,
mut
rx
:
mpsc
::
UnboundedReceiver
<
KvCacheEvent
>
,
cancellation_token
:
dynamo_runtime
::
CancellationToken
,
)
{
)
{
loop
{
loop
{
tokio
::
select!
{
tokio
::
select!
{
biased
;
// Check for cancellation
_
=
cancellation_token
.cancelled
()
=>
{
_
=
cancellation_token
.cancelled
()
=>
{
tracing
::
debug!
(
"Event processor
received cancellation signal"
);
tracing
::
info!
(
"KV Event source
received cancellation signal"
);
break
;
break
;
}
}
event
=
rx
.recv
()
=>
{
// Process incoming messages
let
Some
(
event
)
=
event
else
{
msg
=
raw_rx
.recv
()
=>
{
tracing
::
debug!
(
"Event processor channel closed."
);
let
Some
((
seq
,
payload
))
=
msg
else
{
tracing
::
debug!
(
"Event processor channel closed"
);
break
;
break
;
};
};
let
batch_result
=
rmps
::
from_slice
::
<
KvEventBatch
>
(
&
payload
);
// Encapsulate in a router event and publish.
let
Ok
(
batch
)
=
batch_result
else
{
let
router_event
=
RouterEvent
::
new
(
worker_id
,
event
);
let
e
=
batch_result
.unwrap_err
();
if
let
Err
(
e
)
=
publisher
.publish
(
KV_EVENT_SUBJECT
,
&
router_event
)
.await
{
tracing
::
warn!
(
error
=%
e
,
"Failed to decode KVEventBatch msgpack"
);
tracing
::
error!
(
"Failed to publish event: {}"
,
e
);
continue
;
};
for
raw_evt
in
batch
.events
.into_iter
()
{
let
Some
(
event
)
=
convert_event
(
raw_evt
,
seq
,
kv_block_size
,
&
warning_count
)
else
{
// Case where convert_event returns None
continue
;
};
let
router_event
=
RouterEvent
::
new
(
worker_id
,
event
);
if
let
Err
(
e
)
=
component
.publish
(
KV_EVENT_SUBJECT
,
&
router_event
)
.await
{
tracing
::
warn!
(
error
=%
e
,
"Failed to publish router event."
);
}
}
}
}
}
}
}
}
}
tracing
::
debug!
(
"Event processor exiting"
);
}
}
// Error handling configuration for ZMQ operations
// Error handling configuration for ZMQ operations
...
@@ -230,8 +221,9 @@ fn calculate_backoff_ms(consecutive_errors: u32) -> u64 {
...
@@ -230,8 +221,9 @@ fn calculate_backoff_ms(consecutive_errors: u32) -> u64 {
async
fn
start_zmq_listener
(
async
fn
start_zmq_listener
(
zmq_endpoint
:
String
,
zmq_endpoint
:
String
,
zmq_topic
:
String
,
zmq_topic
:
String
,
raw_tx
:
mpsc
::
UnboundedSender
<
(
u64
,
Vec
<
u8
>
)
>
,
tx
:
mpsc
::
UnboundedSender
<
KvCacheEvent
>
,
zmq_token
:
dynamo_runtime
::
CancellationToken
,
cancellation_token
:
CancellationToken
,
kv_block_size
:
usize
,
)
{
)
{
tracing
::
debug!
(
tracing
::
debug!
(
"KVEventPublisher connecting to ZMQ endpoint {} (topic '{}')"
,
"KVEventPublisher connecting to ZMQ endpoint {} (topic '{}')"
,
...
@@ -239,6 +231,8 @@ async fn start_zmq_listener(
...
@@ -239,6 +231,8 @@ async fn start_zmq_listener(
zmq_topic
zmq_topic
);
);
let
warning_count
=
Arc
::
new
(
AtomicU32
::
new
(
0
));
let
mut
socket
=
SubSocket
::
new
();
let
mut
socket
=
SubSocket
::
new
();
// Subscribe to the requested topic (empty string == all topics)
// Subscribe to the requested topic (empty string == all topics)
...
@@ -259,7 +253,7 @@ async fn start_zmq_listener(
...
@@ -259,7 +253,7 @@ async fn start_zmq_listener(
biased
;
biased
;
// Check for cancellation
// Check for cancellation
_
=
zmq
_token
.cancelled
()
=>
{
_
=
cancellation
_token
.cancelled
()
=>
{
tracing
::
info!
(
"ZMQ listener received cancellation signal"
);
tracing
::
info!
(
"ZMQ listener received cancellation signal"
);
break
;
break
;
}
}
...
@@ -292,7 +286,6 @@ async fn start_zmq_listener(
...
@@ -292,7 +286,6 @@ async fn start_zmq_listener(
tokio
::
time
::
sleep
(
Duration
::
from_millis
(
backoff_ms
))
.await
;
tokio
::
time
::
sleep
(
Duration
::
from_millis
(
backoff_ms
))
.await
;
continue
;
continue
;
};
};
// Reset error count on successful message
// Reset error count on successful message
consecutive_errors
=
0
;
consecutive_errors
=
0
;
...
@@ -303,8 +296,10 @@ async fn start_zmq_listener(
...
@@ -303,8 +296,10 @@ async fn start_zmq_listener(
tracing
::
warn!
(
expected
=
3
,
actual
=%
frames
.len
(),
"Received unexpected ZMQ frame count"
);
tracing
::
warn!
(
expected
=
3
,
actual
=%
frames
.len
(),
"Received unexpected ZMQ frame count"
);
continue
;
continue
;
}
}
let
payload
=
frames
.remove
(
2
);
let
seq_bytes
=
frames
.remove
(
1
);
// Extract the payload and sequence number.
let
payload
=
frames
.pop
()
.unwrap
();
let
seq_bytes
=
frames
.pop
()
.unwrap
();
if
seq_bytes
.len
()
!=
8
{
if
seq_bytes
.len
()
!=
8
{
tracing
::
warn!
(
expected
=
8
,
actual
=%
seq_bytes
.len
(),
"Invalid sequence number byte length"
);
tracing
::
warn!
(
expected
=
8
,
actual
=%
seq_bytes
.len
(),
"Invalid sequence number byte length"
);
...
@@ -312,14 +307,28 @@ async fn start_zmq_listener(
...
@@ -312,14 +307,28 @@ async fn start_zmq_listener(
}
}
let
seq
=
u64
::
from_be_bytes
(
seq_bytes
.try_into
()
.unwrap
());
let
seq
=
u64
::
from_be_bytes
(
seq_bytes
.try_into
()
.unwrap
());
if
raw_tx
.send
((
seq
,
payload
))
.is_err
()
{
tracing
::
warn!
(
"Failed to send message to channel - receiver dropped"
);
// Decode our batch of events.
break
;
let
batch_result
=
rmps
::
from_slice
::
<
KvEventBatch
>
(
&
payload
);
let
Ok
(
batch
)
=
batch_result
else
{
let
e
=
batch_result
.unwrap_err
();
tracing
::
warn!
(
error
=%
e
,
"Failed to decode KVEventBatch msgpack"
);
continue
;
};
// For each of our events, convert them to [`KvCacheEvent`] and send to the event_processor.
for
raw_event
in
batch
.events
.into_iter
()
{
if
let
Some
(
event
)
=
convert_event
(
raw_event
,
seq
,
kv_block_size
,
&
warning_count
)
{
if
tx
.send
(
event
)
.is_err
()
{
tracing
::
warn!
(
"Failed to send message to channel - receiver dropped"
);
return
;
}
}
}
}
}
}
}
}
tracing
::
debug!
(
"ZMQ listener exiting"
);
}
}
tracing
::
debug!
(
"ZMQ listener exiting"
);
}
}
/// Convert a raw event coming from the ZMQ channel into the internal
/// Convert a raw event coming from the ZMQ channel into the internal
...
@@ -630,6 +639,7 @@ mod test_event_processing {
...
@@ -630,6 +639,7 @@ mod test_event_processing {
#[cfg(test)]
#[cfg(test)]
mod
tests_startup_helpers
{
mod
tests_startup_helpers
{
use
super
::
*
;
use
super
::
*
;
use
crate
::
kv_router
::
protocols
::
ExternalSequenceBlockHash
;
use
async_trait
;
use
async_trait
;
use
bytes
::
Bytes
;
use
bytes
::
Bytes
;
use
std
::
sync
::{
Arc
,
Mutex
};
use
std
::
sync
::{
Arc
,
Mutex
};
...
@@ -691,53 +701,35 @@ mod tests_startup_helpers {
...
@@ -691,53 +701,35 @@ mod tests_startup_helpers {
}
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Test start_event_processor
in isolation
// Test start_event_processor
//--------------------------------------------------------------------
//--------------------------------------------------------------------
#[tokio::test]
#[tokio::test]
async
fn
test_start_event_processor_sends_router_event
()
{
async
fn
test_start_event_processor
()
{
let
kv_block_size
=
4
;
let
(
component
,
published
)
=
MockComponent
::
new
();
let
worker_id
=
99
;
let
event
=
KvCacheEvent
{
// 1) build a one-item KvEventBatch and msgpack-encode it
event_id
:
1
,
let
batch
=
KvEventBatch
{
data
:
KvCacheEventData
::
Removed
(
KvCacheRemoveData
{
ts
:
0.0
,
block_hashes
:
vec!
[
ExternalSequenceBlockHash
(
1
),
ExternalSequenceBlockHash
(
2
)],
events
:
vec!
[
RawKvEvent
::
BlockRemoved
{
}),
block_hashes
:
vec!
[
1
,
2
],
}],
};
};
let
payload
=
rmps
::
to_vec
(
&
batch
)
.unwrap
();
let
token
=
dynamo_runtime
::
CancellationToken
::
new
();
// 2) ch
an
n
el
feeding the processor
let
token
=
C
an
c
el
lationToken
::
new
();
let
(
tx
,
rx
)
=
mpsc
::
unbounded_channel
::
<
(
u64
,
Vec
<
u8
>
)
>
();
let
(
tx
,
rx
)
=
mpsc
::
unbounded_channel
::
<
KvCacheEvent
>
();
tx
.send
(
(
123
,
payload
.clone
()))
.unwrap
();
// seq = 123
tx
.send
(
event
)
.unwrap
();
drop
(
tx
);
drop
(
tx
);
// 3) mock component to capture output
let
handle
=
tokio
::
spawn
(
start_event_processor
(
component
,
1
,
token
,
rx
));
let
(
comp
,
published
)
=
MockComponent
::
new
();
// 4) run the function under test (let it consume exactly one msg)
let
handle
=
tokio
::
spawn
(
start_event_processor
(
rx
,
comp
,
worker_id
,
kv_block_size
,
Arc
::
new
(
AtomicU32
::
new
(
0
)),
token
,
));
tokio
::
time
::
timeout
(
std
::
time
::
Duration
::
from_secs
(
1
),
handle
)
tokio
::
time
::
timeout
(
tokio
::
time
::
Duration
::
from_secs
(
1
),
handle
)
.await
.await
.unwrap
()
.unwrap
()
.unwrap
();
.unwrap
();
// 5) assert we have exactly one RouterEvent pushed with right worker_id
let
published
=
published
.lock
()
.unwrap
();
let
published
=
published
.lock
()
.unwrap
();
let
(
subject
,
bytes
)
=
&
published
[
0
]
;
assert_eq!
(
published
.len
(),
1
)
;
let
(
subject
,
_
)
=
&
published
[
0
];
assert_eq!
(
subject
,
&
KV_EVENT_SUBJECT
.to_string
());
assert_eq!
(
subject
,
&
KV_EVENT_SUBJECT
.to_string
());
assert_eq!
(
bytes
.first
(),
payload
.first
())
}
}
//--------------------------------------------------------------------
//--------------------------------------------------------------------
...
@@ -747,7 +739,7 @@ mod tests_startup_helpers {
...
@@ -747,7 +739,7 @@ mod tests_startup_helpers {
#[tokio::test]
#[tokio::test]
async
fn
test_start_zmq_listener_pushes_to_channel
()
{
async
fn
test_start_zmq_listener_pushes_to_channel
()
{
// Prepare channel that listener should fill
// Prepare channel that listener should fill
let
(
tx
,
mut
rx
)
=
mpsc
::
unbounded_channel
::
<
(
u64
,
Vec
<
u8
>
)
>
();
let
(
tx
,
mut
rx
)
=
mpsc
::
unbounded_channel
::
<
KvCacheEvent
>
();
// ZMQ TCP endpoint using localhost with fixed port
// ZMQ TCP endpoint using localhost with fixed port
let
endpoint
=
"tcp://127.0.0.1:15555"
;
let
endpoint
=
"tcp://127.0.0.1:15555"
;
...
@@ -763,7 +755,7 @@ mod tests_startup_helpers {
...
@@ -763,7 +755,7 @@ mod tests_startup_helpers {
// Spawn async listener
// Spawn async listener
let
listener_handle
=
tokio
::
spawn
({
let
listener_handle
=
tokio
::
spawn
({
let
token
=
token
.clone
();
let
token
=
token
.clone
();
start_zmq_listener
(
endpoint
.to_string
(),
topic
,
tx
,
token
)
start_zmq_listener
(
endpoint
.to_string
(),
topic
,
tx
,
token
,
4
)
});
});
// Give time for the connection to establish
// Give time for the connection to establish
...
@@ -771,7 +763,18 @@ mod tests_startup_helpers {
...
@@ -771,7 +763,18 @@ mod tests_startup_helpers {
// Send synthetic 3-frame message: [topic, seq(8B), payload]
// Send synthetic 3-frame message: [topic, seq(8B), payload]
let
seq
:
u64
=
77
;
let
seq
:
u64
=
77
;
let
payload
=
Bytes
::
from
(
"hello"
);
let
events
=
vec!
[
RawKvEvent
::
BlockStored
{
block_hashes
:
vec!
[
42
],
parent_block_hash
:
None
,
token_ids
:
vec!
[
0
,
1
,
2
,
3
],
block_size
:
4
,
lora_id
:
None
,
}];
let
batch
=
KvEventBatch
{
ts
:
0.0
,
events
};
let
payload
=
Bytes
::
from
(
rmps
::
to_vec
(
&
batch
)
.unwrap
());
let
frames
=
vec!
[
let
frames
=
vec!
[
Bytes
::
from
(
""
),
Bytes
::
from
(
""
),
...
@@ -789,9 +792,19 @@ mod tests_startup_helpers {
...
@@ -789,9 +792,19 @@ mod tests_startup_helpers {
tokio
::
time
::
sleep
(
tokio
::
time
::
Duration
::
from_millis
(
100
))
.await
;
tokio
::
time
::
sleep
(
tokio
::
time
::
Duration
::
from_millis
(
100
))
.await
;
// Check that we received the message
// Check that we received the message
let
(
got_seq
,
got_payload
)
=
rx
.try_recv
()
.expect
(
"no message received"
);
let
event
=
rx
.try_recv
()
.expect
(
"no message received"
);
assert_eq!
(
got_seq
,
seq
);
assert_eq!
(
got_payload
,
payload
);
let
KvCacheEventData
::
Stored
(
KvCacheStoreData
{
parent_hash
,
blocks
,
})
=
event
.data
else
{
panic!
(
"expected KvCacheStoreData"
);
};
assert
!
(
parent_hash
.is_none
());
assert_eq!
(
blocks
.len
(),
1
);
assert_eq!
(
blocks
[
0
]
.block_hash
.0
,
42
);
// Stop the listener
// Stop the listener
token
.cancel
();
token
.cancel
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment