-
Notifications
You must be signed in to change notification settings - Fork 5
Fix #230: Modify Barrier::wait to return BarrierWaitResult #254
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
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
bf13531
Fix #230: Modify Barrier::wait to return BarrierWaitResult
r1ru 3da5d85
Merge branch 'main' into barrier_sync
r1ru fecabaf
Move Barrier implementation to barrier.rs
r1ru 32fc9b4
Create simple test
r1ru 4e5703d
Merge branch 'main' into barrier_sync
r1ru 220bf37
Bug fix: resolve infinite loop
r1ru 5f360a1
Handles the case where Barrier::wait is called more than the specifie…
r1ru 86edfc0
Fix typo
r1ru 64ffd48
Fix typo
r1ru f2e57c1
Verify the algorithm of Barrier::wait with TLA+
r1ru f0e5a23
Fix typo
r1ru 5a4d8d7
Fix typo
r1ru 6570b17
Fix the implementation of Barrier::wait
r1ru File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| pub use awkernel_lib::sync::mutex as raw_mutex; | ||
| pub mod barrier; | ||
| pub mod mutex; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| use super::mutex::AsyncLock; | ||
| use crate::pubsub::{self, Attribute, Publisher, Subscriber}; | ||
| use alloc::{vec, vec::Vec}; | ||
|
|
||
| struct BarrierState { | ||
| count: usize, | ||
| } | ||
|
|
||
| /// A barrier enables multiple threads to synchronize the beginning of some computation. | ||
| pub struct Barrier { | ||
| lock: AsyncLock<BarrierState>, | ||
| num_threads: usize, | ||
| tx: Publisher<()>, | ||
| rxs: Vec<Subscriber<()>>, | ||
| } | ||
|
|
||
| /// `BarrierWaitResult` is returned by `Barrier::wait` when all threads in `Barrier` have rendezvoused. | ||
| pub struct BarrierWaitResult(bool); | ||
|
|
||
| impl BarrierWaitResult { | ||
| pub fn is_reader(&self) -> bool { | ||
| self.0 | ||
| } | ||
| } | ||
|
|
||
| impl Barrier { | ||
| /// Creates a new barrier that can block a given number of threads. | ||
| pub fn new(n: usize) -> Self { | ||
| let attr = Attribute { | ||
| queue_size: 1, | ||
| ..Attribute::default() | ||
| }; | ||
| let (tx, rx) = pubsub::create_pubsub(attr); | ||
|
|
||
| let mut rxs = vec![rx.clone(); n - 2]; | ||
| rxs.push(rx); | ||
|
|
||
| Self { | ||
| lock: AsyncLock::new(BarrierState { count: 0 }), | ||
| num_threads: n, | ||
| tx, | ||
| rxs, | ||
| } | ||
| } | ||
|
|
||
| /// Blocks the current thread until all threads have redezvoused here. | ||
| /// A single (arbitrary) thread will receive `BarrierWaitResult(true)` when returning from this function, and other threads will receive `BarrierWaitResult(false)`. | ||
| pub async fn wait(&self) -> BarrierWaitResult { | ||
| let mut lock = self.lock.lock().await; | ||
| let count = lock.count; | ||
| if count < (self.num_threads - 1) { | ||
| lock.count += 1; | ||
| drop(lock); | ||
| self.rxs[count].recv().await; | ||
| BarrierWaitResult(false) | ||
| } else { | ||
| lock.count = 0; | ||
| drop(lock); | ||
| self.tx.send(()).await; | ||
| BarrierWaitResult(true) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use alloc::sync::Arc; | ||
| use core::sync::atomic::{AtomicUsize, Ordering}; | ||
|
|
||
| #[test] | ||
| fn test_simple_async_barrier() { | ||
| let barrier = Arc::new(Barrier::new(10)); | ||
| let num_waits = Arc::new(AtomicUsize::new(0)); | ||
| let num_leaders = Arc::new(AtomicUsize::new(0)); | ||
| let tasks = crate::mini_task::Tasks::new(); | ||
|
|
||
| for _ in 0..10 { | ||
| let barrier = barrier.clone(); | ||
| let num_waits = num_waits.clone(); | ||
| let num_leaders = num_leaders.clone(); | ||
| let task = async move { | ||
| num_waits.fetch_add(1, Ordering::Relaxed); | ||
|
|
||
| if barrier.wait().await.is_reader() { | ||
| num_leaders.fetch_add(1, Ordering::Relaxed); | ||
| } | ||
| // Verify that Barrier synchronizes the specified number of threads | ||
| assert_eq!(num_waits.load(Ordering::Relaxed), 10); | ||
|
|
||
| // It is safe to call Barrier::wait again | ||
| barrier.wait().await; | ||
| }; | ||
|
|
||
| tasks.spawn(task); | ||
| } | ||
| tasks.run(); | ||
|
|
||
| // Verify that only one thread receives BarrierWaitResult(true) | ||
| assert_eq!(num_leaders.load(Ordering::Relaxed), 1); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| # Specification of Barrier implementation | ||
| ## How to run | ||
|
|
||
| 1. Download tla2tools (https://github.com/tlaplus/tlaplus/releases) | ||
|
|
||
| 2. Translate PlusCal to TLA+ | ||
| ```bash | ||
| java -cp tla2tools.jar pcal.trans barrier.tla | ||
| ``` | ||
|
|
||
| 3. Run TLC | ||
| ```bash | ||
| java -jar tla2tools.jar -config barrier.cfg barrier.tla | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| SPECIFICATION Spec | ||
| \* Add statements after this line. | ||
| CONSTANT Threads = {1, 2, 3, 4} | ||
| CONSTANT N = 2 | ||
| INVARIANT BarrierInvariant |
110 changes: 110 additions & 0 deletions
110
specification/awkernel_async_lib/src/barrier/barrier.tla
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| ----------------- MODULE barrier ---------------- | ||
| EXTENDS TLC, Integers, FiniteSets, Sequences | ||
| CONSTANTS Threads, N | ||
| ASSUME N \in Nat | ||
| ASSUME Threads \in SUBSET Nat | ||
|
|
||
| \* It is obvious that a deadlock wil ocuur if this conditon is not satisfied. | ||
| ASSUME Cardinality(Threads) % N = 0 | ||
|
|
||
| (*--algorithm barrier | ||
|
|
||
| \* `count` records how many times `wait` has been called. | ||
| \* `num_blocked` records the number of blocked threads. | ||
| variables | ||
| count = 0, | ||
| num_blocked = 0, | ||
| blocked = FALSE; | ||
|
|
||
| \* If `count` < N, then the thread is blocked. otherwise, all blocked threads are awakened. | ||
| \* This property implies that Barrier does not block more than N threads. | ||
| define | ||
| BarrierInvariant == num_blocked = count % N | ||
| end define; | ||
|
|
||
| \* Note that `wait` is modeled as an atomic operation. | ||
| \* Therefore, the implementation needs to use mechanisms such as locks. | ||
| procedure wait() begin | ||
| Wait: | ||
| count := count + 1; | ||
| if count % N /= 0 then | ||
| num_blocked := num_blocked + 1; | ||
| blocked := TRUE; | ||
| Await: | ||
ytakano marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| await ~blocked; | ||
| return; | ||
| else | ||
| num_blocked := 0; | ||
| blocked := FALSE; | ||
| return; | ||
| end if; | ||
| end procedure; | ||
|
|
||
| fair process thread \in Threads begin | ||
| Body: | ||
| call wait(); | ||
| end process; | ||
|
|
||
| end algorithm*) | ||
| \* BEGIN TRANSLATION (chksum(pcal) = "78d1002e" /\ chksum(tla) = "8098b806") | ||
| VARIABLES pc, count, num_blocked, blocked, stack | ||
|
|
||
| (* define statement *) | ||
| BarrierInvariant == num_blocked = count % N | ||
|
|
||
|
|
||
| vars == << pc, count, num_blocked, blocked, stack >> | ||
|
|
||
| ProcSet == (Threads) | ||
|
|
||
| Init == (* Global variables *) | ||
| /\ count = 0 | ||
| /\ num_blocked = 0 | ||
| /\ blocked = FALSE | ||
| /\ stack = [self \in ProcSet |-> << >>] | ||
| /\ pc = [self \in ProcSet |-> "Body"] | ||
|
|
||
| Wait(self) == /\ pc[self] = "Wait" | ||
| /\ count' = count + 1 | ||
| /\ IF count' % N /= 0 | ||
| THEN /\ num_blocked' = num_blocked + 1 | ||
| /\ blocked' = TRUE | ||
| /\ pc' = [pc EXCEPT ![self] = "Await"] | ||
| /\ stack' = stack | ||
| ELSE /\ num_blocked' = 0 | ||
| /\ blocked' = FALSE | ||
| /\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc] | ||
| /\ stack' = [stack EXCEPT ![self] = Tail(stack[self])] | ||
|
|
||
| Await(self) == /\ pc[self] = "Await" | ||
| /\ ~blocked | ||
| /\ pc' = [pc EXCEPT ![self] = Head(stack[self]).pc] | ||
| /\ stack' = [stack EXCEPT ![self] = Tail(stack[self])] | ||
| /\ UNCHANGED << count, num_blocked, blocked >> | ||
|
|
||
| wait(self) == Wait(self) \/ Await(self) | ||
|
|
||
| Body(self) == /\ pc[self] = "Body" | ||
| /\ stack' = [stack EXCEPT ![self] = << [ procedure |-> "wait", | ||
| pc |-> "Done" ] >> | ||
| \o stack[self]] | ||
| /\ pc' = [pc EXCEPT ![self] = "Wait"] | ||
| /\ UNCHANGED << count, num_blocked, blocked >> | ||
|
|
||
| thread(self) == Body(self) | ||
|
|
||
| (* Allow infinite stuttering to prevent deadlock on termination. *) | ||
| Terminating == /\ \A self \in ProcSet: pc[self] = "Done" | ||
| /\ UNCHANGED vars | ||
|
|
||
| Next == (\E self \in ProcSet: wait(self)) | ||
| \/ (\E self \in Threads: thread(self)) | ||
| \/ Terminating | ||
|
|
||
| Spec == /\ Init /\ [][Next]_vars | ||
| /\ \A self \in Threads : WF_vars(thread(self)) /\ WF_vars(wait(self)) | ||
|
|
||
| Termination == <>(\A self \in ProcSet: pc[self] = "Done") | ||
|
|
||
| \* END TRANSLATION | ||
| ==== | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.