-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
rt: high tail latencies with threaded scheduler when under load #2702
Comments
I spent more time investigating this. I discovered the high latency spike is due to a call to When running a multi-threaded process, the kernel needs to perform synchronization on the FD table. Synchronization is performed using RCU. In practice, the read lock on the FD table is released after "some period of time" after usage and not immediately after the read is complete. When To diagnose, I used the following:
Additionally, running the test after "warming up" the FD table results in smooth execution:
"warming up" the FD table is done by |
FWIW, I think I've been seeing this exact same behaviour when using recvmsg on a Unix domain socket to receive lots of file descriptors (in an unrelated non-Rust project). I was also able to sort it out by using dup2() to force the FD table to be big enough to fit all sockets I'll receive. |
I was seeing the same issue regarding By following @carllerche 's comment, I was able to use The code I'm doing
[update] better to avoid use of
|
Given a TCP echo client that:
Running this client on the threaded runtime results in significant tail latencies.
Code
Output
Running this against the
echo
example results in the following output:Compare this with
basic_scheduler
This behavior is most likely a conflation of a number of factors. In the above example, the primary issue is spawning many tasks from the main function. When using the threaded scheduler, the main function runs outside of the scheduler. Spawned tasks are sent to the scheduler using the injection queue. This injection queue (MPMC) is slower than the scheduler's primary queue (SPMC).
When the scheduler is under load, it heavily prioritizes its local queue. In the above example, the scheduler is under load, so it prioritizes already spawned tasks instead of checking for new tasks. This results in the time to first poll for tasks to be very high.
This behavior can be verified by wrapping the contents of the
main
function with a spawn:Doing this changes the output to:
And if we increase the number of threads to 8, we get:
This is better. Notice how adding threads reduces the latencies compared to
basic_scheduler
. However, the maximum latency is surprisingly high (26ms) vs.basic_scheduler
. I have not yet investigated why that is the case.Fixing
I believe the fix for the injection queue will require:
The current injection queue is fairly naive. It is a linked list guarded with a mutex. One option to consider is switching to an MPSC intrusive channel with a mutex guarding the head. This probably won't do too much. Workers probably want to acquire tasks from the injection queue in batches. Instead of popping one task at a time, when the local queue is not full, grab a bunch of tasks from the injection queue.
Heuristic wise, when the worker is under load, we may want to consider checking the injection queue more often. This may be less necessary if tasks are acquired in batches. If the injection queue does need to be checked more often, one option would be to check the queue every ~5 ticks if the last time the queue was checked there was a task.
The high tail latency after adding the spawn in the main fn is most likely not due to the injection queue. I believe adding the spawn should prevent the injection queue from being used as the total number of tasks is less than the local queue capacity (256). I do not know what is the cause for that behavior yet.
The text was updated successfully, but these errors were encountered: