-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add condition variables to pico_sync (fixes #1093) #1101
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
base: develop
Are you sure you want to change the base?
Conversation
I'm just a contributor so please take this with a "pinch of salt" and I suggest waiting for someone with authority before acting on any of this. First of all, if this is to be called a condition variable module, I think it would be reasonable for an SDK user to expect it to also include a cond_broadcast() or similarly named function that signals all waiting threads. If an RTOS is in use, there could be >1 waiting threads. I appreciate that implementing cond_broadcast() is a challenge in the context of the SDK, in part because there is nothing resembling a thread control block that could be used to efficiently implement a linked list of threads waiting for a particular condition variable. I have two suggestions. My first suggestion is a (hopefully) upcoming thread local variable module for the SDK, which essentially provides a thread control block by another name. My second suggestion is to add a broadcast_count variable to cond_t, which atomically increments whenever cond_broadcast() is called. I've never implemented condition variables this way and I wonder if it's flawed but I think it might give waiting threads enough information to all wake up on broadcast:
Finally, I think there are some issues with condition variables as implemented. There are three cases where cond_wait() blocks by calling lock_internal_spin_unlock_with_wait():
I use the term "blocked" here, and in the operating system sense, because that is exactly what will happen when an RTOS is in use: the calling thread will transition to a blocked state. In order to transition the thread out of the blocked state, another thread must call lock_internal_spin_unlock_with_notify(). Of the three, case 1 is covered by the call to lock_internal_spin_unlock_with_notify() in cond_signal() and I believe this case is fully covered. I think there are issues with cases 2 and 3 though. In case 2, I there's no notification when cond->waiter becomes LOCK_INVALID_OWNER_ID so a thread waiting to become the current waiter might never wake up. Case 3 is partially covered by the call to lock_internal_spin_unlock_with_notify() in mutex_exit(). However, code in cond_wait() also releases the mutex by bypassing the public mutex API and setting mtx->owner = LOCK_INVALID_OWNER_ID. So, like case 2, a thread waiting for the mutex to be released might never wake up. |
Thank you.
|
Perhaps it's slightly less efficient but I think cond_wait() is clearer refactored like this. I haven't attempted to fix any of the issues I mentioned above.
|
58fa5fb
to
26d7860
Compare
@alastairpatrick Thank you for your feedback. I eventually implemented broadcast as well as timed waits. |
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
Thanks for the update; I haven't really had a chance to look at this in detail; I do note however that the latest commit uses "_sev()" in some path which is not valid under a RTOS (it is what |
@kilograham Thank you for this feedback. The |
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
23c4cb5
to
653b354
Compare
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
Also slightly modify SMP code to handle the specific case of the Pico. Code relies on condition variables implementation proposed in this upstream PR: raspberrypi/pico-sdk#1101 Signed-off-by: Paul Guyot <[email protected]>
653b354
to
f593b3a
Compare
@kilograham this was rebased on top of SDK 2.0 and tested on RP2040, RP2350 ARM and RP2350 RISCV. |
f593b3a
to
ee1abdf
Compare
@kilograham this has been rebased on top of SDK 2.1.1. Bazel compilation was fixed. Tests have been extended to increase coverage. They didn't pass on Pico 2W as there was a race condition that has been fixed (mutex spinlock was released before condition waiter was set). |
|
ee1abdf
to
a70d1c7
Compare
Implement condition variables as a companion to mutexes. Condition variables can be signaled and broadcast and implementation should work with any number of cores. To prevent deadlocks, at most a single spin lock is held at a given time. As a result there can be a race condition if several cores call cond_signal at the same time (without holding the mutex).
a70d1c7
to
b333635
Compare
This PR provides condition variables as companion to mutexes.
It is implemented without any assumption on the number of cores.
Like mutexes, condition variables are protected by a spinlock.
When waiting on a condition variable, a core tries to be the waiter (=owner) and when it is, it waits to be signaled.
When signaling a condition variable, the caller verifies that there is a waiter, and if there is, sets a boolean to signal it.
There is a trick to avoid holding two spinlocks which was incompatible with RP2350: the condition variable spinlock protects the cv waiter and the broadcast flag only, while the mutex spinlock is used in
cond_wait
andcond_signal
to for the signal. Signaling means locking two spinlocks (or only one if they are the same). This introduces a potential race condition if more than one core try to signal at the same time, which can be solved by only callingcond_signal
while mutex is held.This busy-loop implementation seems to be immune from spurious wakeup.