-
Notifications
You must be signed in to change notification settings - Fork 285
pinned events: limit the max amount of concurrent requests to load pinned events #3878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pinned events: limit the max amount of concurrent requests to load pinned events #3878
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, one question.
// Give it time to load events. As each request takes `request_delay`, we should | ||
// have exactly `MAX_PINNED_EVENTS_CONCURRENT_REQUESTS` if the max | ||
// concurrency setting is honoured. | ||
sleep(Duration::from_millis(request_delay)).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be safer to sleep for 1.5 * request_delay
just to make sure some other delay doesn't cause us to just miss it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do that I'm pretty sure we'll get 20
instead since the 2nd batch of requests will be running by then.
EDIT: yep, we do.
Verifications failed:
- Mock #0.
Expected range of matching incoming requests: == 10
Number of matched incoming requests: 20
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then 0.5 * request_delay
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, sounds good. I wanted to use the full delay to prove this worked as expected, but it's true it might result in flaky tests 🫤 .
c86c842
to
b8f24bd
Compare
2e22282
to
fdb0b01
Compare
@@ -26,7 +26,7 @@ use tracing::{debug, warn}; | |||
|
|||
use crate::timeline::event_handler::TimelineEventKind; | |||
|
|||
const MAX_CONCURRENT_REQUESTS: usize = 10; | |||
pub const MAX_PINNED_EVENTS_CONCURRENT_REQUESTS: usize = 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's a pub
item, please add a documentation to it.
If it's a pub
only for testing purposes, maybe adding a #[cfg(test)]
would be nice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's a pub item, please add a documentation to it.
True, my bad.
If it's a pub only for testing purposes, maybe adding a #[cfg(test)] would be nice?
Well, it just needs to be pub
for the tests, but it needs to exist in general since it's used in the code below it. I don't know if there's some way to do that or it's better to just keep it public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's one limitation of Rust where we cannot add a #[cfg]
for a visibility only. There is a crate to handle that, https://crates.io/crates/cfg-vis, but we have removed it recently.
You can write this as follows:
#[cfg(test)]
pub const … = …;
#[cfg(not(test))]
const … = …;
if you don't want to repeat the constant value, you can have a private constant just for that:
const VALUE: usize = 10;
#[cfg(test)]
pub const …: usize = VALUE;
#[cfg(not(test))]
const …: usize = VALUE;
of course, with better names that …
and VALUE
:-D.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this won't work as the target test is an integration one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can I have another review, pretty please 🙏 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the Rust docs:
In Rust, integration tests are entirely external to your library. They use your library in the same way any other code would, which means they can only call functions that are part of your library’s public API. Their purpose is to test whether many parts of your library work together correctly. Units of code that work correctly on their own could have problems when integrated, so test coverage of the integrated code is important as well. To create integration tests, you first need a tests directory.
So I guess the test and testing cfgs are not used and we're back to "It seems like this won't work as the target test is an integration one." 🫤 .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this apparaently can't be done and the doc comment was added as @Hywan requested, can we have another review so we can at least make some progress?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've found a better idea, but it involves a bit more work (sowwy). Since the PinnedEventLoader
can use an arbitrary PinnedEventsRoom
impl, can you make the test a non-integration test, at the bottom of this file, using a test impl for PinnedEventsRoom
? Maybe running the assertions will be even simpler with that, as we don't have to rely on wiremock anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, wiremock is quite the help here... I'd rather not change everything for just a pub
modifier. Maybe I can add this to be part of the TimelineFocus::PinnedEvents
instead, like max_events_to_load
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also make the testing more targeted by not involved network, and make it much faster to run, but I understand this is a big ask.
Maybe I can add this to be part of the TimelineFocus::PinnedEvents instead, like max_events_to_load?
Yep, sounds great, thanks!
4f57f54
to
7cfcbae
Compare
9d6c03a
to
08ba529
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3878 +/- ##
==========================================
+ Coverage 84.17% 84.18% +0.01%
==========================================
Files 267 267
Lines 28181 28181
==========================================
+ Hits 23720 23725 +5
+ Misses 4461 4456 -5 ☔ View full report in Codecov by Sentry. |
let request_config = Some(RequestConfig::default().retry_limit(3)); | ||
|
||
let mut loaded_events: Vec<SyncTimelineEvent> = | ||
stream::iter(pinned_event_ids.into_iter().map(|event_id| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if we could use a plain Semaphore instead of a stream here, it's a bit overkill to use the stream machinery for this IMO…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could and did, but the code is more readable like this (IMO) and there's no performance penalty as far as I could tell by the results of the benchmark. Also, it's used in a very similar way in some other parts of the SDK.
If we wanted to use a Semaphore
instead, what would the semaphore.acquire()
code look like if the result contains an error? I remember we discussed it in a previous PR but I'm not sure if we ever made a decision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code is more readable like this (IMO)
I find the code much worse, because it involves other async concepts that one must be aware of (streams), and higher-level typing that's hard to grasp just from reading the code (stream flattening). But let's keep it like this now, and we can change that later, if it's unbearable :)
5439303
to
9c25610
Compare
This has been with 'changes requested' for > 2 weeks, but the requested changes can't be done (agreed on that in a chat room). Hywan is not working today either, so can I just rebase and force merge it? |
No, PRs need to be approved before getting merged. |
Then we're lucky we're not in a rush, I guess. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, approving assuming my comments will be addressed.
warn!("error when loading pinned event: {err}"); | ||
None | ||
let request_config = Some(RequestConfig::default().retry_limit(3)); | ||
let max_concurrent_requests = self.max_concurrent_requests; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you inline this variable, as it's only used once?
.with_focus(TimelineFocus::PinnedEvents { | ||
max_events_to_load: 100, | ||
max_concurrent_requests: 10, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make a tiny test helper that creates a pinned events focused timeline, instead? that will avoid repeating the new argument everywhere
))); | ||
|
||
// Amount of time to delay the response of an /event mock request, in ms. | ||
let request_delay = 50; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is 50 milliseconds enough for the code cov task? That would be fine to use something greater up to 1 second, fwiw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the codecov report it seems like those lines are counted as covered too, so maybe there's no need to increase this.
5e9cf98
to
0f8a821
Compare
0f8a821
to
742b987
Compare
Depends on #3840.
RequestConfig::max_concurrent_requests
won't work for requests to a same endpoint, since it's a global value that can only be passed toHttpClient
when created.To replace it I added a buffered stream that can run at most
MAX_PINNED_EVENTS_CONCURRENT_REQUESTS
(10) requests concurrently.There is also a test to make sure this max concurrency mechanism works.
Signed-off-by: