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 exclude_protocols: Vec<String>,
55 fallback_fee_tiers: SharedFeeTiers,
57}
58
59impl WorkerPoolConfig {
60 pub fn algorithm_name(&self) -> &str {
62 self.spawner.algorithm_name()
63 }
64}
65
66impl Default for WorkerPoolConfig {
67 fn default() -> Self {
68 Self {
69 name: DEFAULT_ALGORITHM.to_string(),
70 spawner: AlgorithmSpawner::Registry { algorithm: DEFAULT_ALGORITHM.to_string() },
71 num_workers: num_cpus::get(),
72 algorithm_config: AlgorithmConfig::default(),
73 task_queue_capacity: 1000,
74 liquidity_scope: LiquidityScope::default(),
75 exclude_protocols: Vec::new(),
76 fallback_fee_tiers: SharedFeeTiers::default(),
77 }
78 }
79}
80
81pub struct WorkerPool {
86 name: String,
88 algorithm: String,
90 workers: Vec<JoinHandle<()>>,
92 shutdown_tx: broadcast::Sender<()>,
94}
95
96impl WorkerPool {
97 pub fn spawn(
112 config: WorkerPoolConfig,
113 task_rx: async_channel::Receiver<SolveTask>,
114 market_data: MarketData,
115 derived_data: SharedDerivedDataRef,
116 event_rx: broadcast::Receiver<MarketEvent>,
117 derived_event_rx: broadcast::Receiver<DerivedDataEvent>,
118 ) -> Result<Self, UnknownAlgorithmError> {
119 let (shutdown_tx, _) = broadcast::channel(1);
120 let name = config.name.clone();
121 let algorithm = config
122 .spawner
123 .algorithm_name()
124 .to_string();
125
126 let liquidity_scope = config.liquidity_scope;
128 let exclude_protocols = config.exclude_protocols.clone();
129 let fallback_fee_tiers = config.fallback_fee_tiers.clone();
130 let params = SpawnWorkersParams {
131 algorithm: algorithm.clone(),
132 pool_name: name.clone(),
133 num_workers: config.num_workers,
134 algorithm_config: config.algorithm_config,
135 task_rx,
136 market_data,
137 derived_data,
138 event_rx,
139 derived_event_rx,
140 shutdown_tx: shutdown_tx.clone(),
141 liquidity_scope,
142 exclude_protocols,
143 fallback_fee_tiers,
144 };
145 let workers = config.spawner.spawn(params)?;
146
147 info!(
148 name = %name,
149 algorithm = %algorithm,
150 num_workers = workers.len(),
151 "worker pool spawned"
152 );
153
154 Ok(Self { name, algorithm, workers, shutdown_tx })
155 }
156
157 pub fn name(&self) -> &str {
159 &self.name
160 }
161
162 pub fn algorithm(&self) -> &str {
164 &self.algorithm
165 }
166
167 pub fn num_workers(&self) -> usize {
169 self.workers.len()
170 }
171
172 pub fn shutdown(self) {
174 info!(name = %self.name, "shutting down worker pool");
175
176 let _ = self.shutdown_tx.send(());
178
179 for (i, handle) in self.workers.into_iter().enumerate() {
181 if let Err(e) = handle.join() {
182 error!(
183 name = %self.name,
184 worker_id = i,
185 "worker thread panicked: {:?}",
186 e
187 );
188 }
189 }
190
191 info!(name = %self.name, "worker pool shut down");
192 }
193}
194
195#[must_use = "a builder does nothing until .build() is called"]
208pub struct WorkerPoolBuilder {
209 config: WorkerPoolConfig,
210}
211
212impl WorkerPoolBuilder {
213 pub fn new() -> Self {
215 Self { config: WorkerPoolConfig::default() }
216 }
217
218 #[cfg(test)]
220 pub(crate) fn serves_custom_algorithm(&self) -> bool {
221 self.config.spawner.is_custom()
222 }
223
224 pub fn name(mut self, name: impl Into<String>) -> Self {
226 self.config.name = name.into();
227 self
228 }
229
230 pub fn algorithm(mut self, algorithm: impl Into<String>) -> Self {
235 self.config.spawner = AlgorithmSpawner::Registry { algorithm: algorithm.into() };
236 self
237 }
238
239 pub fn with_algorithm<A, F>(mut self, name: impl Into<String>, factory: F) -> Self
251 where
252 A: crate::algorithm::Algorithm + 'static,
253 A::GraphManager: MarketEventHandler + EdgeWeightUpdaterWithDerived + 'static,
254 F: Fn(AlgorithmConfig) -> A + Clone + Send + Sync + 'static,
255 {
256 let name = name.into();
257 let spawner =
258 Box::new(move |params: SpawnWorkersParams| spawn_workers_generic(params, &factory));
259 self.config.spawner = AlgorithmSpawner::Custom { algorithm: name, spawner };
260 self
261 }
262
263 pub fn algorithm_config(mut self, config: AlgorithmConfig) -> Self {
265 self.config.algorithm_config = config;
266 self
267 }
268
269 pub fn num_workers(mut self, n: usize) -> Self {
271 self.config.num_workers = n;
272 self
273 }
274
275 pub fn task_queue_capacity(mut self, capacity: usize) -> Self {
277 self.config.task_queue_capacity = capacity;
278 self
279 }
280
281 pub fn liquidity_scope(mut self, scope: LiquidityScope) -> Self {
283 self.config.liquidity_scope = scope;
284 self
285 }
286
287 pub fn exclude_protocols(mut self, exclude_protocols: Vec<String>) -> Self {
290 self.config.exclude_protocols = exclude_protocols;
291 self
292 }
293
294 pub fn fallback_fee_tiers(mut self, fallback_fee_tiers: SharedFeeTiers) -> Self {
296 self.config.fallback_fee_tiers = fallback_fee_tiers;
297 self
298 }
299
300 pub fn build(
309 self,
310 market_data: MarketData,
311 derived_data: SharedDerivedDataRef,
312 event_rx: broadcast::Receiver<MarketEvent>,
313 derived_event_rx: broadcast::Receiver<DerivedDataEvent>,
314 ) -> Result<(WorkerPool, TaskQueueHandle), UnknownAlgorithmError> {
315 let task_queue =
317 TaskQueue::new(TaskQueueConfig { capacity: self.config.task_queue_capacity });
318 let (task_handle, task_rx) = task_queue.split();
319
320 let pool = WorkerPool::spawn(
322 self.config,
323 task_rx,
324 market_data,
325 derived_data,
326 event_rx,
327 derived_event_rx,
328 )?;
329
330 Ok((pool, task_handle))
331 }
332}
333
334impl Default for WorkerPoolBuilder {
335 fn default() -> Self {
336 Self::new()
337 }
338}