Skip to content

Commit 12570ae

Browse files
committed
Continue LiveNode and NautilusKernel in Rust
1 parent 39e4616 commit 12570ae

File tree

7 files changed

+237
-81
lines changed

7 files changed

+237
-81
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ tracing = "0.1.41"
114114
tracing-subscriber = { version = "0.3.19", default-features = false, features = ["smallvec", "fmt", "ansi", "std", "env-filter"] }
115115
tokio = { version = "1.45.1", features = ["full"] }
116116
tokio-tungstenite = { version = "0.26.2", features = ["rustls-tls-native-roots"] }
117+
tokio-util = "0.7.14"
117118
ustr = { version = "1.1.0", features = ["serde"] }
118119
uuid = { version = "1.17.0", features = ["v4", "serde"] }
119120

crates/adapters/databento/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ serde = { workspace = true }
5858
serde_json = { workspace = true }
5959
strum = { workspace = true }
6060
tokio = { workspace = true }
61+
tokio-util = { workspace = true }
6162
tracing = { workspace = true }
6263
tracing-subscriber = { workspace = true, optional = true }
6364
ustr = { workspace = true }

crates/adapters/databento/src/data.rs

Lines changed: 132 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ use nautilus_model::{
3838
identifiers::{ClientId, Symbol, Venue},
3939
instruments::Instrument,
4040
};
41-
use tokio::sync::mpsc;
41+
use tokio::{sync::mpsc, task::JoinHandle};
42+
use tokio_util::sync::CancellationToken;
4243

4344
use crate::{
4445
historical::{DatabentoHistoricalClient, RangeQueryParams},
@@ -99,6 +100,10 @@ pub struct DatabentoDataClient {
99100
loader: DatabentoDataLoader,
100101
/// Feed handler command senders per dataset.
101102
cmd_channels: Arc<Mutex<AHashMap<String, mpsc::UnboundedSender<LiveCommand>>>>,
103+
/// Task handles for life cycle management.
104+
task_handles: Arc<Mutex<Vec<JoinHandle<()>>>>,
105+
/// Cancellation token for graceful shutdown.
106+
cancellation_token: CancellationToken,
102107
/// Publisher to venue mapping.
103108
publisher_venue_map: Arc<IndexMap<PublisherId, Venue>>,
104109
/// Symbol to venue mapping (for caching).
@@ -143,6 +148,8 @@ impl DatabentoDataClient {
143148
historical,
144149
loader,
145150
cmd_channels: Arc::new(Mutex::new(AHashMap::new())),
151+
task_handles: Arc::new(Mutex::new(Vec::new())),
152+
cancellation_token: CancellationToken::new(),
146153
publisher_venue_map: Arc::new(publisher_venue_map),
147154
symbol_venue_map: Arc::new(RwLock::new(AHashMap::new())),
148155
})
@@ -167,9 +174,9 @@ impl DatabentoDataClient {
167174
if !channels.contains_key(dataset) {
168175
tracing::info!("Creating new feed handler for dataset: {dataset}");
169176
let cmd_tx = self.initialize_live_feed(dataset.to_string())?;
170-
channels.insert(dataset.to_string(), cmd_tx);
177+
channels.insert(dataset.to_string(), cmd_tx.clone());
171178

172-
self.send_command_to_dataset(dataset, LiveCommand::Start)?;
179+
tracing::debug!("Feed handler created for dataset: {dataset}, channel stored");
173180
}
174181

175182
Ok(())
@@ -214,50 +221,79 @@ impl DatabentoDataClient {
214221
self.config.bars_timestamp_on_close,
215222
);
216223

217-
// Spawn the feed handler task
218-
tokio::spawn(async move {
219-
if let Err(e) = feed_handler.run().await {
220-
tracing::error!("Feed handler error: {e}");
224+
let cancellation_token = self.cancellation_token.clone();
225+
226+
// Spawn the feed handler task with cancellation support
227+
let feed_handle = tokio::spawn(async move {
228+
tokio::select! {
229+
result = feed_handler.run() => {
230+
if let Err(e) = result {
231+
tracing::error!("Feed handler error: {e}");
232+
}
233+
}
234+
_ = cancellation_token.cancelled() => {
235+
tracing::info!("Feed handler cancelled");
236+
}
221237
}
222238
});
223239

224-
// Spawn message processing task
225-
tokio::spawn(async move {
240+
let cancellation_token = self.cancellation_token.clone();
241+
242+
// Spawn message processing task with cancellation support
243+
let msg_handle = tokio::spawn(async move {
226244
let mut msg_rx = msg_rx;
227-
while let Some(msg) = msg_rx.recv().await {
228-
match msg {
229-
LiveMessage::Data(data) => {
230-
tracing::debug!("Received data: {data:?}");
231-
// TODO: Forward to message bus or data engine
232-
}
233-
LiveMessage::Instrument(instrument) => {
234-
tracing::debug!("Received instrument: {}", instrument.id());
235-
// TODO: Forward to cache or instrument manager
236-
}
237-
LiveMessage::Status(status) => {
238-
tracing::debug!("Received status: {status:?}");
239-
// TODO: Forward to appropriate handler
240-
}
241-
LiveMessage::Imbalance(imbalance) => {
242-
tracing::debug!("Received imbalance: {imbalance:?}");
243-
// TODO: Forward to appropriate handler
245+
loop {
246+
tokio::select! {
247+
msg = msg_rx.recv() => {
248+
match msg {
249+
Some(LiveMessage::Data(data)) => {
250+
tracing::debug!("Received data: {data:?}");
251+
// TODO: Forward to message bus or data engine
252+
}
253+
Some(LiveMessage::Instrument(instrument)) => {
254+
tracing::debug!("Received instrument: {}", instrument.id());
255+
// TODO: Forward to cache or instrument manager
256+
}
257+
Some(LiveMessage::Status(status)) => {
258+
tracing::debug!("Received status: {status:?}");
259+
// TODO: Forward to appropriate handler
260+
}
261+
Some(LiveMessage::Imbalance(imbalance)) => {
262+
tracing::debug!("Received imbalance: {imbalance:?}");
263+
// TODO: Forward to appropriate handler
264+
}
265+
Some(LiveMessage::Statistics(statistics)) => {
266+
tracing::debug!("Received statistics: {statistics:?}");
267+
// TODO: Forward to appropriate handler
268+
}
269+
Some(LiveMessage::Error(error)) => {
270+
tracing::error!("Feed handler error: {error}");
271+
// TODO: Handle error appropriately
272+
}
273+
Some(LiveMessage::Close) => {
274+
tracing::info!("Feed handler closed");
275+
break;
276+
}
277+
None => {
278+
tracing::debug!("Message channel closed");
279+
break;
280+
}
281+
}
244282
}
245-
LiveMessage::Statistics(statistics) => {
246-
tracing::debug!("Received statistics: {statistics:?}");
247-
// TODO: Forward to appropriate handler
248-
}
249-
LiveMessage::Error(error) => {
250-
tracing::error!("Feed handler error: {error}");
251-
// TODO: Handle error appropriately
252-
}
253-
LiveMessage::Close => {
254-
tracing::info!("Feed handler closed");
283+
_ = cancellation_token.cancelled() => {
284+
tracing::info!("Message processing cancelled");
255285
break;
256286
}
257287
}
258288
}
259289
});
260290

291+
{
292+
let mut handles = self.task_handles.lock().unwrap();
293+
handles.push(feed_handle);
294+
handles.push(msg_handle);
295+
}
296+
261297
Ok(cmd_tx)
262298
}
263299
}
@@ -280,6 +316,9 @@ impl DataClient for DatabentoDataClient {
280316
fn stop(&self) -> anyhow::Result<()> {
281317
tracing::debug!("Stopping Databento data client");
282318

319+
// Signal cancellation to all running tasks
320+
self.cancellation_token.cancel();
321+
283322
// Send close command to all active feed handlers
284323
let channels = self.cmd_channels.lock().unwrap();
285324
for (dataset, tx) in channels.iter() {
@@ -317,6 +356,9 @@ impl DataClient for DatabentoDataClient {
317356
async fn disconnect(&self) -> anyhow::Result<()> {
318357
tracing::debug!("Disconnecting Databento data client");
319358

359+
// Signal cancellation to all running tasks
360+
self.cancellation_token.cancel();
361+
320362
// Send close command to all active feed handlers
321363
{
322364
let channels = self.cmd_channels.lock().unwrap();
@@ -327,9 +369,22 @@ impl DataClient for DatabentoDataClient {
327369
}
328370
}
329371

372+
// Wait for all spawned tasks to complete
373+
let handles = {
374+
let mut task_handles = self.task_handles.lock().unwrap();
375+
std::mem::take(&mut *task_handles)
376+
};
377+
378+
for handle in handles {
379+
if let Err(e) = handle.await {
380+
if !e.is_cancelled() {
381+
tracing::error!("Task join error: {e}");
382+
}
383+
}
384+
}
385+
330386
self.is_connected.store(false, Ordering::Relaxed);
331387

332-
// Clear all command senders
333388
{
334389
let mut channels = self.cmd_channels.lock().unwrap();
335390
channels.clear();
@@ -352,8 +407,18 @@ impl DataClient for DatabentoDataClient {
352407
tracing::debug!("Subscribe quotes: {cmd:?}");
353408

354409
let dataset = self.get_dataset_for_venue(cmd.instrument_id.venue)?;
410+
let was_new_handler = {
411+
let channels = self.cmd_channels.lock().unwrap();
412+
!channels.contains_key(&dataset)
413+
};
414+
355415
self.get_or_create_feed_handler(&dataset)?;
356416

417+
// Start the feed handler if it was newly created
418+
if was_new_handler {
419+
self.send_command_to_dataset(&dataset, LiveCommand::Start)?;
420+
}
421+
357422
let symbol = instrument_id_to_symbol_string(
358423
cmd.instrument_id,
359424
&mut self.symbol_venue_map.write().unwrap(),
@@ -373,8 +438,18 @@ impl DataClient for DatabentoDataClient {
373438
tracing::debug!("Subscribe trades: {cmd:?}");
374439

375440
let dataset = self.get_dataset_for_venue(cmd.instrument_id.venue)?;
441+
let was_new_handler = {
442+
let channels = self.cmd_channels.lock().unwrap();
443+
!channels.contains_key(&dataset)
444+
};
445+
376446
self.get_or_create_feed_handler(&dataset)?;
377447

448+
// Start the feed handler if it was newly created
449+
if was_new_handler {
450+
self.send_command_to_dataset(&dataset, LiveCommand::Start)?;
451+
}
452+
378453
let symbol = instrument_id_to_symbol_string(
379454
cmd.instrument_id,
380455
&mut self.symbol_venue_map.write().unwrap(),
@@ -394,8 +469,18 @@ impl DataClient for DatabentoDataClient {
394469
tracing::debug!("Subscribe book deltas: {cmd:?}");
395470

396471
let dataset = self.get_dataset_for_venue(cmd.instrument_id.venue)?;
472+
let was_new_handler = {
473+
let channels = self.cmd_channels.lock().unwrap();
474+
!channels.contains_key(&dataset)
475+
};
476+
397477
self.get_or_create_feed_handler(&dataset)?;
398478

479+
// Start the feed handler if it was newly created
480+
if was_new_handler {
481+
self.send_command_to_dataset(&dataset, LiveCommand::Start)?;
482+
}
483+
399484
let symbol = instrument_id_to_symbol_string(
400485
cmd.instrument_id,
401486
&mut self.symbol_venue_map.write().unwrap(),
@@ -418,8 +503,18 @@ impl DataClient for DatabentoDataClient {
418503
tracing::debug!("Subscribe instrument status: {cmd:?}");
419504

420505
let dataset = self.get_dataset_for_venue(cmd.instrument_id.venue)?;
506+
let was_new_handler = {
507+
let channels = self.cmd_channels.lock().unwrap();
508+
!channels.contains_key(&dataset)
509+
};
510+
421511
self.get_or_create_feed_handler(&dataset)?;
422512

513+
// Start the feed handler if it was newly created
514+
if was_new_handler {
515+
self.send_command_to_dataset(&dataset, LiveCommand::Start)?;
516+
}
517+
423518
let symbol = instrument_id_to_symbol_string(
424519
cmd.instrument_id,
425520
&mut self.symbol_venue_map.write().unwrap(),

crates/backtest/src/engine.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,10 @@ impl BacktestEngine {
233233
// Check client has been registered
234234
self.add_market_data_client_if_not_exists(instrument.id().venue);
235235

236-
self.kernel.data_engine.process(&instrument as &dyn Any);
236+
self.kernel
237+
.data_engine
238+
.borrow_mut()
239+
.process(&instrument as &dyn Any);
237240
log::info!(
238241
"Added instrument {} to exchange {}",
239242
instrument_id,
@@ -342,6 +345,7 @@ impl BacktestEngine {
342345
if !self
343346
.kernel
344347
.data_engine
348+
.borrow()
345349
.registered_clients()
346350
.contains(&client_id)
347351
{
@@ -356,6 +360,7 @@ impl BacktestEngine {
356360
);
357361
self.kernel
358362
.data_engine
363+
.borrow_mut()
359364
.register_client(data_client_adapter, None);
360365
}
361366
}
@@ -436,11 +441,20 @@ mod tests {
436441
.get(&venue)
437442
.is_some_and(|venue| venue.borrow().get_matching_engine(&instrument_id).is_some())
438443
);
439-
assert_eq!(engine.kernel.data_engine.registered_clients().len(), 1);
444+
assert_eq!(
445+
engine
446+
.kernel
447+
.data_engine
448+
.borrow()
449+
.registered_clients()
450+
.len(),
451+
1
452+
);
440453
assert!(
441454
engine
442455
.kernel
443456
.data_engine
457+
.borrow()
444458
.registered_clients()
445459
.contains(&client_id)
446460
);

0 commit comments

Comments
 (0)