Skip to main content

opendal_core/raw/oio/write/
block_write.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19
20use futures::Future;
21use futures::FutureExt;
22use futures::TryFutureExt;
23use futures::select;
24use uuid::Uuid;
25
26use crate::raw::*;
27use crate::*;
28
29/// BlockWrite is used to implement [`oio::Write`] based on block
30/// uploads. By implementing BlockWrite, services don't need to
31/// care about the details of uploading blocks.
32///
33/// # Architecture
34///
35/// The architecture after adopting [`BlockWrite`]:
36///
37/// - Services impl `BlockWrite`
38/// - `BlockWriter` impl `Write`
39/// - Expose `BlockWriter` as `Service::Writer`
40///
41/// # Notes
42///
43/// `BlockWrite` has an oneshot optimization when `write` has been called only once:
44///
45/// ```no_build
46/// w.write(bs).await?;
47/// w.close().await?;
48/// ```
49///
50/// We will use `write_once` instead of starting a new block upload.
51///
52/// # Requirements
53///
54/// Services that implement `BlockWrite` must fulfill the following requirements:
55///
56/// - Must be a http service that could accept `AsyncBody`.
57/// - Don't need initialization before writing.
58/// - Block ID is generated by caller `BlockWrite` instead of services.
59/// - Complete block by an ordered block id list.
60pub trait BlockWrite: Send + Sync + Unpin + 'static {
61    /// `write_once` writes all data to storage in a single operation.
62    ///
63    /// BlockWriter will call this API when:
64    ///
65    /// - The buffer contains all data, so `BlockWriter` can upload it in one
66    ///   operation, for example, in a single network request.
67    fn write_once(
68        &self,
69        size: u64,
70        body: Buffer,
71    ) -> impl Future<Output = Result<Metadata>> + MaybeSend;
72
73    /// write_block will write a block of the data.
74    ///
75    /// BlockWriter will call this API and stores the result in
76    /// order.
77    ///
78    /// - block_id is the id of the block.
79    fn write_block(
80        &self,
81        block_id: Uuid,
82        size: u64,
83        body: Buffer,
84    ) -> impl Future<Output = Result<()>> + MaybeSend;
85
86    /// complete_block will complete the block upload to build the final
87    /// file.
88    fn complete_block(
89        &self,
90        block_ids: Vec<Uuid>,
91    ) -> impl Future<Output = Result<Metadata>> + MaybeSend;
92
93    /// abort_block will cancel the block upload and purge all data.
94    fn abort_block(&self, block_ids: Vec<Uuid>) -> impl Future<Output = Result<()>> + MaybeSend;
95}
96
97struct WriteInput<W: BlockWrite> {
98    w: Arc<W>,
99    executor: Executor,
100    block_id: Uuid,
101    bytes: Buffer,
102}
103
104/// BlockWriter will implement [`oio::Write`] based on block
105/// uploads.
106pub struct BlockWriter<W: BlockWrite> {
107    w: Arc<W>,
108    executor: Executor,
109
110    started: bool,
111    block_ids: Vec<Uuid>,
112    cache: Option<Buffer>,
113    tasks: ConcurrentTasks<WriteInput<W>, Uuid>,
114}
115
116impl<W: BlockWrite> BlockWriter<W> {
117    /// Create a new BlockWriter.
118    pub fn new(executor: impl Into<Executor>, inner: W, concurrent: usize) -> Self {
119        let executor = executor.into();
120
121        Self {
122            w: Arc::new(inner),
123            executor: executor.clone(),
124            started: false,
125            block_ids: Vec::new(),
126            cache: None,
127
128            tasks: ConcurrentTasks::new(executor, concurrent, 8192, |input| {
129                Box::pin(async move {
130                    let fut = input
131                        .w
132                        .write_block(
133                            input.block_id,
134                            input.bytes.len() as u64,
135                            input.bytes.clone(),
136                        )
137                        .map_ok(|_| input.block_id);
138                    match input.executor.timeout() {
139                        None => {
140                            let result = fut.await;
141                            (input, result)
142                        }
143                        Some(timeout) => {
144                            let result = select! {
145                                result = fut.fuse() => {
146                                    result
147                                }
148                                _ = timeout.fuse() => {
149                                      Err(Error::new(
150                                            ErrorKind::Unexpected, "write block timeout")
151                                                .with_context("block_id", input.block_id.to_string())
152                                                .set_temporary())
153                                }
154                            };
155                            (input, result)
156                        }
157                    }
158                })
159            }),
160        }
161    }
162
163    fn fill_cache(&mut self, bs: Buffer) -> usize {
164        let size = bs.len();
165        assert!(self.cache.is_none());
166        self.cache = Some(bs);
167        size
168    }
169}
170
171impl<W> oio::Write for BlockWriter<W>
172where
173    W: BlockWrite,
174{
175    async fn write(&mut self, bs: Buffer) -> Result<()> {
176        if !self.started && self.cache.is_none() {
177            self.fill_cache(bs);
178            return Ok(());
179        }
180
181        // The block upload process has been started.
182        self.started = true;
183
184        let bytes = self.cache.clone().expect("pending write must exist");
185        self.tasks
186            .execute(WriteInput {
187                w: self.w.clone(),
188                executor: self.executor.clone(),
189                block_id: Uuid::new_v4(),
190                bytes,
191            })
192            .await?;
193        self.cache = None;
194        self.fill_cache(bs);
195        Ok(())
196    }
197
198    async fn close(&mut self) -> Result<Metadata> {
199        if !self.started {
200            let (size, body) = match self.cache.clone() {
201                Some(cache) => (cache.len(), cache),
202                None => (0, Buffer::new()),
203            };
204
205            let meta = self.w.write_once(size as u64, body).await?;
206            self.cache = None;
207            return Ok(meta);
208        }
209
210        if let Some(cache) = self.cache.clone() {
211            self.tasks
212                .execute(WriteInput {
213                    w: self.w.clone(),
214                    executor: self.executor.clone(),
215                    block_id: Uuid::new_v4(),
216                    bytes: cache,
217                })
218                .await?;
219            self.cache = None;
220        }
221
222        loop {
223            let Some(result) = self.tasks.next().await.transpose()? else {
224                break;
225            };
226            self.block_ids.push(result);
227        }
228
229        let block_ids = self.block_ids.clone();
230        self.w.complete_block(block_ids).await
231    }
232
233    async fn abort(&mut self) -> Result<()> {
234        if !self.started {
235            return Ok(());
236        }
237
238        self.tasks.clear();
239        self.cache = None;
240        self.w.abort_block(self.block_ids.clone()).await?;
241        Ok(())
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use std::collections::HashMap;
248    use std::sync::Mutex;
249
250    use pretty_assertions::assert_eq;
251    use rand::{Rng, RngExt, rng};
252    use tokio::time::sleep;
253
254    use super::*;
255    use crate::raw::oio::Write;
256
257    struct TestWrite {
258        length: u64,
259        bytes: HashMap<Uuid, Buffer>,
260        content: Option<Buffer>,
261    }
262
263    impl TestWrite {
264        pub fn new() -> Arc<Mutex<Self>> {
265            let v = Self {
266                length: 0,
267                bytes: HashMap::new(),
268                content: None,
269            };
270
271            Arc::new(Mutex::new(v))
272        }
273    }
274
275    impl BlockWrite for Arc<Mutex<TestWrite>> {
276        async fn write_once(&self, size: u64, body: Buffer) -> Result<Metadata> {
277            sleep(Duration::from_nanos(50)).await;
278
279            if rng().random_bool(1.0 / 10.0) {
280                return Err(
281                    Error::new(ErrorKind::Unexpected, "I'm a crazy monkey!").set_temporary()
282                );
283            }
284
285            let mut this = self.lock().unwrap();
286            this.length = size;
287            this.content = Some(body);
288            Ok(MetadataBuilder::unknown().build())
289        }
290
291        async fn write_block(&self, block_id: Uuid, size: u64, body: Buffer) -> Result<()> {
292            // Add an async sleep here to enforce some pending.
293            sleep(Duration::from_millis(50)).await;
294
295            // We will have 10% percent rate for write part to fail.
296            if rng().random_bool(1.0 / 10.0) {
297                return Err(
298                    Error::new(ErrorKind::Unexpected, "I'm a crazy monkey!").set_temporary()
299                );
300            }
301
302            let mut this = self.lock().unwrap();
303            this.length += size;
304            this.bytes.insert(block_id, body);
305
306            Ok(())
307        }
308
309        async fn complete_block(&self, block_ids: Vec<Uuid>) -> Result<Metadata> {
310            let mut this = self.lock().unwrap();
311            let mut bs = Vec::new();
312            for id in block_ids {
313                bs.push(this.bytes[&id].clone());
314            }
315            this.content = Some(bs.into_iter().flatten().collect());
316
317            Ok(MetadataBuilder::unknown().build())
318        }
319
320        async fn abort_block(&self, _: Vec<Uuid>) -> Result<()> {
321            Ok(())
322        }
323    }
324
325    #[tokio::test]
326    async fn test_block_writer_with_concurrent_errors() {
327        let mut rng = rng();
328
329        let mut w = BlockWriter::new(Executor::default(), TestWrite::new(), 8);
330        let mut total_size = 0u64;
331        let mut expected_content = Vec::new();
332
333        for _ in 0..1000 {
334            let size = rng.random_range(1..1024);
335            total_size += size as u64;
336
337            let mut bs = vec![0; size];
338            rng.fill_bytes(&mut bs);
339
340            expected_content.extend_from_slice(&bs);
341
342            loop {
343                match w.write(bs.clone().into()).await {
344                    Ok(_) => break,
345                    Err(_) => continue,
346                }
347            }
348        }
349
350        loop {
351            match w.close().await {
352                Ok(_) => break,
353                Err(_) => continue,
354            }
355        }
356
357        let inner = w.w.lock().unwrap();
358
359        assert_eq!(total_size, inner.length, "length must be the same");
360        assert!(inner.content.is_some());
361        assert_eq!(
362            expected_content,
363            inner.content.clone().unwrap().to_bytes(),
364            "content must be the same"
365        );
366    }
367
368    #[tokio::test]
369    async fn test_block_writer_with_retry_when_write_once_error() {
370        let mut rng = rng();
371
372        for _ in 1..100 {
373            let mut w = BlockWriter::new(Executor::default(), TestWrite::new(), 8);
374
375            let size = rng.random_range(1..1024);
376            let mut bs = vec![0; size];
377            rng.fill_bytes(&mut bs);
378
379            loop {
380                match w.write(bs.clone().into()).await {
381                    Ok(_) => break,
382                    Err(_) => continue,
383                }
384            }
385
386            loop {
387                match w.close().await {
388                    Ok(_) => break,
389                    Err(_) => continue,
390                }
391            }
392
393            let inner = w.w.lock().unwrap();
394            assert_eq!(size as u64, inner.length, "length must be the same");
395            assert!(inner.content.is_some());
396            assert_eq!(
397                bs,
398                inner.content.clone().unwrap().to_bytes(),
399                "content must be the same"
400            );
401        }
402    }
403}