1use std::thread::JoinHandle;
12
13use tokio::sync::broadcast;
14use tracing::{error, info};
15
16use crate::{
17 algorithm::AlgorithmConfig,
18 derived::{events::DerivedDataEvent, SharedDerivedDataRef},
19 feed::{
20 events::{MarketEvent, MarketEventHandler},
21 market_data::MarketData,
22 },
23 graph::EdgeWeightUpdaterWithDerived,
24 propamm_fallback::SharedFeeTiers,
25 types::internal::SolveTask,
26 worker_pool::{
27 registry::{
28 spawn_workers_generic, AlgorithmSpawner, SpawnWorkersParams, UnknownAlgorithmError,
29 DEFAULT_ALGORITHM,
30 },
31 task_queue::{TaskQueue, TaskQueueConfig, TaskQueueHandle},
32 },
33 worker_pool_router::LiquidityScope,
34};
35
36#[derive(Debug)]
38pub struct WorkerPoolConfig {
39 name: String,
43 spawner: AlgorithmSpawner,
45 num_workers: usize,
47 algorithm_config: AlgorithmConfig,
49 task_queue_capacity: usize,
51 liquidity_scope: LiquidityScope,
53 fallback_fee_tiers: SharedFeeTiers,
55}
56
57impl WorkerPoolConfig {
58 pub fn algorithm_name(&self) -> &str {
60 self.spawner.algorithm_name()
61 }
62}
63
64impl Default for WorkerPoolConfig {
65 fn default() -> Self {
66 Self {
67 name: DEFAULT_ALGORITHM.to_string(),
68 spawner: AlgorithmSpawner::Registry { algorithm: DEFAULT_ALGORITHM.to_string() },
69 num_workers: num_cpus::get(),
70 algorithm_config: AlgorithmConfig::default(),
71 task_queue_capacity: 1000,
72 liquidity_scope: LiquidityScope::default(),
73 fallback_fee_tiers: SharedFeeTiers::default(),
74 }
75 }
76}
77
78pub struct WorkerPool {
83 name: String,
85 algorithm: String,
87 workers: Vec<JoinHandle<()>>,
89 shutdown_tx: broadcast::Sender<()>,
91}
92
93impl WorkerPool {
94 pub fn spawn(
109 config: WorkerPoolConfig,
110 task_rx: async_channel::Receiver<SolveTask>,
111 market_data: MarketData,
112 derived_data: SharedDerivedDataRef,
113 event_rx: broadcast::Receiver<MarketEvent>,
114 derived_event_rx: broadcast::Receiver<DerivedDataEvent>,
115 ) -> Result<Self, UnknownAlgorithmError> {
116 let (shutdown_tx, _) = broadcast::channel(1);
117 let name = config.name.clone();
118 let algorithm = config
119 .spawner
120 .algorithm_name()
121 .to_string();
122
123 let liquidity_scope = config.liquidity_scope;
125 let fallback_fee_tiers = config.fallback_fee_tiers.clone();
126 let params = SpawnWorkersParams {
127 algorithm: algorithm.clone(),
128 pool_name: name.clone(),
129 num_workers: config.num_workers,
130 algorithm_config: config.algorithm_config,
131 task_rx,
132 market_data,
133 derived_data,
134 event_rx,
135 derived_event_rx,
136 shutdown_tx: shutdown_tx.clone(),
137 liquidity_scope,
138 fallback_fee_tiers,
139 };
140 let workers = config.spawner.spawn(params)?;
141
142 info!(
143 name = %name,
144 algorithm = %algorithm,
145 num_workers = workers.len(),
146 "worker pool spawned"
147 );
148
149 Ok(Self { name, algorithm, workers, shutdown_tx })
150 }
151
152 pub fn name(&self) -> &str {
154 &self.name
155 }
156
157 pub fn algorithm(&self) -> &str {
159 &self.algorithm
160 }
161
162 pub fn num_workers(&self) -> usize {
164 self.workers.len()
165 }
166
167 pub fn shutdown(self) {
169 info!(name = %self.name, "shutting down worker pool");
170
171 let _ = self.shutdown_tx.send(());
173
174 for (i, handle) in self.workers.into_iter().enumerate() {
176 if let Err(e) = handle.join() {
177 error!(
178 name = %self.name,
179 worker_id = i,
180 "worker thread panicked: {:?}",
181 e
182 );
183 }
184 }
185
186 info!(name = %self.name, "worker pool shut down");
187 }
188}
189
190#[must_use = "a builder does nothing until .build() is called"]
203pub struct WorkerPoolBuilder {
204 config: WorkerPoolConfig,
205}
206
207impl WorkerPoolBuilder {
208 pub fn new() -> Self {
210 Self { config: WorkerPoolConfig::default() }
211 }
212
213 pub fn name(mut self, name: impl Into<String>) -> Self {
215 self.config.name = name.into();
216 self
217 }
218
219 pub fn algorithm(mut self, algorithm: impl Into<String>) -> Self {
224 self.config.spawner = AlgorithmSpawner::Registry { algorithm: algorithm.into() };
225 self
226 }
227
228 pub fn with_algorithm<A, F>(mut self, name: impl Into<String>, factory: F) -> Self
240 where
241 A: crate::algorithm::Algorithm + 'static,
242 A::GraphManager: MarketEventHandler + EdgeWeightUpdaterWithDerived + 'static,
243 F: Fn(AlgorithmConfig) -> A + Clone + Send + Sync + 'static,
244 {
245 let name = name.into();
246 let spawner =
247 Box::new(move |params: SpawnWorkersParams| spawn_workers_generic(params, &factory));
248 self.config.spawner = AlgorithmSpawner::Custom { algorithm: name, spawner };
249 self
250 }
251
252 pub fn algorithm_config(mut self, config: AlgorithmConfig) -> Self {
254 self.config.algorithm_config = config;
255 self
256 }
257
258 pub fn num_workers(mut self, n: usize) -> Self {
260 self.config.num_workers = n;
261 self
262 }
263
264 pub fn task_queue_capacity(mut self, capacity: usize) -> Self {
266 self.config.task_queue_capacity = capacity;
267 self
268 }
269
270 pub fn liquidity_scope(mut self, scope: LiquidityScope) -> Self {
272 self.config.liquidity_scope = scope;
273 self
274 }
275
276 pub fn fallback_fee_tiers(mut self, fallback_fee_tiers: SharedFeeTiers) -> Self {
278 self.config.fallback_fee_tiers = fallback_fee_tiers;
279 self
280 }
281
282 pub fn build(
291 self,
292 market_data: MarketData,
293 derived_data: SharedDerivedDataRef,
294 event_rx: broadcast::Receiver<MarketEvent>,
295 derived_event_rx: broadcast::Receiver<DerivedDataEvent>,
296 ) -> Result<(WorkerPool, TaskQueueHandle), UnknownAlgorithmError> {
297 let task_queue =
299 TaskQueue::new(TaskQueueConfig { capacity: self.config.task_queue_capacity });
300 let (task_handle, task_rx) = task_queue.split();
301
302 let pool = WorkerPool::spawn(
304 self.config,
305 task_rx,
306 market_data,
307 derived_data,
308 event_rx,
309 derived_event_rx,
310 )?;
311
312 Ok((pool, task_handle))
313 }
314}
315
316impl Default for WorkerPoolBuilder {
317 fn default() -> Self {
318 Self::new()
319 }
320}