Improved architecture with signle mutex

This commit is contained in:
2026-07-30 05:56:59 +02:00
parent 535c6a2964
commit 7f1e3f8d7d
7 changed files with 106 additions and 112 deletions
-5
View File
@@ -35,11 +35,6 @@ pub async fn send_raw(data: &[u8]) -> tokio::io::Result<()> {
Ok(()) Ok(())
} }
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
#[allow(async_fn_in_trait)] #[allow(async_fn_in_trait)]
pub trait Strategy { pub trait Strategy {
async fn start(&self) -> tokio::io::Result<()> { async fn start(&self) -> tokio::io::Result<()> {
+2 -2
View File
@@ -60,7 +60,7 @@ impl Engine {
.await; .await;
} }
self.strategy.reload(id.as_str()).await?; self.strategy_engine.reload(id.as_str()).await?;
self.config.lock().await.strategy = id; self.config.lock().await.strategy = id;
}); });
@@ -75,7 +75,7 @@ impl Engine {
self.terminal_server.broadcast( self.terminal_server.broadcast(
pulse_sdk::terminal::TerminalServerMessage::StrategyUpdated(Strategy { pulse_sdk::terminal::TerminalServerMessage::StrategyUpdated(Strategy {
strategy: self.strategy.child.manifest.lock().await.clone(), strategy: self.strategy_engine.strategy.lock().await.manifest.clone(),
mode: Mode::Auto, mode: Mode::Auto,
state: ItemState::Running, state: ItemState::Running,
+2 -2
View File
@@ -20,7 +20,7 @@ pub struct WatchList {
#[derive(Clone)] #[derive(Clone)]
pub struct Engine { pub struct Engine {
pub terminal_server: Arc<TerminalServer>, pub terminal_server: Arc<TerminalServer>,
pub strategy: Arc<StrategyEngine>, pub strategy_engine: Arc<StrategyEngine>,
pub config: Arc<Mutex<Config>>, pub config: Arc<Mutex<Config>>,
pub accounts: Arc<Mutex<AccountList>>, pub accounts: Arc<Mutex<AccountList>>,
pub watch_list: Arc<Mutex<WatchList>>, pub watch_list: Arc<Mutex<WatchList>>,
@@ -38,7 +38,7 @@ impl Engine {
Ok(Arc::new_cyclic(|engine| Self { Ok(Arc::new_cyclic(|engine| Self {
terminal_server: TerminalServer::new(engine.clone()), terminal_server: TerminalServer::new(engine.clone()),
strategy: strategy.initialize(engine.clone()), strategy_engine: strategy.initialize(engine.clone()),
config, config,
accounts, accounts,
watch_list: Arc::new(Mutex::new(WatchList { watch_list: Arc::new(Mutex::new(WatchList {
+56 -58
View File
@@ -1,26 +1,24 @@
use anyhow::Context;
use hypersdk::hypercore::{self, CandleInterval, Subscription, WebSocket}; use hypersdk::hypercore::{self, CandleInterval, Subscription, WebSocket};
use pulse_sdk::prelude::*; use pulse_sdk::prelude::*;
use std::{ use std::{
collections::HashSet, collections::HashSet,
path::PathBuf,
process::Stdio,
sync::{Arc, Weak}, sync::{Arc, Weak},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use tokio::{ use tokio::{fs, sync::Mutex};
fs,
process::{Child, Command},
sync::Mutex,
};
use crate::{ use crate::{
engine::Engine, engine::Engine,
store::{pulse_strategy, strategy::StrategyChild}, store::{
pulse_strategy,
strategy::{StrategyChild, get_manifest},
},
}; };
pub struct StrategyEngine { pub struct StrategyEngine {
pub engine: Weak<Engine>, pub engine: Weak<Engine>,
pub child: Arc<StrategyChild>, pub strategy: Mutex<StrategyChild>,
pub ws: WebSocket, pub ws: WebSocket,
pub subscriptions: Mutex<HashSet<Subscription>>, pub subscriptions: Mutex<HashSet<Subscription>>,
} }
@@ -42,7 +40,7 @@ impl StrategyEngine {
)?; )?;
Ok(Self { Ok(Self {
child: Arc::new(StrategyChild::new(strategy, strategy_manifest)), strategy: Mutex::new(StrategyChild::new(strategy, strategy_manifest)),
engine: Weak::new(), engine: Weak::new(),
ws: hypercore::mainnet_ws(), ws: hypercore::mainnet_ws(),
subscriptions: Mutex::new(HashSet::new()), subscriptions: Mutex::new(HashSet::new()),
@@ -55,21 +53,63 @@ impl StrategyEngine {
Arc::new(self) Arc::new(self)
} }
pub async fn run(&self) -> anyhow::Result<()> { pub async fn send(self: &Arc<Self>, msg: &StrategyEngineMessage) -> tokio::io::Result<()> {
self.strategy.lock().await.send(msg).await
}
pub async fn spawn(self: &Arc<Self>) {
let engine = self.clone();
tokio::spawn(async move { engine.run().await });
}
pub async fn reload(self: &Arc<Self>, id: &str) -> tokio::io::Result<()> {
{
let strategy = pulse_strategy(id)?;
let (child, manifest) = get_manifest(
&strategy,
&strategy.join("strategy.bash"),
&fs::read(strategy.join("strategy.toml")).await?,
)?;
let mut strategy = self.strategy.lock().await;
strategy.child.kill().await?;
strategy.child = child;
strategy.manifest = manifest;
}
let engine = self.clone();
tokio::spawn(async move { engine.run().await });
Ok(())
}
pub async fn run(self: &Arc<Self>) -> anyhow::Result<()> {
let engine = self let engine = self
.engine .engine
.upgrade() .upgrade()
.expect("Failed to upgrade engine (StrategyEngine)"); .expect("Failed to upgrade engine (StrategyEngine)");
self.child.send(&StrategyEngineMessage::Initialize).await?; let mut stdout = {
let mut child = self.strategy.lock().await;
child
.child
.stdout
.take()
.context("Failed to obtain strategy stdout")?
};
self.send(&StrategyEngineMessage::Initialize).await?;
loop { loop {
match self.child.recv().await? { match StrategyChild::read(&mut stdout).await? {
None => {} None => {}
Some(StrategyMessage::GetWatchList) => { Some(StrategyMessage::GetWatchList) => {
self.child self.send(&StrategyEngineMessage::WatchList(
.send(&StrategyEngineMessage::WatchList(
engine.watch_list.lock().await.clone().items, engine.watch_list.lock().await.clone().items,
)) ))
.await?; .await?;
@@ -150,8 +190,7 @@ impl StrategyEngine {
let start_time = now.saturating_sub(interval_ms * count as u64); let start_time = now.saturating_sub(interval_ms * count as u64);
self.child self.send(&StrategyEngineMessage::Candlestick {
.send(&StrategyEngineMessage::Candlestick {
candles: client candles: client
.candle_snapshot(&symbol, interval, start_time, now) .candle_snapshot(&symbol, interval, start_time, now)
.await?, .await?,
@@ -163,45 +202,4 @@ impl StrategyEngine {
} }
} }
} }
pub async fn spawn(self: &Arc<Self>) {
let engine = self.clone();
tokio::spawn(async move { engine.run().await });
}
pub async fn reload(self: &Arc<Self>, id: &str) -> tokio::io::Result<()> {
let strategy = pulse_strategy(id)?;
let (child, manifest) = get_manifest(
&strategy,
&strategy.join("strategy.bash"),
&fs::read(strategy.join("strategy.toml")).await?,
)?;
self.child.reload(child, manifest).await?;
let engine = self.clone();
tokio::spawn(async move { engine.run().await });
Ok(())
}
}
fn get_manifest<'de, M: serde::Deserialize<'de>>(
strategy_dir: &PathBuf,
strategy_path: &PathBuf,
manifest: &'de [u8],
) -> tokio::io::Result<(Child, M)> {
Ok((
Command::new("bash")
.arg(strategy_path)
.current_dir(strategy_dir)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()?,
toml::from_slice(manifest)
.map_err(|v| tokio::io::Error::new(std::io::ErrorKind::InvalidInput, v.to_string()))?,
))
} }
+1 -1
View File
@@ -9,7 +9,7 @@ async fn main() -> anyhow::Result<()> {
let broadcaster = engine.spawn_broadcaster().await; let broadcaster = engine.spawn_broadcaster().await;
engine.strategy.spawn().await; engine.strategy_engine.spawn().await;
engine.terminal_server.run().await?; engine.terminal_server.run().await?;
+28 -33
View File
@@ -1,31 +1,25 @@
use std::{path::PathBuf, process::Stdio};
use pulse_sdk::{ use pulse_sdk::{
map_postcard_err, map_postcard_err,
strategy::{StrategyEngineMessage, StrategyManifest, StrategyMessage}, strategy::{StrategyEngineMessage, StrategyManifest, StrategyMessage},
}; };
use tokio::{ use tokio::{
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},
process::{Child, ChildStdout}, process::{Child, ChildStdout, Command},
sync::Mutex,
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct StrategyChild { pub struct StrategyChild {
pub manifest: Mutex<StrategyManifest>, pub manifest: StrategyManifest,
pub stdout: Mutex<ChildStdout>, pub child: Child,
pub process: Mutex<Child>,
} }
impl StrategyChild { impl StrategyChild {
pub fn new(mut child: Child, manifest: StrategyManifest) -> Self { pub fn new(child: Child, manifest: StrategyManifest) -> Self {
Self { Self { child, manifest }
stdout: Mutex::new(child.stdout.take().expect("Failed to obtain child stdout")),
process: Mutex::new(child),
manifest: Mutex::new(manifest),
}
} }
pub async fn recv(&self) -> tokio::io::Result<Option<StrategyMessage>> { pub async fn read(stdout: &mut ChildStdout) -> tokio::io::Result<Option<StrategyMessage>> {
let mut stdout = self.stdout.lock().await;
let mut len_buf = [0u8; size_of::<usize>()]; let mut len_buf = [0u8; size_of::<usize>()];
let size = stdout.read_exact(&mut len_buf).await?; let size = stdout.read_exact(&mut len_buf).await?;
@@ -42,14 +36,13 @@ impl StrategyChild {
Ok(Some(map_postcard_err(postcard::from_bytes(&buffer))?)) Ok(Some(map_postcard_err(postcard::from_bytes(&buffer))?))
} }
pub async fn send(&self, msg: &StrategyEngineMessage) -> tokio::io::Result<()> { pub async fn send(&mut self, msg: &StrategyEngineMessage) -> tokio::io::Result<()> {
self.send_raw(&map_postcard_err(postcard::to_allocvec(msg))?) self.send_raw(&map_postcard_err(postcard::to_allocvec(msg))?)
.await .await
} }
pub async fn send_raw(&self, msg: &[u8]) -> tokio::io::Result<()> { pub async fn send_raw(&mut self, msg: &[u8]) -> tokio::io::Result<()> {
let mut process = self.process.lock().await; let stdin = self.child.stdin.as_mut().unwrap();
let stdin = process.stdin.as_mut().unwrap();
stdin.write_all(&msg.len().to_le_bytes()).await?; stdin.write_all(&msg.len().to_le_bytes()).await?;
stdin.write_all(msg).await?; stdin.write_all(msg).await?;
@@ -57,20 +50,22 @@ impl StrategyChild {
Ok(()) Ok(())
} }
}
pub async fn reload( pub fn get_manifest<'de, M: serde::Deserialize<'de>>(
&self, strategy_dir: &PathBuf,
mut child: Child, strategy_path: &PathBuf,
manifest: StrategyManifest, manifest: &'de [u8],
) -> tokio::io::Result<()> { ) -> tokio::io::Result<(Child, M)> {
let mut process = self.process.lock().await; Ok((
Command::new("bash")
process.kill().await?; .arg(strategy_path)
.current_dir(strategy_dir)
*self.stdout.lock().await = child.stdout.take().expect("Failed to obtain child stdout"); .stdin(Stdio::piped())
*self.manifest.lock().await = manifest; .stdout(Stdio::piped())
*process = child; .stderr(Stdio::inherit())
.spawn()?,
Ok(()) toml::from_slice(manifest)
} .map_err(|v| tokio::io::Error::new(std::io::ErrorKind::InvalidInput, v.to_string()))?,
))
} }
+7 -1
View File
@@ -65,7 +65,13 @@ impl TerminalServer {
self.send_to( self.send_to(
id, id,
pulse_sdk::terminal::TerminalServerMessage::StrategyUpdated(Strategy { pulse_sdk::terminal::TerminalServerMessage::StrategyUpdated(Strategy {
strategy: engine.strategy.child.manifest.lock().await.clone(), strategy: engine
.strategy_engine
.strategy
.lock()
.await
.manifest
.clone(),
mode: Mode::Auto, mode: Mode::Auto,
state: ItemState::Running, state: ItemState::Running,
cooldown: engine.config.lock().await.cooldown, cooldown: engine.config.lock().await.cooldown,