Skip to main content

coven_storage/
memory_requests.rs

1use super::memory_upload::MemoryStart;
2use super::*;
3
4/// Each attempted native call in the in-memory provider, before fault injection.
5/// This provider has one page per listing and no implicit metadata lookups.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub enum MemoryRequest {
8    /// Read a whole object or an explicit byte range (including missing paths).
9    Read(ObjectPath, Option<ByteRange>),
10    /// Enumerate one prefix in one native call.
11    List(ObjectPrefix),
12    /// Publish an immutable object.
13    Create(ObjectPath),
14    /// Replace an object.
15    Replace(ObjectPath),
16    /// Query exact metadata.
17    Status(ObjectPath),
18    /// Delete an object.
19    Delete(ObjectPath),
20    /// Account, permission or multipart control/part call.
21    Other(&'static str),
22}
23
24impl MemoryStorage {
25    /// Attempted calls on this connection; `for_device` starts a separate trace.
26    pub fn request_trace(&self) -> Vec<MemoryRequest> {
27        self.provider.request_trace.lock().expect("request trace poisoned").clone()
28    }
29}
30
31impl MemoryStorage {
32    /// Hold one listing's reply after taking its snapshot. The test is notified
33    /// when the result is fixed, and can mutate remote state before releasing it.
34    pub async fn hold_next_listing(
35        &self,
36        prefix: ObjectPrefix,
37        listed: tokio::sync::oneshot::Sender<()>,
38        resume: tokio::sync::oneshot::Receiver<()>,
39    ) {
40        let mut state = self.provider.state.lock().await;
41        assert!(state.held_listing.is_none(), "a listing is already held");
42        state.held_listing = Some(HeldRequest { prefix, listed, resume });
43    }
44    /// Hold the next request, whatever it is, once it is recorded: tell
45    /// `started`, then wait for `resume`.
46    pub async fn hold_next_request(
47        &self,
48        started: tokio::sync::oneshot::Sender<()>,
49        resume: tokio::sync::oneshot::Receiver<()>,
50    ) {
51        let mut state = self.provider.state.lock().await;
52        assert!(state.held_request.is_none(), "a request is already held");
53        state.held_request = Some(HeldNext { request: None, started, resume });
54    }
55    /// Hold the next request that is `request` once it is recorded, as
56    /// [`Self::hold_next_request`] does; every other request goes through.
57    pub async fn hold_next_request_of(
58        &self,
59        request: MemoryRequest,
60        started: tokio::sync::oneshot::Sender<()>,
61        resume: tokio::sync::oneshot::Receiver<()>,
62    ) {
63        let mut state = self.provider.state.lock().await;
64        assert!(state.held_request.is_none(), "a request is already held");
65        state.held_request = Some(HeldNext { request: Some(request), started, resume });
66    }
67    /// Hold a single-request creation before publication, notifying its observer.
68    pub async fn hold_next_creation(
69        &self,
70        prefix: ObjectPrefix,
71        started: tokio::sync::oneshot::Sender<()>,
72        resume: tokio::sync::oneshot::Receiver<()>,
73    ) {
74        let mut state = self.provider.state.lock().await;
75        assert!(state.held_creation.is_none(), "a creation is already held");
76        state.held_creation = Some(HeldRequest { prefix, listed: started, resume });
77    }
78}
79
80/// A provider permission request held before changing its durable grant.
81pub(super) struct HeldAccess {
82    account: String,
83    grant: bool,
84    started: tokio::sync::oneshot::Sender<()>,
85    resume: tokio::sync::oneshot::Receiver<()>,
86}
87
88impl MemoryStorage {
89    /// Hold one account request before it reaches the provider's grant state.
90    pub async fn hold_next_access(
91        &self,
92        account: &str,
93        grant: bool,
94        started: tokio::sync::oneshot::Sender<()>,
95        resume: tokio::sync::oneshot::Receiver<()>,
96    ) {
97        let mut state = self.provider.state.lock().await;
98        assert!(state.held_access.is_none(), "an access request is already held");
99        state.held_access = Some(HeldAccess { account: account.into(), grant, started, resume });
100    }
101}
102
103impl MemoryProvider {
104    #[expect(
105        clippy::let_underscore_must_use,
106        reason = "a send fails only when the receiver is gone, which this names"
107    )]
108    pub(super) async fn hold_access(&self, account: &str, grant: bool) {
109        let held = {
110            let mut state = self.state.lock().await;
111            if state.held_access.as_ref().is_some_and(|h| h.account == account && h.grant == grant)
112            {
113                state.held_access.take()
114            } else {
115                None
116            }
117        };
118        if let Some(held) = held {
119            let _ = held.started.send(());
120            held.resume.await.expect("held access request was not released");
121        }
122    }
123}
124
125impl MemoryProvider {
126    pub(super) async fn before_request(&self, request: MemoryRequest) -> Result<(), StorageError> {
127        self.request_going(request, None).await
128    }
129    /// Record `request`, hold it if a test asked, then answer it as `Faults`
130    /// say. A planned session's `start` is checked against the publication
131    /// gate past the hold, as S3 checks it as the request goes out, and goes
132    /// out once that check passes (Appendix E.10).
133    async fn request_going(
134        &self,
135        request: MemoryRequest,
136        start: Option<MemoryStart<'_>>,
137    ) -> Result<(), StorageError> {
138        let active = self.active_requests.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
139        let _request = ActiveMemoryRequest(&self.active_requests);
140        self.peak_requests.fetch_max(active, std::sync::atomic::Ordering::SeqCst);
141        self.request_trace.lock().expect("request trace poisoned").push(request.clone());
142        self.requests.send_modify(|count| *count += 1);
143        let held = {
144            let mut state = self.state.lock().await;
145            match &state.held_request {
146                Some(held) if held.request.as_ref().is_none_or(|kind| *kind == request) => {
147                    state.held_request.take()
148                }
149                _ => None,
150            }
151        };
152        if let Some(held) = held {
153            held.started
154                .send(())
155                .map_err(|()| StorageFailure::Protocol.with_source("request observer dropped"))?;
156            held.resume.await.map_err(|closed| StorageFailure::Protocol.with_source(closed))?;
157        }
158        if let Some(start) = start {
159            start.go()?;
160        }
161        if !self.online.load(std::sync::atomic::Ordering::SeqCst) {
162            return Err(StorageFailure::Network
163                .with_source(std::io::Error::from(std::io::ErrorKind::NotConnected)));
164        }
165        let (delay, failure, barrier) = {
166            let mut state = self.state.lock().await;
167            if std::mem::replace(&mut state.faults.expire_uploads, false) {
168                state.uploads.clear();
169            }
170            let failure = if state.faults.fail_next > 0 {
171                state.faults.fail_next -= 1;
172                Some((state.faults.failure, state.faults.response, state.faults.retry_after))
173            } else {
174                None
175            };
176            let (barrier, gate) = match &request {
177                MemoryRequest::Create(_) => {
178                    (state.creation_barrier.clone(), state.creation_gate.clone())
179                }
180                MemoryRequest::Read(..) => (None, state.read_gate.clone()),
181                _ => (None, None),
182            };
183            (state.faults.delay, failure, (barrier, gate))
184        };
185        let (barrier, gate) = barrier;
186        if let Some(barrier) = barrier {
187            barrier.wait().await;
188        }
189        if let Some(gate) = gate {
190            gate.acquire()
191                .await
192                .map_err(|closed| StorageFailure::Protocol.with_source(closed))?
193                .forget();
194        }
195        if !delay.is_zero() {
196            self.clock.deadline(delay).await;
197        }
198        match failure {
199            Some(failure) => Err(self.injected(failure)),
200            None => self.authorize(request).await,
201        }
202    }
203    /// An injected failure, as `Faults` describes it: no response without a
204    /// status or a `Retry-After`, else a response, a 429 unless it names
205    /// another status.
206    pub(super) fn injected(
207        &self,
208        (failure, status, retry_after): (StorageFailure, Option<u16>, Option<u64>),
209    ) -> StorageError {
210        match (status, retry_after) {
211            (None, None) => StorageError::Failure(failure),
212            (status, retry_after) => crate::providers::ProviderResponse::injected(
213                self.config.provider(),
214                failure,
215                status.unwrap_or(429),
216                retry_after,
217            ),
218        }
219    }
220    /// Send the held token; a rejected one is replaced once and the request
221    /// retried once, counted as a second request.
222    async fn authorize(&self, request: MemoryRequest) -> Result<(), StorageError> {
223        let Some(held) = self.tokens.lock().await.clone() else {
224            return Ok(());
225        };
226        let rejected = |tokens: &OAuthTokens| {
227            let token = tokens.access_token.as_str().to_owned();
228            async move { self.state.lock().await.rejected_tokens.contains(&token) }
229        };
230        let used = held.current().await;
231        if !rejected(&used).await {
232            return Ok(());
233        }
234        let replacement = held.rejected(&used).await?;
235        self.request_trace.lock().expect("request trace poisoned").push(request);
236        self.requests.send_modify(|count| *count += 1);
237        if rejected(&replacement).await {
238            return Err(StorageFailure::Authentication.into());
239        }
240        Ok(())
241    }
242    pub(super) async fn before(&self, request: MemoryRequest) -> Result<(), StorageError> {
243        self.before_going(request, None).await
244    }
245    /// [`Self::before`], for a planned session's `start` (`request_going`).
246    pub(super) async fn before_going(
247        &self,
248        request: MemoryRequest,
249        start: Option<MemoryStart<'_>>,
250    ) -> Result<(), StorageError> {
251        self.request_going(request, start).await?;
252        if let Account::Recipient(email) = &self.account {
253            let state = self.state.lock().await;
254            let allowed = match state.accounts.get(email) {
255                Some(AccountAccess::Joined) => true,
256                Some(AccountAccess::Invited) | None => false,
257            };
258            if !allowed {
259                return Err(StorageFailure::PermissionDenied.into());
260            }
261        }
262        Ok(())
263    }
264}
265
266struct ActiveMemoryRequest<'a>(&'a std::sync::atomic::AtomicUsize);
267impl Drop for ActiveMemoryRequest<'_> {
268    fn drop(&mut self) {
269        self.0.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
270    }
271}