lzma_rust2/enc/
lzma2_writer_mt.rs1use std::{
2 io::{self, Write},
3 sync::{
4 Arc, Mutex,
5 atomic::{AtomicBool, AtomicU32, Ordering},
6 mpsc::SyncSender,
7 },
8};
9
10use super::Lzma2Writer;
11use crate::{
12 AutoFinish, AutoFinisher, ByteWriter, Lzma2Options, error_invalid_input, set_error,
13 work_pool::{WorkPool, WorkPoolConfig},
14 work_queue::WorkerHandle,
15};
16
17#[derive(Debug, Clone)]
19struct WorkUnit {
20 data: Vec<u8>,
21 options: Lzma2Options,
22}
23
24pub struct Lzma2WriterMt<W: Write> {
26 inner: W,
27 options: Lzma2Options,
28 chunk_size: usize,
29 current_work_unit: Vec<u8>,
30 work_pool: WorkPool<WorkUnit, Vec<u8>>,
31}
32
33impl<W: Write> Lzma2WriterMt<W> {
34 pub fn new(inner: W, options: Lzma2Options, num_workers: u32) -> crate::Result<Self> {
42 let chunk_size = match options.chunk_size {
43 None => return Err(error_invalid_input("chunk size must be set")),
44 Some(chunk_size) => chunk_size.get().max(options.lzma_options.dict_size as u64),
45 };
46
47 let chunk_size = usize::try_from(chunk_size)
48 .map_err(|_| error_invalid_input("chunk size bigger than usize"))?;
49
50 let num_work = u64::MAX;
52
53 Ok(Self {
54 inner,
55 options,
56 chunk_size,
57 current_work_unit: Vec::with_capacity(chunk_size),
58 work_pool: WorkPool::new(
59 WorkPoolConfig::new(num_workers, num_work),
60 worker_thread_logic,
61 ),
62 })
63 }
64
65 fn send_work_unit(&mut self) -> io::Result<()> {
67 if self.current_work_unit.is_empty() {
68 return Ok(());
69 }
70
71 self.drain_available_results()?;
72
73 let work_data = core::mem::take(&mut self.current_work_unit);
74 let mut single_chunk_options = self.options.clone();
75 single_chunk_options.chunk_size = None;
76 single_chunk_options.lzma_options.preset_dict = None;
77
78 let mut work_data_opt = Some(work_data);
79
80 self.work_pool.dispatch_next_work(&mut |_seq| {
81 let data = work_data_opt.take().ok_or_else(|| {
82 io::Error::new(io::ErrorKind::InvalidInput, "work already provided")
83 })?;
84 Ok(WorkUnit {
85 data,
86 options: single_chunk_options.clone(),
87 })
88 })?;
89
90 self.drain_available_results()?;
91
92 Ok(())
93 }
94
95 fn drain_available_results(&mut self) -> io::Result<()> {
97 while let Some(compressed_data) = self.work_pool.try_get_result()? {
98 self.inner.write_all(&compressed_data)?;
99 }
100 Ok(())
101 }
102
103 pub fn auto_finish(self) -> AutoFinisher<Self> {
105 AutoFinisher(Some(self))
106 }
107
108 pub fn into_inner(self) -> W {
110 self.inner
111 }
112
113 pub fn finish(mut self) -> io::Result<W> {
115 if !self.current_work_unit.is_empty() {
116 self.send_work_unit()?;
117 }
118
119 if self.work_pool.next_index_to_dispatch() == 0 {
121 self.inner.write_u8(0x00)?;
122 self.inner.flush()?;
123
124 return Ok(self.inner);
125 }
126
127 self.work_pool.finish();
129
130 while let Some(compressed_data) = self.work_pool.get_result(|_| {
132 Err(io::Error::new(
133 io::ErrorKind::InvalidInput,
134 "no more work to dispatch",
135 ))
136 })? {
137 self.inner.write_all(&compressed_data)?;
138 }
139
140 self.inner.write_u8(0x00)?;
141 self.inner.flush()?;
142
143 Ok(self.inner)
144 }
145}
146
147fn worker_thread_logic(
149 worker_handle: WorkerHandle<(u64, WorkUnit)>,
150 result_tx: SyncSender<(u64, Vec<u8>)>,
151 shutdown_flag: Arc<AtomicBool>,
152 error_store: Arc<Mutex<Option<io::Error>>>,
153 active_workers: Arc<AtomicU32>,
154) {
155 while !shutdown_flag.load(Ordering::Acquire) {
156 let (index, work_unit) = match worker_handle.steal() {
157 Some(work) => {
158 active_workers.fetch_add(1, Ordering::Release);
159 work
160 }
161 None => {
162 break;
164 }
165 };
166
167 let mut compressed_buffer = Vec::new();
168
169 let mut writer = Lzma2Writer::new(&mut compressed_buffer, work_unit.options);
170
171 let result = match writer.write_all(&work_unit.data) {
172 Ok(_) => match writer.flush() {
173 Ok(_) => compressed_buffer,
174 Err(error) => {
175 active_workers.fetch_sub(1, Ordering::Release);
176 set_error(error, &error_store, &shutdown_flag);
177 return;
178 }
179 },
180 Err(error) => {
181 active_workers.fetch_sub(1, Ordering::Release);
182 set_error(error, &error_store, &shutdown_flag);
183 return;
184 }
185 };
186
187 if result_tx.send((index, result)).is_err() {
188 active_workers.fetch_sub(1, Ordering::Release);
189 return;
190 }
191
192 active_workers.fetch_sub(1, Ordering::Release);
193 }
194}
195
196impl<W: Write> Write for Lzma2WriterMt<W> {
197 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
198 if buf.is_empty() {
199 return Ok(0);
200 }
201
202 let mut total_written = 0;
203 let mut remaining_buf = buf;
204
205 while !remaining_buf.is_empty() {
206 let chunk_remaining = self.chunk_size.saturating_sub(self.current_work_unit.len());
207 let to_write = remaining_buf.len().min(chunk_remaining);
208
209 if to_write > 0 {
210 self.current_work_unit
211 .extend_from_slice(&remaining_buf[..to_write]);
212 total_written += to_write;
213 remaining_buf = &remaining_buf[to_write..];
214 }
215
216 if self.current_work_unit.len() >= self.chunk_size {
217 self.send_work_unit()?;
218 }
219
220 self.drain_available_results()?;
221 }
222
223 Ok(total_written)
224 }
225
226 fn flush(&mut self) -> io::Result<()> {
227 if !self.current_work_unit.is_empty() {
228 self.send_work_unit()?;
229 }
230
231 while let Some(compressed_data) = self.work_pool.try_get_result()? {
233 self.inner.write_all(&compressed_data)?;
234 }
235
236 self.inner.flush()
237 }
238}
239
240impl<W: Write> AutoFinish for Lzma2WriterMt<W> {
241 fn finish_ignore_error(self) {
242 let _ = self.finish();
243 }
244}