Improved architecture with signle mutex
This commit is contained in:
@@ -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<()> {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
@@ -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?;
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
|
||||||
&self,
|
|
||||||
mut child: Child,
|
|
||||||
manifest: StrategyManifest,
|
|
||||||
) -> tokio::io::Result<()> {
|
|
||||||
let mut process = self.process.lock().await;
|
|
||||||
|
|
||||||
process.kill().await?;
|
|
||||||
|
|
||||||
*self.stdout.lock().await = child.stdout.take().expect("Failed to obtain child stdout");
|
|
||||||
*self.manifest.lock().await = manifest;
|
|
||||||
*process = child;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub 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()))?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user