chitralverma opened a new issue, #7909:
URL: https://github.com/apache/opendal/issues/7909

   # Feature Description
   
   Add an optional read path that fills a **caller-owned destination buffer** 
directly, so that capable services and transports can write bytes into memory 
the caller already owns without OpenDAL first materializing an intermediate
   owned `Buffer`/`Bytes`.
   
   Today OpenDAL's reader pipeline always yields owned `Buffer`/`Bytes` chunks. 
The closest public API, `Reader::read_into(&mut impl BufMut, range)`, still 
consumes those owned chunks from the reader stream and copies them into the
   caller's `BufMut`:
   
   ```rust
   pub async fn read_into(
       &self,
       buf: &mut impl BufMut,
       range: impl Into<BytesRange>,
   ) -> Result<usize> {
       let mut stream = self.clone().into_stream(range).await?;
       let mut read = 0;
       loop {
           let Some(bs) = stream.try_next().await? else {
               return Ok(read);
           };
           read += bs.len();
           buf.put(bs); // copy into caller memory
       }
   }
   ```
   
   With `&mut [u8]` as the `BufMut`, `bytes::BufMut::put_slice` performs a 
`copy_from_slice` into the caller's allocation. So the effective path is:
   
   ```text
   backend/transport -> OpenDAL Buffer/Bytes -> caller-owned buffer
                                            one memcpy
   ```
   
   The request is for an API where the caller supplies the destination and, on
   capable backends, the backend writes into it directly:
   
   ```text
   backend/transport -> caller-owned destination      (no intermediate data 
buffer)
   ```
   
   # Problem and Solution
   
   ## Problem
   
   Many callers do not own the shape of their destination memory — it is 
dictated by a foreign API, a hardware constraint, or a preallocated pool — and 
they require OpenDAL to *fill that specific memory*. In these cases OpenDAL's 
`Buffer`-first model forces an extra copy that has no semantic purpose.
   
   Concrete cases where the caller owns the destination and cannot adopt an 
OpenDAL `Buffer`:
   
   - **FFI / foreign filesystem bridges.** C/C++ `pread`-style contracts 
(`read(void *buffer, size_t nr_bytes, off_t offset)`) hand OpenDAL a raw 
destination pointer by value and require it to be filled. The buffer is owned 
and freed by the foreign runtime; OpenDAL cannot return or substitute its own 
`Buffer`, so today every such bridge pays a memcpy per read.
   - **Columnar / Parquet readers.** Arrow-style readers assemble column chunks 
into preallocated, correctly-aligned buffers (often part of a larger arena or a 
`MutableBuffer`). They want positioned range reads landed straight into those 
buffers rather than into a transient `Buffer` that is immediately copied and 
dropped.
   - **Buffer pools and arenas.** Query engines and network servers read into 
slabs from a fixed-capacity pool to bound allocation and improve cache 
locality. A `Buffer`-returning read defeats the pool because the data lands in 
OpenDAL-owned memory first.
   - **Special-purpose memory.** Destinations such as `mmap` regions, GPU 
pinned host memory, `O_DIRECT`-aligned buffers, or `io_uring`-registered 
buffers must be written in place. They cannot be replaced by an OpenDAL 
allocation.
   
   The extra copy is most visible exactly where network latency does *not* hide 
memory bandwidth: local `fs`, fast attached storage, the in-memory `memory` 
service, and cache hits (e.g. a warm cache layer). For large scans, many 
concurrent positioned reads, or high-throughput links, the redundant copy shows 
up as CPU cycles, memory bandwidth, and allocator pressure.
   
   ## Proposed solution
   
   Introduce a caller-owned buffer read at two levels, gated by capability with 
a safe fallback:
   
   1. **Public high-level method** on `Reader`, e.g.
   
      ```rust
      /// Read exactly `dst.len()` bytes for `range` into caller-owned memory.
      pub async fn read_exact_into(
          &self,
          dst: &mut [u8],
          range: Range<u64>,
      ) -> Result<()>;
      ```
   
      with a fixed-capacity, `pread`-like contract that matches positioned 
reads.
   
   2. **Optional raw capability** on the positioned-read path so capable 
services fill the destination directly, for example an additive method 
alongside the existing `PositionRead::read_at`:
   
      ```rust
      async fn read_at_into(
          handle: &Self::Handle,
          offset: u64,
          dst: &mut [u8],
      ) -> Result<usize>;
      ```
   
      Services that can write into a foreign destination (starting with `fs` 
via platform positioned reads, and `memory` via `copy_from_slice`) implement 
it. Everything else falls back to the existing `read_at` + one copy, so no 
service
      is required to change and behavior is preserved.
   
   The existing `read`/`read_into`/`fetch`/stream APIs and their 
zero-copy-within-OpenDAL semantics stay exactly as they are; this is purely 
additive.
   
   A pragmatic first increment: land the raw capability + fs implementation + 
generic fallback + the `Reader` method, benchmark it against `read_into`, then 
consider extending to transports and other services. A full design (capability
   advertisement, layer composition — retry/timeout/cancellation with borrowed 
memory, short-read/EOF semantics, `MaybeUninit` vs initialized slices) is worth 
an RFC; i'll be happy to draft that if this issue makes sense.
   
   # Additional Context
   
   - [ ] Yes, I am willing to contribute to the development of this feature.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to