Skip to main content

hdfs_native/
file.rs

1use std::io::{self, SeekFrom};
2use std::pin::Pin;
3use std::sync::Arc;
4use std::task::{Context, Poll};
5use std::time::Duration;
6
7use bytes::{BufMut, Bytes, BytesMut};
8use futures::stream::BoxStream;
9use futures::{Stream, StreamExt, stream};
10use log::warn;
11use tokio::io::{AsyncRead, AsyncSeek, ReadBuf};
12use tokio::runtime::Handle;
13
14use crate::common::config::Configuration;
15use crate::ec::{EcSchema, resolve_ec_policy};
16use crate::hdfs::block_reader::get_block_stream;
17use crate::hdfs::block_writer::BlockWriter;
18use crate::hdfs::crypto::FileCryptoCodec;
19use crate::hdfs::protocol::{LeaseTracker, NamenodeProtocol};
20use crate::proto::hdfs;
21use crate::{HdfsError, Result};
22
23const COMPLETE_RETRY_DELAY_MS: u64 = 500;
24const COMPLETE_RETRIES: u32 = 5;
25
26fn io_error(error: HdfsError) -> io::Error {
27    io::Error::other(error)
28}
29
30struct PendingRead {
31    stream: BoxStream<'static, Result<Bytes>>,
32    end_position: usize,
33}
34
35pub struct FileReader {
36    protocol: Arc<NamenodeProtocol>,
37    located_blocks: hdfs::LocatedBlocksProto,
38    ec_schema: Option<EcSchema>,
39    position: usize,
40    config: Arc<Configuration>,
41    handle: Handle,
42    pending_read: Option<std::sync::Mutex<PendingRead>>,
43    crypto: Option<Arc<FileCryptoCodec>>,
44}
45
46impl FileReader {
47    pub(crate) fn new(
48        protocol: Arc<NamenodeProtocol>,
49        located_blocks: hdfs::LocatedBlocksProto,
50        ec_schema: Option<EcSchema>,
51        config: Arc<Configuration>,
52        handle: Handle,
53        crypto: Option<Arc<FileCryptoCodec>>,
54    ) -> Self {
55        Self {
56            protocol,
57            located_blocks,
58            ec_schema,
59            position: 0,
60            config,
61            handle,
62            pending_read: None,
63            crypto,
64        }
65    }
66
67    /// Returns the total size of the file
68    pub fn file_length(&self) -> usize {
69        self.located_blocks.file_length as usize
70    }
71
72    /// Returns the remaining bytes left based on the current cursor position.
73    pub fn remaining(&self) -> usize {
74        if self.position > self.file_length() {
75            0
76        } else {
77            self.file_length() - self.position
78        }
79    }
80
81    /// Sets the cursor to the position. Panics if the position is beyond the end of the file
82    pub fn set_position(&mut self, pos: usize) {
83        if pos > self.file_length() {
84            panic!("Cannot seek beyond the end of a file");
85        }
86        self.pending_read = None;
87        self.position = pos;
88    }
89
90    /// Returns the current cursor position in the file
91    pub fn tell(&self) -> usize {
92        self.position
93    }
94
95    /// Read up to `len` bytes into a new [Bytes] object, advancing the internal position in the file.
96    /// An empty [Bytes] object will be returned if the end of the file has been reached.
97    pub async fn read_bytes(&mut self, len: usize) -> Result<Bytes> {
98        self.pending_read = None;
99        if self.position >= self.file_length() {
100            Ok(Bytes::new())
101        } else {
102            let offset = self.position;
103            self.position = usize::min(self.position + len, self.file_length());
104            self.read_range(offset, self.position - offset).await
105        }
106    }
107
108    /// Read up to `buf.len()` bytes into the provided slice, advancing the internal position in the file.
109    /// Returns the number of bytes that were read, or 0 if the end of the file has been reached.
110    pub async fn read_into(&mut self, buf: &mut [u8]) -> Result<usize> {
111        self.pending_read = None;
112        if self.position >= self.file_length() {
113            Ok(0)
114        } else {
115            let offset = self.position;
116            self.position = usize::min(self.position + buf.len(), self.file_length());
117            let read_bytes = self.position - offset;
118            self.read_range_buf(&mut buf[..read_bytes], offset).await?;
119            Ok(read_bytes)
120        }
121    }
122
123    /// Read up to `len` bytes starting at `offset` into a new [Bytes] object. The returned buffer
124    /// could be smaller than `len` if `offset + len` extends beyond the end of the file.
125    ///
126    /// Panics if the requested range is outside of the file
127    pub async fn read_range(&self, offset: usize, len: usize) -> Result<Bytes> {
128        let mut stream = self.read_range_stream(offset, len);
129        let mut buf = BytesMut::with_capacity(len);
130        while let Some(bytes) = stream.next().await.transpose()? {
131            buf.put(bytes);
132        }
133        Ok(buf.freeze())
134    }
135
136    /// Read file data into an existing buffer
137    ///
138    /// Panics if the requested range is outside of the file
139    pub async fn read_range_buf(&self, mut buf: &mut [u8], offset: usize) -> Result<()> {
140        let mut stream = self.read_range_stream(offset, buf.len());
141        while let Some(bytes) = stream.next().await.transpose()? {
142            buf.put(bytes);
143        }
144
145        Ok(())
146    }
147
148    /// Return a stream of `Bytes` objects containing the content of the file
149    ///
150    /// Panics if the requested range is outside of the file
151    pub fn read_range_stream(
152        &self,
153        offset: usize,
154        len: usize,
155    ) -> impl Stream<Item = Result<Bytes>> + use<> {
156        if offset + len > self.file_length() {
157            panic!("Cannot read past end of the file");
158        }
159
160        let block_streams: Vec<BoxStream<Result<Bytes>>> = self
161            .located_blocks
162            .blocks
163            .iter()
164            .flat_map(move |block| {
165                let block_file_start = block.offset as usize;
166                let block_file_end = block_file_start + block.b.num_bytes() as usize;
167
168                if block_file_start < (offset + len) && block_file_end > offset {
169                    // We need to read this block
170                    let block_start = offset - usize::min(offset, block_file_start);
171                    let block_end = usize::min(offset + len, block_file_end) - block_file_start;
172                    Some(get_block_stream(
173                        Arc::clone(&self.protocol),
174                        block.clone(),
175                        block_start,
176                        block_end - block_start,
177                        self.ec_schema.clone(),
178                        Arc::clone(&self.config),
179                        self.handle.clone(),
180                        self.crypto.clone(),
181                    ))
182                } else {
183                    // No data is needed from this block
184                    None
185                }
186            })
187            .collect();
188
189        stream::iter(block_streams).flatten()
190    }
191}
192
193impl AsyncRead for FileReader {
194    fn poll_read(
195        mut self: Pin<&mut Self>,
196        cx: &mut Context<'_>,
197        buf: &mut ReadBuf<'_>,
198    ) -> Poll<io::Result<()>> {
199        if buf.remaining() == 0 {
200            return Poll::Ready(Ok(()));
201        }
202
203        let starting_len = buf.filled().len();
204
205        loop {
206            if self.pending_read.is_none() {
207                if self.position >= self.file_length() {
208                    return Poll::Ready(Ok(()));
209                }
210
211                let offset = self.position;
212                let len = usize::min(buf.remaining(), self.file_length() - self.position);
213                let stream = self.read_range_stream(offset, len).boxed();
214                self.pending_read = Some(std::sync::Mutex::new(PendingRead {
215                    stream,
216                    end_position: offset + len,
217                }));
218            }
219
220            let poll_result = {
221                let mut pending = self.pending_read.as_ref().unwrap().lock().unwrap();
222                Pin::new(&mut pending.stream).poll_next(cx)
223            };
224
225            match poll_result {
226                Poll::Ready(Some(Ok(bytes))) => {
227                    self.position += bytes.len();
228                    buf.put_slice(&bytes);
229
230                    if self.pending_read.as_ref().is_some_and(|pending| {
231                        self.position >= pending.lock().unwrap().end_position
232                    }) {
233                        self.pending_read = None;
234                    }
235
236                    if buf.remaining() == 0 {
237                        return Poll::Ready(Ok(()));
238                    }
239                }
240                Poll::Ready(Some(Err(error))) => {
241                    self.pending_read = None;
242                    return Poll::Ready(Err(io_error(error)));
243                }
244                Poll::Ready(None) => {
245                    self.pending_read = None;
246                    return Poll::Ready(Ok(()));
247                }
248                Poll::Pending => {
249                    if buf.filled().len() > starting_len {
250                        return Poll::Ready(Ok(()));
251                    }
252                    return Poll::Pending;
253                }
254            }
255        }
256    }
257}
258
259impl AsyncSeek for FileReader {
260    fn start_seek(mut self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> {
261        let file_length = self.file_length() as i128;
262        let current = self.tell() as i128;
263        let new_pos = match position {
264            SeekFrom::Start(pos) => i128::from(pos),
265            SeekFrom::End(offset) => file_length + i128::from(offset),
266            SeekFrom::Current(offset) => current + i128::from(offset),
267        };
268
269        if new_pos < 0 || new_pos > file_length {
270            return Err(io::Error::new(
271                io::ErrorKind::InvalidInput,
272                "cannot seek outside of file bounds",
273            ));
274        }
275
276        self.set_position(new_pos as usize);
277        Ok(())
278    }
279
280    fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
281        Poll::Ready(Ok(self.tell() as u64))
282    }
283}
284
285pub struct FileWriter {
286    src: String,
287    protocol: Arc<NamenodeProtocol>,
288    status: hdfs::HdfsFileStatusProto,
289    config: Arc<Configuration>,
290    handle: Handle,
291    block_writer: Option<BlockWriter>,
292    last_block: Option<hdfs::LocatedBlockProto>,
293    closed: bool,
294    bytes_written: usize,
295    crypto: Option<Arc<FileCryptoCodec>>,
296}
297
298impl FileWriter {
299    #[allow(clippy::too_many_arguments)]
300    pub(crate) fn new(
301        protocol: Arc<NamenodeProtocol>,
302        src: String,
303        status: hdfs::HdfsFileStatusProto,
304        config: Arc<Configuration>,
305        handle: Handle,
306        // Some for append, None for create
307        last_block: Option<hdfs::LocatedBlockProto>,
308        crypto: Option<Arc<FileCryptoCodec>>,
309    ) -> Self {
310        protocol.add_file_lease(status.file_id(), status.namespace.clone());
311        Self {
312            protocol,
313            src,
314            status,
315            config,
316            handle,
317            block_writer: None,
318            last_block,
319            closed: false,
320            bytes_written: 0,
321            crypto,
322        }
323    }
324
325    async fn create_block_writer(&mut self) -> Result<()> {
326        let new_block = if let Some(last_block) = self.last_block.take() {
327            // Append operation on first write. Erasure code appends always just create a new block.
328            if last_block.b.num_bytes() < self.status.blocksize() && self.status.ec_policy.is_none()
329            {
330                // The last block isn't full, just write data to it
331                last_block
332            } else {
333                // The last block is full, so create a new block to write to
334                self.protocol
335                    .add_block(&self.src, Some(last_block.b), self.status.file_id)
336                    .await?
337                    .block
338            }
339        } else {
340            // Not appending to an existing block, just create a new one
341            // If there's an existing block writer, close it first
342            let extended_block = if let Some(block_writer) = self.block_writer.take() {
343                Some(block_writer.close().await?)
344            } else {
345                None
346            };
347
348            self.protocol
349                .add_block(&self.src, extended_block, self.status.file_id)
350                .await?
351                .block
352        };
353
354        let block_writer = BlockWriter::new(
355            Arc::clone(&self.protocol),
356            new_block,
357            self.protocol.get_cached_server_defaults().await?,
358            Arc::clone(&self.config),
359            self.handle.clone(),
360            self.status
361                .ec_policy
362                .as_ref()
363                .map(resolve_ec_policy)
364                .transpose()?
365                .as_ref(),
366            &self.src,
367            &self.status,
368            self.crypto.clone(),
369        )
370        .await?;
371
372        self.block_writer = Some(block_writer);
373        Ok(())
374    }
375
376    async fn get_block_writer(&mut self) -> Result<&mut BlockWriter> {
377        // If the current writer is full, or hasn't been created, create one
378        if self.block_writer.as_ref().is_some_and(|b| b.is_full()) || self.block_writer.is_none() {
379            self.create_block_writer().await?;
380        }
381
382        Ok(self.block_writer.as_mut().unwrap())
383    }
384
385    pub async fn write_bytes(&mut self, mut buf: Bytes) -> Result<usize> {
386        let bytes_to_write = buf.len();
387        while !buf.is_empty() {
388            let block_writer = self.get_block_writer().await?;
389
390            block_writer.write(&mut buf).await?;
391        }
392
393        self.bytes_written += bytes_to_write;
394
395        Ok(bytes_to_write)
396    }
397
398    pub async fn close(&mut self) -> Result<()> {
399        if !self.closed {
400            let extended_block = if let Some(block_writer) = self.block_writer.take() {
401                Some(block_writer.close().await?)
402            } else {
403                None
404            };
405
406            let mut retry_delay = COMPLETE_RETRY_DELAY_MS;
407            let mut retries = 0;
408            while retries < COMPLETE_RETRIES {
409                let successful = self
410                    .protocol
411                    .complete(&self.src, extended_block.clone(), self.status.file_id)
412                    .await?
413                    .result;
414
415                if successful {
416                    self.closed = true;
417                    return Ok(());
418                }
419
420                // Sleep in the provided runtime in case we are not called from a tokio runtime
421                let sleep = {
422                    let _guard = self.handle.enter();
423                    tokio::time::sleep(Duration::from_millis(retry_delay))
424                };
425                sleep.await;
426
427                retry_delay *= 2;
428                retries += 1;
429            }
430            Err(HdfsError::OperationFailed(
431                "Failed to complete file in time".to_string(),
432            ))
433        } else {
434            Ok(())
435        }
436    }
437}
438
439impl Drop for FileWriter {
440    fn drop(&mut self) {
441        if !self.closed {
442            warn!(
443                "FileWriter dropped without being closed. File content may not have saved or may not be complete"
444            );
445        }
446
447        self.protocol
448            .remove_file_lease(self.status.file_id(), self.status.namespace.clone());
449    }
450}