fix(mailroom): reject post requests over capacity#1509
Conversation
Both `post_v2` and `post_v1_req_and_wait` grow the in-memory counters tracked by `len()`, but only `wait_v2` was guarded by `has_capacity()`. Apply the same check to the two write paths so the capacity bound is enforced symmetrically.
| id: &ShortId, | ||
| payload: Vec<u8>, | ||
| ) -> Result<Option<oneshot::Receiver<Vec<u8>>>, Error> { | ||
| if !self.has_capacity().await? { |
There was a problem hiding this comment.
note that technically in this fn it could be that the v2 removal makes room for the v1 insert.
i think it's better this way since the code is simpler, but technically it implies v1 writers will in some cases see effective capacity of n-1 instead of n
There was a problem hiding this comment.
That's not obvious, thanks for pointing it out.
I'll come back to this when I understand the code better.
There was a problem hiding this comment.
IMO don't bother fixing this, maybe just add a comment to indicate that this edge case exists
Add regression tests covering `OverCapacity` rejection from each DB method that the capacity check guards: `post_v2_payload`, `post_v1_request_and_wait_for_response`, and `wait_for_v2_payload`. Long-poll tests spawn parallel waiters and poll `Mailboxes::len` until `pending_v1`/`pending_v2` are full before probing. Extract `random_id` and `random_payload` helpers shared across the three new tests.
Thanks for the review. |
|
|
||
| fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) } | ||
|
|
||
| fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() } | ||
|
|
There was a problem hiding this comment.
When a ran using cargo run The compiler returns two warnings about these functions being unused. Since they are only used in tests, you can use #[cfg(test)]
| fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) } | |
| fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() } | |
| #[cfg(test)] | |
| fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) } | |
| #[cfg(test)] | |
| fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() } | |
There was a problem hiding this comment.
in the rest of the codebase when we have this kind of stuff we usually add
#[cfg(test)]
mod tests {
...
}around all the tests to reduce the boilerplate, i can't decide if it's worth it in this case
There was a problem hiding this comment.
Agreed, wrapping everything in a #[cfg(test)] mod tests { } block is cleaner and more consistent with the rest of the codebase. It avoids annotating each helper individually and makes it clear at a glance that all of it is test-only code
There was a problem hiding this comment.
I would prefer to wrap tests in files.rs to mod tests {} for consistency with codebase, even we decided to delete this helper functions below.
There was a problem hiding this comment.
@nothingmuch should I add commit hash d8afeaa to .git-blame-ignore-revs file? And if so - what's the accepted way to do this: a commit in this PR, a separate PR, etc?
There was a problem hiding this comment.
should I add commit hash d8afeaa to
.git-blame-ignore-revsfile?
no need IMO, git blame has -w option, ignore-revs is more useful for massive reformatting that isn't trivially addressed by git's move/copy detection or -w
And if so - what's the accepted way to do this: a commit in this PR, a separate PR, etc?
in general, just another commit in the PR makes the most sense to me, it's impossible to do it in one commit but that's the closest thing to that
|
| fn random_id() -> ShortId { ShortId(OsRng.next_u64().to_ne_bytes()) } | ||
|
|
||
| fn random_payload() -> Vec<u8> { rand::random::<[u8; 32]>().to_vec() } |
There was a problem hiding this comment.
i think these functions can be removed, you can just use the loop index as the id
| assert!(matches!( | ||
| db.post_v2_payload(&random_id(), random_payload()).await, | ||
| Err(DbError::OverCapacity) | ||
| )); |
There was a problem hiding this comment.
all mailboxes share the same capacity, so i think it'd be more correct to assert that whenever there's no capacity, this is true of all request types:
| assert!(matches!( | |
| db.post_v2_payload(&random_id(), random_payload()).await, | |
| Err(DbError::OverCapacity) | |
| )); | |
| assert!(matches!( | |
| db.post_v2_payload(&random_id(), random_payload()).await, | |
| Err(DbError::OverCapacity) | |
| )); | |
| assert!(matches!(db.wait_for_v2_payload(&random_id()).await, Err(DbError::OverCapacity))); | |
| assert!(matches!( | |
| db.post_v1_request_and_wait_for_response(&random_id(), random_payload()).await, | |
| Err(DbError::OverCapacity) | |
| )); |
same for the lower checks (so this can be a utility function, or even just a single test which always asserts this for a variety of different at-capacity setups)
Move existing tests under a `mod tests {}` submodule.
No functional changes.
Drop the `random_id` and `random_payload` helpers in favor of deterministic `ShortId([i; 8])` and a fixed payload.
Capacity is shared across all request types, so once it is full every path must reject with `OverCapacity` regardless of how it was filled. Extract the assertion into a helper and call it from each capacity test.
|
@va-an sorry for the delay in running the CI workflows, can you fix the lint errors before we merge? |
Coverage Report for CI Build 25330149521Warning Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes. Coverage increased (+0.1%) to 85.088%Details
Uncovered Changes
Coverage Regressions15 previously-covered lines in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
i think this is the result of the toolchain (provided by |
Closes #1507
Both
post_v2andpost_v1_req_and_waitgrow the in-memory counters tracked bylen(), but onlywait_v2was guarded byhas_capacity(). This patch applies the same check to the two write paths so the capacity bound is enforced symmetrically.Pull Request Checklist
Please confirm the following before requesting review: