Skip to main content

simd_brotli/enc/
worker_pool.rs

1#![cfg(feature = "std")]
2
3use crate::alloc::{Allocator, SliceWrapper};
4use core::mem;
5use std;
6// in-place thread create
7use std::sync::RwLock;
8use std::sync::{Arc, Condvar, Mutex};
9
10use crate::enc::backward_references::UnionHasher;
11use crate::enc::fixed_queue::{FixedQueue, MAX_THREADS};
12use crate::enc::threading::{
13    BatchSpawnableLite, BrotliEncoderThreadError, CompressMulti, CompressionThreadResult,
14    InternalOwned, InternalSendAlloc, Joinable, Owned, SendAlloc,
15};
16use crate::enc::{BrotliAlloc, BrotliEncoderParams};
17
18struct JobReply<T: Send + 'static> {
19    result: T,
20    work_id: u64,
21}
22
23struct JobRequest<
24    ReturnValue: Send + 'static,
25    ExtraInput: Send + 'static,
26    Alloc: BrotliAlloc + Send + 'static,
27    U: Send + 'static + Sync,
28> {
29    func: fn(ExtraInput, usize, usize, &U, Alloc) -> ReturnValue,
30    extra_input: ExtraInput,
31    index: usize,
32    thread_size: usize,
33    data: Arc<RwLock<U>>,
34    alloc: Alloc,
35    work_id: u64,
36}
37
38struct WorkQueue<
39    ReturnValue: Send + 'static,
40    ExtraInput: Send + 'static,
41    Alloc: BrotliAlloc + Send + 'static,
42    U: Send + 'static + Sync,
43> {
44    jobs: FixedQueue<JobRequest<ReturnValue, ExtraInput, Alloc, U>>,
45    results: FixedQueue<JobReply<ReturnValue>>,
46    shutdown: bool,
47    immediate_shutdown: bool,
48    num_in_progress: usize,
49    cur_work_id: u64,
50}
51impl<
52    ReturnValue: Send + 'static,
53    ExtraInput: Send + 'static,
54    Alloc: BrotliAlloc + Send + 'static,
55    U: Send + 'static + Sync,
56> Default for WorkQueue<ReturnValue, ExtraInput, Alloc, U>
57{
58    fn default() -> Self {
59        WorkQueue {
60            jobs: FixedQueue::default(),
61            results: FixedQueue::default(),
62            num_in_progress: 0,
63            immediate_shutdown: false,
64            shutdown: false,
65            cur_work_id: 0,
66        }
67    }
68}
69
70pub struct GuardedQueue<
71    ReturnValue: Send + 'static,
72    ExtraInput: Send + 'static,
73    Alloc: BrotliAlloc + Send + 'static,
74    U: Send + 'static + Sync,
75>(Arc<(Mutex<WorkQueue<ReturnValue, ExtraInput, Alloc, U>>, Condvar)>);
76pub struct WorkerPool<
77    ReturnValue: Send + 'static,
78    ExtraInput: Send + 'static,
79    Alloc: BrotliAlloc + Send + 'static,
80    U: Send + 'static + Sync,
81> {
82    queue: GuardedQueue<ReturnValue, ExtraInput, Alloc, U>,
83    join: [Option<std::thread::JoinHandle<()>>; MAX_THREADS],
84}
85
86impl<
87    ReturnValue: Send + 'static,
88    ExtraInput: Send + 'static,
89    Alloc: BrotliAlloc + Send + 'static,
90    U: Send + 'static + Sync,
91> Drop for WorkerPool<ReturnValue, ExtraInput, Alloc, U>
92{
93    fn drop(&mut self) {
94        {
95            let (lock, cvar) = &*self.queue.0;
96            let mut local_queue = lock.lock().unwrap();
97            local_queue.immediate_shutdown = true;
98            cvar.notify_all();
99        }
100        for thread_handle in self.join.iter_mut() {
101            if let Some(th) = thread_handle.take() {
102                th.join().unwrap();
103            }
104        }
105    }
106}
107impl<
108    ReturnValue: Send + 'static,
109    ExtraInput: Send + 'static,
110    Alloc: BrotliAlloc + Send + 'static,
111    U: Send + 'static + Sync,
112> WorkerPool<ReturnValue, ExtraInput, Alloc, U>
113{
114    fn do_work(queue: Arc<(Mutex<WorkQueue<ReturnValue, ExtraInput, Alloc, U>>, Condvar)>) {
115        loop {
116            let ret;
117            {
118                // need to drop possible job before the final lock is taken,
119                // so refcount of possible_job Arc is 0 by the time the job is delivered
120                // to the caller. We basically need a barrier (the lock) to happen
121                // after the destructor that decrefs possible_job
122                let possible_job;
123                {
124                    let (lock, cvar) = &*queue;
125                    let mut local_queue = lock.lock().unwrap();
126                    if local_queue.immediate_shutdown {
127                        break;
128                    }
129                    possible_job = match local_queue.jobs.pop() {
130                        Some(res) => {
131                            cvar.notify_all();
132                            local_queue.num_in_progress += 1;
133                            res
134                        }
135                        _ => {
136                            if local_queue.shutdown {
137                                break;
138                            } else {
139                                let _lock = cvar.wait(local_queue); // unlock immediately, unfortunately
140                                continue;
141                            }
142                        }
143                    };
144                }
145                ret = match possible_job.data.read() {
146                    Ok(job_data) => JobReply {
147                        result: (possible_job.func)(
148                            possible_job.extra_input,
149                            possible_job.index,
150                            possible_job.thread_size,
151                            &*job_data,
152                            possible_job.alloc,
153                        ),
154                        work_id: possible_job.work_id,
155                    },
156                    _ => {
157                        break; // poisoned lock
158                    }
159                };
160            }
161            {
162                let (lock, cvar) = &*queue;
163                let mut local_queue = lock.lock().unwrap();
164                local_queue.num_in_progress -= 1;
165                local_queue.results.push(ret).unwrap();
166                cvar.notify_all();
167            }
168        }
169    }
170    fn _push_job(&mut self, job: JobRequest<ReturnValue, ExtraInput, Alloc, U>) {
171        let (lock, cvar) = &*self.queue.0;
172        let mut local_queue = lock.lock().unwrap();
173        loop {
174            if local_queue.jobs.size() + local_queue.num_in_progress + local_queue.results.size()
175                < MAX_THREADS
176            {
177                local_queue.jobs.push(job).unwrap();
178                cvar.notify_all();
179                break;
180            }
181            local_queue = cvar.wait(local_queue).unwrap();
182        }
183    }
184    fn _try_push_job(
185        &mut self,
186        job: JobRequest<ReturnValue, ExtraInput, Alloc, U>,
187    ) -> Result<(), JobRequest<ReturnValue, ExtraInput, Alloc, U>> {
188        let (lock, cvar) = &*self.queue.0;
189        let mut local_queue = lock.lock().unwrap();
190        if local_queue.jobs.size() + local_queue.num_in_progress + local_queue.results.size()
191            < MAX_THREADS
192        {
193            local_queue.jobs.push(job).unwrap();
194            cvar.notify_all();
195            Ok(())
196        } else {
197            Err(job)
198        }
199    }
200    fn start(
201        queue: Arc<(Mutex<WorkQueue<ReturnValue, ExtraInput, Alloc, U>>, Condvar)>,
202    ) -> std::thread::JoinHandle<()> {
203        std::thread::spawn(move || Self::do_work(queue))
204    }
205    pub fn new(num_threads: usize) -> Self {
206        let queue = Arc::new((Mutex::new(WorkQueue::default()), Condvar::new()));
207        WorkerPool {
208            queue: GuardedQueue(queue.clone()),
209            join: [
210                Some(Self::start(queue.clone())),
211                if 1 < num_threads {
212                    Some(Self::start(queue.clone()))
213                } else {
214                    None
215                },
216                if 2 < num_threads {
217                    Some(Self::start(queue.clone()))
218                } else {
219                    None
220                },
221                if 3 < num_threads {
222                    Some(Self::start(queue.clone()))
223                } else {
224                    None
225                },
226                if 4 < num_threads {
227                    Some(Self::start(queue.clone()))
228                } else {
229                    None
230                },
231                if 5 < num_threads {
232                    Some(Self::start(queue.clone()))
233                } else {
234                    None
235                },
236                if 6 < num_threads {
237                    Some(Self::start(queue.clone()))
238                } else {
239                    None
240                },
241                if 7 < num_threads {
242                    Some(Self::start(queue.clone()))
243                } else {
244                    None
245                },
246                if 8 < num_threads {
247                    Some(Self::start(queue.clone()))
248                } else {
249                    None
250                },
251                if 9 < num_threads {
252                    Some(Self::start(queue.clone()))
253                } else {
254                    None
255                },
256                if 10 < num_threads {
257                    Some(Self::start(queue.clone()))
258                } else {
259                    None
260                },
261                if 11 < num_threads {
262                    Some(Self::start(queue.clone()))
263                } else {
264                    None
265                },
266                if 12 < num_threads {
267                    Some(Self::start(queue.clone()))
268                } else {
269                    None
270                },
271                if 13 < num_threads {
272                    Some(Self::start(queue.clone()))
273                } else {
274                    None
275                },
276                if 14 < num_threads {
277                    Some(Self::start(queue.clone()))
278                } else {
279                    None
280                },
281                if 15 < num_threads {
282                    Some(Self::start(queue.clone()))
283                } else {
284                    None
285                },
286            ],
287        }
288    }
289}
290
291pub fn new_work_pool<
292    Alloc: BrotliAlloc + Send + 'static,
293    SliceW: SliceWrapper<u8> + Send + 'static + Sync,
294>(
295    num_threads: usize,
296) -> WorkerPool<
297    CompressionThreadResult<Alloc>,
298    UnionHasher<Alloc>,
299    Alloc,
300    (SliceW, BrotliEncoderParams),
301>
302where
303    <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
304    <Alloc as Allocator<u16>>::AllocatedMemory: Send + Sync,
305    <Alloc as Allocator<u32>>::AllocatedMemory: Send + Sync,
306{
307    WorkerPool::new(num_threads)
308}
309
310pub struct WorkerJoinable<
311    ReturnValue: Send + 'static,
312    ExtraInput: Send + 'static,
313    Alloc: BrotliAlloc + Send + 'static,
314    U: Send + 'static + Sync,
315> {
316    queue: GuardedQueue<ReturnValue, ExtraInput, Alloc, U>,
317    work_id: u64,
318}
319impl<
320    ReturnValue: Send + 'static,
321    ExtraInput: Send + 'static,
322    Alloc: BrotliAlloc + Send + 'static,
323    U: Send + 'static + Sync,
324> Joinable<ReturnValue, BrotliEncoderThreadError>
325    for WorkerJoinable<ReturnValue, ExtraInput, Alloc, U>
326{
327    fn join(self) -> Result<ReturnValue, BrotliEncoderThreadError> {
328        let (lock, cvar) = &*self.queue.0;
329        let mut local_queue = lock.lock().unwrap();
330        loop {
331            match local_queue
332                .results
333                .remove(|data: &Option<JobReply<ReturnValue>>| {
334                    if let Some(ref item) = *data {
335                        item.work_id == self.work_id
336                    } else {
337                        false
338                    }
339                }) {
340                Some(matched) => return Ok(matched.result),
341                None => local_queue = cvar.wait(local_queue).unwrap(),
342            };
343        }
344    }
345}
346
347impl<
348    ReturnValue: Send + 'static,
349    ExtraInput: Send + 'static,
350    Alloc: BrotliAlloc + Send + 'static,
351    U: Send + 'static + Sync,
352> BatchSpawnableLite<ReturnValue, ExtraInput, Alloc, U>
353    for WorkerPool<ReturnValue, ExtraInput, Alloc, U>
354where
355    <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
356    <Alloc as Allocator<u16>>::AllocatedMemory: Send + Sync,
357    <Alloc as Allocator<u32>>::AllocatedMemory: Send + Sync,
358{
359    type FinalJoinHandle = Arc<RwLock<U>>;
360    type JoinHandle = WorkerJoinable<ReturnValue, ExtraInput, Alloc, U>;
361
362    fn make_spawner(&mut self, input: &mut Owned<U>) -> Self::FinalJoinHandle {
363        std::sync::Arc::<RwLock<U>>::new(RwLock::new(
364            mem::replace(input, Owned(InternalOwned::Borrowed)).unwrap(),
365        ))
366    }
367    fn spawn(
368        &mut self,
369        locked_input: &mut Self::FinalJoinHandle,
370        work: &mut SendAlloc<ReturnValue, ExtraInput, Alloc, Self::JoinHandle>,
371        index: usize,
372        num_threads: usize,
373        f: fn(ExtraInput, usize, usize, &U, Alloc) -> ReturnValue,
374    ) {
375        assert!(num_threads <= MAX_THREADS);
376        let (lock, cvar) = &*self.queue.0;
377        let mut local_queue = lock.lock().unwrap();
378        loop {
379            if local_queue.jobs.size() + local_queue.num_in_progress + local_queue.results.size()
380                <= MAX_THREADS
381            {
382                let work_id = local_queue.cur_work_id;
383                local_queue.cur_work_id += 1;
384                let (local_alloc, local_extra) = work.replace_with_default();
385                local_queue
386                    .jobs
387                    .push(JobRequest {
388                        func: f,
389                        extra_input: local_extra,
390                        index,
391                        thread_size: num_threads,
392                        data: locked_input.clone(),
393                        alloc: local_alloc,
394                        work_id,
395                    })
396                    .unwrap();
397                *work = SendAlloc(InternalSendAlloc::Join(WorkerJoinable {
398                    queue: GuardedQueue(self.queue.0.clone()),
399                    work_id,
400                }));
401                cvar.notify_all();
402                break;
403            } else {
404                local_queue = cvar.wait(local_queue).unwrap(); // hope room frees up
405            }
406        }
407    }
408}
409
410pub fn compress_worker_pool<
411    Alloc: BrotliAlloc + Send + 'static,
412    SliceW: SliceWrapper<u8> + Send + 'static + Sync,
413>(
414    params: &BrotliEncoderParams,
415    owned_input: &mut Owned<SliceW>,
416    output: &mut [u8],
417    alloc_per_thread: &mut [SendAlloc<
418        CompressionThreadResult<Alloc>,
419        UnionHasher<Alloc>,
420        Alloc,
421        <WorkerPool<
422            CompressionThreadResult<Alloc>,
423            UnionHasher<Alloc>,
424            Alloc,
425            (SliceW, BrotliEncoderParams),
426        > as BatchSpawnableLite<
427            CompressionThreadResult<Alloc>,
428            UnionHasher<Alloc>,
429            Alloc,
430            (SliceW, BrotliEncoderParams),
431        >>::JoinHandle,
432    >],
433    work_pool: &mut WorkerPool<
434        CompressionThreadResult<Alloc>,
435        UnionHasher<Alloc>,
436        Alloc,
437        (SliceW, BrotliEncoderParams),
438    >,
439) -> Result<usize, BrotliEncoderThreadError>
440where
441    <Alloc as Allocator<u8>>::AllocatedMemory: Send,
442    <Alloc as Allocator<u16>>::AllocatedMemory: Send + Sync,
443    <Alloc as Allocator<u32>>::AllocatedMemory: Send + Sync,
444{
445    CompressMulti(params, owned_input, output, alloc_per_thread, work_pool)
446}
447
448// out of place thread create