Skip to main content

mountos_native/
bulk.rs

1//! Bulk calls on the client: batched stat and whole-file read and write.
2
3use crate::client::Client;
4use crate::error::{Error, Result};
5use crate::events::lock;
6use crate::frame::MAX_META_BYTES;
7use crate::wire::*;
8use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
9use std::sync::Mutex;
10
11/// The server rejects more items than this in one batch request.
12pub(crate) const MAX_BATCH_ITEMS: usize = 4096;
13
14/// Result of one path in `Client::stat_batch`.
15pub type StatResult = Result<FileInfo>;
16
17/// Turns a server error for one item into an inner result. Connection and protocol errors stay outer.
18pub(crate) fn item<T>(r: Result<T>) -> Result<Result<T>> {
19  match r {
20    Err(e @ Error::Server { .. }) => Ok(Err(e)),
21    other => other.map(Ok),
22  }
23}
24
25/// Maps the error list of a batch reply to one slot per item.
26pub(crate) fn batch_errors(n: usize, errors: Vec<BatchItemError>) -> Result<Vec<Option<Error>>> {
27  let mut out: Vec<Option<Error>> = (0..n).map(|_| None).collect();
28  for e in errors {
29    let slot = out
30      .get_mut(e.index as usize)
31      .ok_or_else(|| Error::Protocol(format!("batch error index {} out of {n}", e.index)))?;
32    *slot = Some(Error::Server {
33      code: e.code,
34      message: e.message,
35    });
36  }
37  Ok(out)
38}
39
40/// Runs `job(0..n)` on up to `workers` threads and returns the results in index order. After the
41/// first failure no new job starts. The first failure in index order is returned.
42pub(crate) fn par_map<T: Send>(n: usize, workers: usize, job: impl Fn(usize) -> Result<T> + Sync) -> Result<Vec<T>> {
43  if n <= 1 || workers <= 1 {
44    return (0..n).map(job).collect();
45  }
46  let next = AtomicUsize::new(0);
47  let failed = AtomicBool::new(false);
48  let slots: Mutex<Vec<Option<Result<T>>>> = Mutex::new((0..n).map(|_| None).collect());
49  std::thread::scope(|sc| {
50    for _ in 0..workers.min(n) {
51      sc.spawn(|| loop {
52        let i = next.fetch_add(1, Ordering::SeqCst);
53        if i >= n || failed.load(Ordering::SeqCst) {
54          break;
55        }
56        let r = job(i);
57        if r.is_err() {
58          failed.store(true, Ordering::SeqCst);
59        }
60        lock(&slots)[i] = Some(r);
61      });
62    }
63  });
64  let mut out = Vec::with_capacity(n);
65  for r in slots
66    .into_inner()
67    .unwrap_or_else(|e| e.into_inner())
68    .into_iter()
69    .flatten()
70  {
71    out.push(r?);
72  }
73  Ok(out)
74}
75
76impl Client {
77  /// Returns attributes for many paths. The result has one entry per path in input order.
78  /// Large inputs are split into several requests that run in parallel on the pool.
79  pub fn stat_batch(&self, paths: &[String]) -> Result<Vec<StatResult>> {
80    let meta_budget = MAX_META_BYTES as usize / 2;
81    let mut parts = Vec::new();
82    let mut start = 0;
83    while start < paths.len() {
84      let (mut end, mut size) = (start, 0usize);
85      while end < paths.len()
86        && end - start < MAX_BATCH_ITEMS
87        && (end == start || size + paths[end].len() + 8 <= meta_budget)
88      {
89        size += paths[end].len() + 8;
90        end += 1;
91      }
92      parts.push(&paths[start..end]);
93      start = end;
94    }
95    let chunks = par_map(parts.len(), self.pool_size(), |i| {
96      let part = parts[i];
97      let r: StatBatchResponse = self.unary(method::STAT_BATCH, &StatBatchRequest { paths: part.to_vec() })?;
98      if r.infos.len() != part.len() {
99        return Err(Error::Protocol(format!(
100          "stat batch returned {} infos for {} paths",
101          r.infos.len(),
102          part.len()
103        )));
104      }
105      let mut errs = batch_errors(part.len(), r.errors)?;
106      Ok(
107        r.infos
108          .into_iter()
109          .enumerate()
110          .map(|(i, info)| match errs[i].take() {
111            Some(e) => Err(e),
112            None => Ok(info),
113          })
114          .collect::<Vec<_>>(),
115      )
116    })?;
117    Ok(chunks.into_iter().flatten().collect())
118  }
119
120  /// Reads a whole file. A file that fits in one payload takes one request. A larger file is
121  /// opened and read in chunks until end of file.
122  pub fn read_file(&self, path: &str) -> Result<Vec<u8>> {
123    let (data, limit) = self
124      .run_any(|c| {
125        let req = ReadFileRequest {
126          path: path.into(),
127          max_bytes: c.max_payload() as u64,
128        };
129        let (r, data) = c.call_msg::<ReadFileResponse>(method::READ_FILE, &req, &[])?;
130        if r.count as usize != data.len() {
131          return Err(Error::Protocol(format!(
132            "read file count {} payload {}",
133            r.count,
134            data.len()
135          )));
136        }
137        Ok((data, c.max_payload()))
138      })?
139      .0;
140    if data.len() < limit {
141      return Ok(data);
142    }
143    let f = self.open(path, open_flag::READ_ONLY, 0)?;
144    let mut out = Vec::new();
145    loop {
146      let part = f.read_at(out.len() as u64, limit)?;
147      let short = part.len() < limit;
148      out.extend_from_slice(&part);
149      if short {
150        return Ok(out);
151      }
152    }
153  }
154
155  /// Writes a whole file, creating it when missing. With `truncate` the old content is dropped
156  /// first. Data larger than one payload is written through an opened file in chunks.
157  pub fn write_file(&self, path: &str, data: &[u8], truncate: bool) -> Result<()> {
158    let sent = self
159      .run_any(|c| {
160        if data.len() > c.max_payload() {
161          return Ok(false);
162        }
163        let req = WriteFileRequest {
164          path: path.into(),
165          truncate,
166        };
167        let (r, _) = c.call_msg::<WriteFileResponse>(method::WRITE_FILE, &req, data)?;
168        if r.count as usize != data.len() {
169          return Err(Error::Protocol(format!(
170            "write file count {} for {} bytes",
171            r.count,
172            data.len()
173          )));
174        }
175        Ok(true)
176      })?
177      .0;
178    if sent {
179      return Ok(());
180    }
181    let mut flags = open_flag::WRITE_ONLY | open_flag::CREATE;
182    if truncate {
183      flags |= open_flag::TRUNC;
184    }
185    let f = self.open(path, flags, 0o644)?;
186    f.write_at(0, data)?;
187    f.close()
188  }
189}