Skip to content

Commit 974b50b

Browse files
committed
samples: async-philosophers: Convert to general async
Convert away from the Zephyr-specific async to standard Rust async. This uses the Embassy crates for time and synchronization. All tasks are spawned within a single executor that runs in the main thread. Signed-off-by: David Brown <[email protected]>
1 parent ead1f93 commit 974b50b

File tree

4 files changed

+86
-110
lines changed

4 files changed

+86
-110
lines changed

samples/async-philosophers/Cargo.toml

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ license = "Apache-2.0 or MIT"
1313
crate-type = ["staticlib"]
1414

1515
[dependencies]
16-
zephyr = "0.1.0"
16+
zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr"] }
17+
static_cell = "2.1"
18+
19+
embassy-executor = { version = "0.7.0", features = ["log", "task-arena-size-2048"] }
20+
embassy-sync = "0.6.2"
21+
22+
# TODO: Don't hardcode the tickrate.
23+
embassy-time = { version = "0.4.0", features = ["tick-hz-10_000"] }
1724

1825
# Dependencies that are used by build.rs.
1926
[build-dependencies]

samples/async-philosophers/prj.conf

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CONFIG_MAIN_STACK_SIZE=8192
77
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
88

99
CONFIG_POLL=y
10+
CONFIG_STACK_CANARIES=y
1011

1112
# CONFIG_DEBUG=y
1213

samples/async-philosophers/src/async_sem.rs

+48-63
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@
99
//! tasks run on the same worker do not need Send. It is just important that write operations on
1010
//! the RefCell do not `.await` or a panic is likely.
1111
12-
use core::cell::RefCell;
13-
14-
use alloc::{rc::Rc, vec::Vec};
15-
use zephyr::{
16-
kio::{sleep, spawn_local},
17-
printkln,
18-
sys::sync::Semaphore,
19-
time::Forever,
12+
use embassy_executor::Spawner;
13+
use embassy_sync::{
14+
blocking_mutex::raw::CriticalSectionRawMutex,
15+
mutex::Mutex,
16+
semaphore::{FairSemaphore, Semaphore},
2017
};
18+
use embassy_time::Timer;
19+
use zephyr::{printkln, sync::Arc};
2120

22-
use crate::{get_random_delay, Stats, NUM_PHIL};
21+
use crate::{get_random_delay, ResultSignal, Stats, NUM_PHIL};
22+
23+
type ESemaphore = FairSemaphore<CriticalSectionRawMutex, NUM_PHIL>;
24+
25+
/// The semaphores for the forks.
26+
static FORKS: [ESemaphore; NUM_PHIL] = [const { ESemaphore::new(1) }; NUM_PHIL];
27+
28+
/// The semaphore to wait for them all to finish.
29+
static DONE_SEM: ESemaphore = ESemaphore::new(0);
2330

2431
/// Number of iterations of each philospher.
2532
///
@@ -28,56 +35,27 @@ use crate::{get_random_delay, Stats, NUM_PHIL};
2835
/// there are two waits, so typically, each "eat" will take about a second.
2936
const EAT_COUNT: usize = 10;
3037

31-
pub async fn phil() -> Stats {
32-
// It is a little tricky to be able to use local workers. We have to have this nested thread
33-
// that waits. This is because the Future from `local_phil()` does not implement Send, since it
34-
// waits for the philosophers, which are not Send. However, this outer async function does not
35-
// hold onto any data that is not send, and therefore will be Send. Fortunately, this extra
36-
// Future is very lightweight.
37-
spawn_local(local_phil(), c"phil_wrap").join_async().await
38-
}
39-
40-
async fn local_phil() -> Stats {
38+
#[embassy_executor::task]
39+
pub async fn phil(spawner: Spawner, stats_sig: &'static ResultSignal) {
4140
// Our overall stats.
42-
let stats = Rc::new(RefCell::new(Stats::default()));
43-
44-
// One fork for each philospher.
45-
let forks: Vec<_> = (0..NUM_PHIL)
46-
.map(|_| Rc::new(Semaphore::new(1, 1)))
47-
.collect();
48-
49-
// Create all of the philosphers
50-
let phils: Vec<_> = (0..NUM_PHIL)
51-
.map(|i| {
52-
// Determine the two forks. The forks are paired with each philosopher taking the fork of
53-
// their number, and the next on, module the size of the ring. However, for the last case,
54-
// we need to swap the forks used, it is necessary to obey a strict ordering of the locks to
55-
// avoid deadlocks.
56-
let forks = if i == NUM_PHIL - 1 {
57-
[forks[0].clone(), forks[i].clone()]
58-
} else {
59-
[forks[i].clone(), forks[i + 1].clone()]
60-
};
61-
62-
spawn_local(one_phil(forks, i, stats.clone()), c"phil")
63-
})
64-
.collect();
41+
let stats = Arc::new(Mutex::new(Stats::default()));
6542

66-
// Wait for them all to finish.
67-
for p in phils {
68-
p.join_async().await;
43+
// Spawn off each philosopher.
44+
for i in 0..NUM_PHIL {
45+
let forks = if i == NUM_PHIL - 1 {
46+
[&FORKS[0], &FORKS[i]]
47+
} else {
48+
[&FORKS[i], &FORKS[i + 1]]
49+
};
50+
51+
spawner.spawn(one_phil(forks, i, stats.clone())).unwrap();
6952
}
7053

71-
// Leak the stats as a test.
72-
// Uncomment this to test that the expect below does truly detect a missed drop.
73-
// let _ = Rc::into_raw(stats.clone());
54+
// Wait for them all to finish.
55+
DONE_SEM.acquire(NUM_PHIL).await.unwrap();
7456

75-
// At this point, all of the philosphers should have dropped their stats ref, and we should be
76-
// able to turn stats back into it's value.
77-
// This tests that completed work does drop the future.
78-
Rc::into_inner(stats)
79-
.expect("Failure: a philospher didn't drop it's future")
80-
.into_inner()
57+
// Send the stats back.
58+
stats_sig.signal(stats);
8159
}
8260

8361
/// Simulate a single philospher.
@@ -86,28 +64,35 @@ async fn local_phil() -> Stats {
8664
/// likely deadlock.
8765
///
8866
/// This will run for EAT_COUNT times, and then return.
89-
async fn one_phil(forks: [Rc<Semaphore>; 2], n: usize, stats: Rc<RefCell<Stats>>) {
67+
#[embassy_executor::task(pool_size = NUM_PHIL)]
68+
async fn one_phil(
69+
forks: [&'static ESemaphore; 2],
70+
n: usize,
71+
stats: Arc<Mutex<CriticalSectionRawMutex, Stats>>,
72+
) {
9073
for i in 0..EAT_COUNT {
9174
// Acquire the forks.
9275
// printkln!("Child {n} take left fork");
93-
forks[0].take_async(Forever).await.unwrap();
76+
forks[0].acquire(1).await.unwrap().disarm();
9477
// printkln!("Child {n} take right fork");
95-
forks[1].take_async(Forever).await.unwrap();
78+
forks[1].acquire(1).await.unwrap().disarm();
9679

9780
// printkln!("Child {n} eating");
9881
let delay = get_random_delay(n, 25);
99-
sleep(delay).await;
100-
stats.borrow_mut().record_eat(n, delay);
82+
Timer::after(delay).await;
83+
stats.lock().await.record_eat(n, delay);
10184

10285
// Release the forks.
10386
// printkln!("Child {n} giving up forks");
104-
forks[1].give();
105-
forks[0].give();
87+
forks[1].release(1);
88+
forks[0].release(1);
10689

10790
let delay = get_random_delay(n, 25);
108-
sleep(delay).await;
109-
stats.borrow_mut().record_think(n, delay);
91+
Timer::after(delay).await;
92+
stats.lock().await.record_think(n, delay);
11093

11194
printkln!("Philospher {n} finished eating time {i}");
11295
}
96+
97+
DONE_SEM.release(1);
11398
}

samples/async-philosophers/src/lib.rs

+29-46
Original file line numberDiff line numberDiff line change
@@ -9,76 +9,59 @@
99

1010
extern crate alloc;
1111

12-
use zephyr::{
13-
kio::spawn,
14-
kobj_define, printkln,
15-
sync::Arc,
16-
sys::uptime_get,
17-
time::{Duration, Tick},
18-
work::WorkQueueBuilder,
19-
};
12+
use embassy_executor::Spawner;
13+
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
14+
use embassy_time::Duration;
15+
use static_cell::StaticCell;
16+
use zephyr::{embassy::Executor, printkln, sync::Arc, sys::uptime_get};
2017

2118
mod async_sem;
2219

2320
/// How many philosophers. There will be the same number of forks.
2421
const NUM_PHIL: usize = 6;
2522

26-
/// Size of the stack for the work queue.
27-
const WORK_STACK_SIZE: usize = 2048;
28-
2923
// The dining philosophers problem is a simple example of cooperation between multiple threads.
3024
// This implementation demonstrates a few ways that Zephyr's work-queues can be used to simulate
3125
// this problem.
3226

3327
#[no_mangle]
3428
extern "C" fn rust_main() {
35-
printkln!(
36-
"Async/work-queue dining philosophers{}",
37-
zephyr::kconfig::CONFIG_BOARD
38-
);
29+
printkln!("Async dining philosophers{}", zephyr::kconfig::CONFIG_BOARD);
3930
printkln!("Time tick: {}", zephyr::time::SYS_FREQUENCY);
4031

41-
// Create the work queue to run this.
42-
let worker = Arc::new(
43-
WorkQueueBuilder::new()
44-
.set_priority(1)
45-
.start(WORK_STACK.init_once(()).unwrap()),
46-
);
47-
48-
// In addition, create a lower priority worker.
49-
let lower_worker = Arc::new(
50-
WorkQueueBuilder::new()
51-
.set_priority(5)
52-
.start(LOWER_WORK_STACK.init_once(()).unwrap()),
53-
);
54-
55-
// It is important that work queues are not dropped, as they are persistent objects in the
56-
// Zephyr world.
57-
let _ = Arc::into_raw(lower_worker.clone());
58-
let _ = Arc::into_raw(worker.clone());
32+
let executor = EXECUTOR.init(Executor::new());
33+
executor.run(|spawner| {
34+
spawner.spawn(main(spawner)).unwrap();
35+
})
36+
}
37+
38+
static EXECUTOR: StaticCell<Executor> = StaticCell::new();
39+
40+
type ResultSignal = Signal<CriticalSectionRawMutex, Arc<Mutex<CriticalSectionRawMutex, Stats>>>;
41+
static RESULT_SIGNAL: ResultSignal = Signal::new();
5942

43+
#[embassy_executor::task]
44+
async fn main(spawner: Spawner) -> () {
6045
// First run the async semaphore based one.
6146
printkln!("Running 'async-sem' test");
62-
let handle = spawn(async_sem::phil(), &worker, c"async-sem");
63-
let stats = handle.join();
47+
spawner
48+
.spawn(async_sem::phil(spawner, &RESULT_SIGNAL))
49+
.unwrap();
50+
51+
let stats = RESULT_SIGNAL.wait().await;
6452
printkln!("Done with 'async-sem' test");
65-
stats.show();
53+
stats.lock().await.show();
6654

6755
printkln!("All threads done");
6856
}
6957

70-
kobj_define! {
71-
static WORK_STACK: ThreadStack<WORK_STACK_SIZE>;
72-
static LOWER_WORK_STACK: ThreadStack<WORK_STACK_SIZE>;
73-
}
74-
7558
/// Get a random delay, based on the ID of this user, and the current uptime.
7659
fn get_random_delay(id: usize, period: usize) -> Duration {
77-
let tick = (uptime_get() & (usize::MAX as i64)) as usize;
78-
let delay = (tick / 100 * (id + 1)) & 0x1f;
60+
let tick = (uptime_get() & (usize::MAX as i64)) as u64;
61+
let delay = (tick / 100 * (id as u64 + 1)) & 0x1f;
7962

8063
// Use one greater to be sure to never get a delay of zero.
81-
Duration::millis_at_least(((delay + 1) * period) as Tick)
64+
Duration::from_millis((delay + 1) * (period as u64))
8265
}
8366

8467
/// Instead of just printint out so much information that the data just scolls by, gather
@@ -95,11 +78,11 @@ struct Stats {
9578

9679
impl Stats {
9780
fn record_eat(&mut self, index: usize, time: Duration) {
98-
self.eating[index] += time.to_millis();
81+
self.eating[index] += time.as_millis();
9982
}
10083

10184
fn record_think(&mut self, index: usize, time: Duration) {
102-
self.thinking[index] += time.to_millis();
85+
self.thinking[index] += time.as_millis();
10386
self.count[index] += 1;
10487
}
10588

0 commit comments

Comments
 (0)