1use alloc::{Allocator, SliceWrapper, SliceWrapperMut};
2#[cfg(feature = "std")]
3use core::any;
4use core::marker::PhantomData;
5use core::mem;
6use core::ops::Range;
7#[cfg(feature = "std")]
8use std;
9
10use super::backward_references::{AnyHasher, BrotliEncoderParams, CloneWithAlloc, UnionHasher};
11use super::encode::{
12 hasher_setup, BrotliEncoderDestroyInstance, BrotliEncoderMaxCompressedSize,
13 BrotliEncoderOperation, SanitizeParams,
14};
15use super::BrotliAlloc;
16use crate::concat::{BroCatli, BroCatliResult};
17use crate::enc::combined_alloc::{alloc_default, allocate};
18use crate::enc::encode::BrotliEncoderStateStruct;
19
20pub type PoisonedThreadError = ();
21
22#[cfg(feature = "std")]
23pub type LowLevelThreadError = std::boxed::Box<dyn any::Any + Send + 'static>;
24#[cfg(not(feature = "std"))]
25pub type LowLevelThreadError = ();
26
27pub trait AnyBoxConstructor {
28 fn new(data: LowLevelThreadError) -> Self;
29}
30
31pub trait Joinable<T: Send + 'static, U: Send + 'static>: Sized {
32 fn join(self) -> Result<T, U>;
33}
34#[derive(Debug)]
35pub enum BrotliEncoderThreadError {
36 InsufficientOutputSpace,
37 ConcatenationDidNotProcessFullFile,
38 ConcatenationError(BroCatliResult),
39 ConcatenationFinalizationError(BroCatliResult),
40 OtherThreadPanic,
41 ThreadExecError(LowLevelThreadError),
42}
43
44impl AnyBoxConstructor for BrotliEncoderThreadError {
45 fn new(data: LowLevelThreadError) -> Self {
46 BrotliEncoderThreadError::ThreadExecError(data)
47 }
48}
49
50fn set_pending_error(
51 pending_error: &mut Option<BrotliEncoderThreadError>,
52 error: BrotliEncoderThreadError,
53) {
54 if pending_error.is_none() {
55 *pending_error = Some(error);
56 }
57}
58
59pub struct CompressedFileChunk<Alloc: BrotliAlloc + Send + 'static>
60where
61 <Alloc as Allocator<u8>>::AllocatedMemory: Send,
62{
63 data_backing: <Alloc as Allocator<u8>>::AllocatedMemory,
64 data_size: usize,
65}
66pub struct CompressionThreadResult<Alloc: BrotliAlloc + Send + 'static>
67where
68 <Alloc as Allocator<u8>>::AllocatedMemory: Send,
69{
70 compressed: Result<CompressedFileChunk<Alloc>, BrotliEncoderThreadError>,
71 alloc: Alloc,
72}
73pub enum InternalSendAlloc<
74 ReturnVal: Send + 'static,
75 ExtraInput: Send + 'static,
76 Alloc: BrotliAlloc + Send + 'static,
77 Join: Joinable<ReturnVal, BrotliEncoderThreadError>,
78> where
79 <Alloc as Allocator<u8>>::AllocatedMemory: Send,
80{
81 A(Alloc, ExtraInput),
82 Join(Join),
83 SpawningOrJoining(PhantomData<ReturnVal>),
84}
85impl<
86 ReturnVal: Send + 'static,
87 ExtraInput: Send + 'static,
88 Alloc: BrotliAlloc + Send + 'static,
89 Join: Joinable<ReturnVal, BrotliEncoderThreadError>,
90 > InternalSendAlloc<ReturnVal, ExtraInput, Alloc, Join>
91where
92 <Alloc as Allocator<u8>>::AllocatedMemory: Send,
93{
94 fn unwrap_input(&mut self) -> (&mut Alloc, &mut ExtraInput) {
95 match *self {
96 InternalSendAlloc::A(ref mut alloc, ref mut extra) => (alloc, extra),
97 _ => panic!("Bad state for allocator"),
98 }
99 }
100}
101
102pub struct SendAlloc<
103 ReturnValue: Send + 'static,
104 ExtraInput: Send + 'static,
105 Alloc: BrotliAlloc + Send + 'static,
106 Join: Joinable<ReturnValue, BrotliEncoderThreadError>,
107>(pub InternalSendAlloc<ReturnValue, ExtraInput, Alloc, Join>)
108where
110 <Alloc as Allocator<u8>>::AllocatedMemory: Send;
111
112impl<
113 ReturnValue: Send + 'static,
114 ExtraInput: Send + 'static,
115 Alloc: BrotliAlloc + Send + 'static,
116 Join: Joinable<ReturnValue, BrotliEncoderThreadError>,
117 > SendAlloc<ReturnValue, ExtraInput, Alloc, Join>
118where
119 <Alloc as Allocator<u8>>::AllocatedMemory: Send,
120{
121 pub fn new(alloc: Alloc, extra_input: ExtraInput) -> Self {
122 SendAlloc::<ReturnValue, ExtraInput, Alloc, Join>(InternalSendAlloc::A(alloc, extra_input))
123 }
124 pub fn unwrap_or(self, other: Alloc, other_extra: ExtraInput) -> (Alloc, ExtraInput) {
125 match self.0 {
126 InternalSendAlloc::A(alloc, extra_input) => (alloc, extra_input),
127 InternalSendAlloc::SpawningOrJoining(_) | InternalSendAlloc::Join(_) => {
128 (other, other_extra)
129 }
130 }
131 }
132 fn unwrap_view_mut(&mut self) -> (&mut Alloc, &mut ExtraInput) {
133 match self.0 {
134 InternalSendAlloc::A(ref mut alloc, ref mut extra_input) => (alloc, extra_input),
135 InternalSendAlloc::SpawningOrJoining(_) | InternalSendAlloc::Join(_) => {
136 panic!("Item permanently borrowed/leaked")
137 }
138 }
139 }
140 pub fn unwrap(self) -> (Alloc, ExtraInput) {
141 match self.0 {
142 InternalSendAlloc::A(alloc, extra_input) => (alloc, extra_input),
143 InternalSendAlloc::SpawningOrJoining(_) | InternalSendAlloc::Join(_) => {
144 panic!("Item permanently borrowed/leaked")
145 }
146 }
147 }
148 pub fn replace_with_default(&mut self) -> (Alloc, ExtraInput) {
149 match mem::replace(
150 &mut self.0,
151 InternalSendAlloc::SpawningOrJoining(PhantomData),
152 ) {
153 InternalSendAlloc::A(alloc, extra_input) => (alloc, extra_input),
154 InternalSendAlloc::SpawningOrJoining(_) | InternalSendAlloc::Join(_) => {
155 panic!("Item permanently borrowed/leaked")
156 }
157 }
158 }
159}
160
161pub enum InternalOwned<T> {
162 Item(T),
164 Borrowed,
165}
166
167pub struct Owned<T>(pub InternalOwned<T>); impl<T> Owned<T> {
169 pub fn new(data: T) -> Self {
170 Owned::<T>(InternalOwned::Item(data))
171 }
172 pub fn unwrap_or(self, other: T) -> T {
173 if let InternalOwned::Item(x) = self.0 {
174 x
175 } else {
176 other
177 }
178 }
179 pub fn unwrap(self) -> T {
180 if let InternalOwned::Item(x) = self.0 {
181 x
182 } else {
183 panic!("Item permanently borrowed")
184 }
185 }
186 pub fn view(&self) -> &T {
187 if let InternalOwned::Item(ref x) = self.0 {
188 x
189 } else {
190 panic!("Item permanently borrowed")
191 }
192 }
193}
194
195pub trait OwnedRetriever<U: Send + 'static> {
196 fn view<T, F: FnOnce(&U) -> T>(&self, f: F) -> Result<T, PoisonedThreadError>;
197 fn unwrap(self) -> Result<U, PoisonedThreadError>;
198}
199
200#[cfg(feature = "std")]
201impl<U: Send + 'static> OwnedRetriever<U> for std::sync::Arc<std::sync::RwLock<U>> {
202 fn view<T, F: FnOnce(&U) -> T>(&self, f: F) -> Result<T, PoisonedThreadError> {
203 match self.read() {
204 Ok(ref u) => Ok(f(u)),
205 Err(_) => Err(PoisonedThreadError::default()),
206 }
207 }
208 fn unwrap(self) -> Result<U, PoisonedThreadError> {
209 match std::sync::Arc::try_unwrap(self) {
210 Ok(rwlock) => match rwlock.into_inner() {
211 Ok(u) => Ok(u),
212 Err(_) => Err(PoisonedThreadError::default()),
213 },
214 Err(_) => Err(PoisonedThreadError::default()),
215 }
216 }
217}
218
219pub trait BatchSpawnable<
220 ReturnValue: Send + 'static,
221 ExtraInput: Send + 'static,
222 Alloc: BrotliAlloc + Send + 'static,
223 U: Send + 'static + Sync,
224> where
225 <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
226{
227 type JoinHandle: Joinable<ReturnValue, BrotliEncoderThreadError>;
228 type FinalJoinHandle: OwnedRetriever<U>;
229 fn make_spawner(&mut self, input: &mut Owned<U>) -> Self::FinalJoinHandle;
238 fn spawn<F: Fn(ExtraInput, usize, usize, &U, Alloc) -> ReturnValue + Send + 'static + Copy>(
239 &mut self,
240 handle: &mut Self::FinalJoinHandle,
241 alloc: &mut SendAlloc<ReturnValue, ExtraInput, Alloc, Self::JoinHandle>,
242 index: usize,
243 num_threads: usize,
244 f: F,
245 );
246}
247
248pub trait BatchSpawnableLite<
249 ReturnValue: Send + 'static,
250 ExtraInput: Send + 'static,
251 Alloc: BrotliAlloc + Send + 'static,
252 U: Send + 'static + Sync,
253> where
254 <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
255{
256 type JoinHandle: Joinable<ReturnValue, BrotliEncoderThreadError>;
257 type FinalJoinHandle: OwnedRetriever<U>;
258 fn make_spawner(&mut self, input: &mut Owned<U>) -> Self::FinalJoinHandle;
259 fn spawn(
260 &mut self,
261 handle: &mut Self::FinalJoinHandle,
262 alloc_per_thread: &mut SendAlloc<ReturnValue, ExtraInput, Alloc, Self::JoinHandle>,
263 index: usize,
264 num_threads: usize,
265 f: fn(ExtraInput, usize, usize, &U, Alloc) -> ReturnValue,
266 );
267}
268pub fn CompressMultiSlice<
287 Alloc: BrotliAlloc + Send + 'static,
288 Spawner: BatchSpawnableLite<
289 CompressionThreadResult<Alloc>,
290 UnionHasher<Alloc>,
291 Alloc,
292 (
293 <Alloc as Allocator<u8>>::AllocatedMemory,
294 BrotliEncoderParams,
295 ),
296 >,
297>(
298 params: &BrotliEncoderParams,
299 input_slice: &[u8],
300 output: &mut [u8],
301 alloc_per_thread: &mut [SendAlloc<
302 CompressionThreadResult<Alloc>,
303 UnionHasher<Alloc>,
304 Alloc,
305 Spawner::JoinHandle,
306 >],
307 thread_spawner: &mut Spawner,
308) -> Result<usize, BrotliEncoderThreadError>
309where
310 <Alloc as Allocator<u8>>::AllocatedMemory: Send + Sync,
311 <Alloc as Allocator<u16>>::AllocatedMemory: Send + Sync,
312 <Alloc as Allocator<u32>>::AllocatedMemory: Send + Sync,
313{
314 let input = if let InternalSendAlloc::A(ref mut alloc, ref _extra) = alloc_per_thread[0].0 {
315 let mut input = allocate::<u8, _>(alloc, input_slice.len());
316 input.slice_mut().clone_from_slice(input_slice);
317 input
318 } else {
319 alloc_default::<u8, Alloc>()
320 };
321 let mut owned_input = Owned::new(input);
322 let ret = CompressMulti(
323 params,
324 &mut owned_input,
325 output,
326 alloc_per_thread,
327 thread_spawner,
328 );
329 if let InternalSendAlloc::A(ref mut alloc, ref _extra) = alloc_per_thread[0].0 {
330 <Alloc as Allocator<u8>>::free_cell(alloc, owned_input.unwrap());
331 }
332 ret
333}
334
335fn get_range(thread_index: usize, num_threads: usize, file_size: usize) -> Range<usize> {
336 ((thread_index * file_size) / num_threads)..(((thread_index + 1) * file_size) / num_threads)
337}
338
339fn compress_part<Alloc: BrotliAlloc + Send + 'static, SliceW: SliceWrapper<u8>>(
340 hasher: UnionHasher<Alloc>,
341 thread_index: usize,
342 num_threads: usize,
343 input_and_params: &(SliceW, BrotliEncoderParams),
344 mut alloc: Alloc,
345) -> CompressionThreadResult<Alloc>
346where
347 <Alloc as Allocator<u8>>::AllocatedMemory: Send + 'static,
348{
349 let mut range = get_range(thread_index, num_threads, input_and_params.0.len());
350 let mut mem = allocate::<u8, _>(
351 &mut alloc,
352 BrotliEncoderMaxCompressedSize(range.end - range.start),
353 );
354 let mut state = BrotliEncoderStateStruct::new(alloc);
355 state.params = input_and_params.1.clone();
356 if thread_index != 0 {
357 state.params.catable = true; state.params.magic_number = false; }
360 state.params.appendable = true; if thread_index != 0 {
362 state.set_custom_dictionary_with_optional_precomputed_hasher(
363 range.start,
364 &input_and_params.0.slice()[..range.start],
365 hasher,
366 true,
367 );
368 }
369 let mut out_offset = 0usize;
370 let compression_result;
371 let mut available_out = mem.len();
372 loop {
373 let mut next_in_offset = 0usize;
374 let mut available_in = range.end - range.start;
375 let result = state.compress_stream(
376 BrotliEncoderOperation::BROTLI_OPERATION_FINISH,
377 &mut available_in,
378 &input_and_params.0.slice()[range.clone()],
379 &mut next_in_offset,
380 &mut available_out,
381 mem.slice_mut(),
382 &mut out_offset,
383 &mut None,
384 &mut |_a, _b, _c, _d| (),
385 );
386 let new_range = range.start + next_in_offset..range.end;
387 range = new_range;
388 if result {
389 compression_result = Ok(out_offset);
390 break;
391 } else if available_out == 0 {
392 compression_result = Err(BrotliEncoderThreadError::InsufficientOutputSpace); break;
394 }
395 }
396 BrotliEncoderDestroyInstance(&mut state);
397 match compression_result {
398 Ok(size) => CompressionThreadResult::<Alloc> {
399 compressed: Ok(CompressedFileChunk {
400 data_backing: mem,
401 data_size: size,
402 }),
403 alloc: state.m8,
404 },
405 Err(e) => {
406 <Alloc as Allocator<u8>>::free_cell(&mut state.m8, mem);
407 CompressionThreadResult::<Alloc> {
408 compressed: Err(e),
409 alloc: state.m8,
410 }
411 }
412 }
413}
414
415pub fn CompressMulti<
416 Alloc: BrotliAlloc + Send + 'static,
417 SliceW: SliceWrapper<u8> + Send + 'static + Sync,
418 Spawner: BatchSpawnableLite<
419 CompressionThreadResult<Alloc>,
420 UnionHasher<Alloc>,
421 Alloc,
422 (SliceW, BrotliEncoderParams),
423 >,
424>(
425 params: &BrotliEncoderParams,
426 owned_input: &mut Owned<SliceW>,
427 output: &mut [u8],
428 alloc_per_thread: &mut [SendAlloc<
429 CompressionThreadResult<Alloc>,
430 UnionHasher<Alloc>,
431 Alloc,
432 Spawner::JoinHandle,
433 >],
434 thread_spawner: &mut Spawner,
435) -> Result<usize, BrotliEncoderThreadError>
436where
437 <Alloc as Allocator<u8>>::AllocatedMemory: Send,
438 <Alloc as Allocator<u16>>::AllocatedMemory: Send,
439 <Alloc as Allocator<u32>>::AllocatedMemory: Send,
440{
441 let num_threads = alloc_per_thread.len();
442 let actually_owned_mem = mem::replace(owned_input, Owned(InternalOwned::Borrowed));
443 let mut owned_input_pair = Owned::new((actually_owned_mem.unwrap(), params.clone()));
444 let mut spawner_and_input = thread_spawner.make_spawner(&mut owned_input_pair);
446 if num_threads > 1 {
447 thread_spawner.spawn(
449 &mut spawner_and_input,
450 &mut alloc_per_thread[0],
451 0,
452 num_threads,
453 compress_part,
454 );
455 }
456 let mut compression_last_thread_result;
458 if num_threads > 1 && params.favor_cpu_efficiency {
459 let mut local_params = params.clone();
460 SanitizeParams(&mut local_params);
461 let mut hasher = UnionHasher::Uninit;
462 hasher_setup(
463 alloc_per_thread[num_threads - 1].0.unwrap_input().0,
464 &mut hasher,
465 &mut local_params,
466 None, &[],
468 0,
469 0,
470 false,
471 );
472 let mut setup_error = false;
473 for thread_index in 1..num_threads {
474 let res = spawner_and_input.view(|input_and_params: &(SliceW, BrotliEncoderParams)| {
475 let range = get_range(thread_index - 1, num_threads, input_and_params.0.len());
476 let overlap = hasher.StoreLookahead().wrapping_sub(1);
477 if range.end - range.start > overlap {
478 hasher.BulkStoreRange(
479 input_and_params.0.slice(),
480 usize::MAX,
481 if range.start > overlap {
482 range.start - overlap
483 } else {
484 0
485 },
486 range.end - overlap,
487 );
488 }
489 });
490 if let Err(_e) = res {
491 setup_error = true;
492 break;
493 }
494 if thread_index + 1 != num_threads {
495 {
496 let (alloc, out_hasher) = alloc_per_thread[thread_index].unwrap_view_mut();
497 *out_hasher = hasher.clone_with_alloc(alloc);
498 }
499 thread_spawner.spawn(
500 &mut spawner_and_input,
501 &mut alloc_per_thread[thread_index],
502 thread_index,
503 num_threads,
504 compress_part,
505 );
506 }
507 }
508 if setup_error {
509 let mut setup_result = Err(BrotliEncoderThreadError::OtherThreadPanic);
510 for thread in alloc_per_thread.iter_mut() {
511 match mem::replace(
512 &mut thread.0,
513 InternalSendAlloc::SpawningOrJoining(PhantomData),
514 ) {
515 InternalSendAlloc::Join(join) => match join.join() {
516 Ok(mut thread_result) => {
517 if let Ok(compressed_out) = thread_result.compressed {
518 <Alloc as Allocator<u8>>::free_cell(
519 &mut thread_result.alloc,
520 compressed_out.data_backing,
521 );
522 }
523 thread.0 =
524 InternalSendAlloc::A(thread_result.alloc, UnionHasher::Uninit);
525 }
526 Err(join_error) => setup_result = Err(join_error),
527 },
528 other => thread.0 = other,
529 }
530 }
531 if let Ok(retrieved_owned_input) = spawner_and_input.unwrap() {
532 *owned_input = Owned::new(retrieved_owned_input.0);
533 }
534 return setup_result;
535 }
536 let (alloc, _extra) = alloc_per_thread[num_threads - 1].replace_with_default();
537 compression_last_thread_result = spawner_and_input.view(move |input_and_params:&(SliceW, BrotliEncoderParams)| -> CompressionThreadResult<Alloc> {
538 compress_part(hasher,
539 num_threads - 1,
540 num_threads,
541 input_and_params,
542 alloc,
543 )
544 });
545 } else {
546 if num_threads > 1 {
547 for thread_index in 1..num_threads - 1 {
548 thread_spawner.spawn(
549 &mut spawner_and_input,
550 &mut alloc_per_thread[thread_index],
551 thread_index,
552 num_threads,
553 compress_part,
554 );
555 }
556 }
557 let (alloc, _extra) = alloc_per_thread[num_threads - 1].replace_with_default();
558 compression_last_thread_result = spawner_and_input.view(move |input_and_params:&(SliceW, BrotliEncoderParams)| -> CompressionThreadResult<Alloc> {
559 compress_part(UnionHasher::Uninit,
560 num_threads - 1,
561 num_threads,
562 input_and_params,
563 alloc,
564 )
565 });
566 }
567 let mut compression_result = Ok(0usize);
568 let mut pending_error = None;
569 let mut out_file_size = 0usize;
570 let mut bro_cat_li = BroCatli::new();
571 for (index, thread) in alloc_per_thread.iter_mut().enumerate() {
572 let cur_result = if index + 1 == num_threads {
573 match mem::replace(&mut compression_last_thread_result, Err(())) {
574 Ok(result) => Some(result),
575 Err(_err) => {
576 set_pending_error(
577 &mut pending_error,
578 BrotliEncoderThreadError::OtherThreadPanic,
579 );
580 None
581 }
582 }
583 } else {
584 match mem::replace(
585 &mut thread.0,
586 InternalSendAlloc::SpawningOrJoining(PhantomData),
587 ) {
588 InternalSendAlloc::A(_, _) | InternalSendAlloc::SpawningOrJoining(_) => {
589 panic!("Thread not properly spawned")
590 }
591 InternalSendAlloc::Join(join) => match join.join() {
592 Ok(result) => Some(result),
593 Err(err) => {
594 set_pending_error(&mut pending_error, err);
595 None
596 }
597 },
598 }
599 };
600 if let Some(mut cur_result) = cur_result {
601 match cur_result.compressed {
602 Ok(compressed_out) => {
603 if pending_error.is_none() {
604 bro_cat_li.new_brotli_file();
605 let mut in_offset = 0usize;
606 let cat_result = bro_cat_li.stream(
607 &compressed_out.data_backing.slice()[..compressed_out.data_size],
608 &mut in_offset,
609 output,
610 &mut out_file_size,
611 );
612 match cat_result {
613 BroCatliResult::Success | BroCatliResult::NeedsMoreInput => {
614 compression_result = Ok(out_file_size);
615 }
616 BroCatliResult::NeedsMoreOutput => {
617 set_pending_error(
618 &mut pending_error,
619 BrotliEncoderThreadError::InsufficientOutputSpace,
620 );
621 }
623 err => {
624 set_pending_error(
625 &mut pending_error,
626 BrotliEncoderThreadError::ConcatenationError(err),
627 );
628 }
630 }
631 }
632 <Alloc as Allocator<u8>>::free_cell(
633 &mut cur_result.alloc,
634 compressed_out.data_backing,
635 );
636 }
637 Err(e) => {
638 set_pending_error(&mut pending_error, e);
639 }
640 }
641 thread.0 = InternalSendAlloc::A(cur_result.alloc, UnionHasher::Uninit);
642 }
643 }
644 if let Some(error) = pending_error {
645 compression_result = Err(error);
646 }
647 if compression_result.is_ok() {
648 match bro_cat_li.finish(output, &mut out_file_size) {
649 BroCatliResult::Success => compression_result = Ok(out_file_size),
650 err => {
651 compression_result = Err(BrotliEncoderThreadError::ConcatenationFinalizationError(
652 err,
653 ))
654 }
655 }
656 }
657 if let Ok(retrieved_owned_input) = spawner_and_input.unwrap() {
658 *owned_input = Owned::new(retrieved_owned_input.0); } else if compression_result.is_ok() {
660 compression_result = Err(BrotliEncoderThreadError::OtherThreadPanic);
661 }
662 compression_result
663}
664
665mod test;