Skip to content

Commit c224b84

Browse files
committed
Merge branch 'no-wq-exec' into tasks-pre
2 parents eecec3f + 88759ef commit c224b84

File tree

23 files changed

+708
-1807
lines changed

23 files changed

+708
-1807
lines changed

samples/work-philosophers/CMakeLists.txt renamed to samples/async-philosophers/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
cmake_minimum_required(VERSION 3.20.0)
44

55
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6-
project(work_philosophers)
6+
project(async_philosophers)
77

88
rust_cargo_application()

samples/async-philosophers/Cargo.toml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2024 Linaro LTD
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
# This must be rustapp for now.
6+
name = "rustapp"
7+
version = "0.1.0"
8+
edition = "2021"
9+
description = "A sample hello world application in Rust"
10+
license = "Apache-2.0 or MIT"
11+
12+
[lib]
13+
crate-type = ["staticlib"]
14+
15+
[dependencies]
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+
# For real builds, you should figure out your target's tick rate and set the appropriate feature,
23+
# like in these examples. Without this, embassy-time will assume a 1Mhz tick rate, and every time
24+
# operation will involve a conversion.
25+
embassy-time = "0.4.0"
26+
# embassy-time = { version = "0.4.0", features = ["tick-hz-10_000"] }
27+
# embassy-time = { version = "0.4.0", features = ["tick-hz-100"] }
28+
29+
# Dependencies that are used by build.rs.
30+
[build-dependencies]
31+
zephyr-build = "0.1.0"
32+
33+
[profile.release]
34+
debug-assertions = true
35+
overflow-checks = true
36+
debug = true

samples/work-philosophers/prj.conf renamed to samples/async-philosophers/prj.conf

Lines changed: 1 addition & 0 deletions
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/work-philosophers/sample.yaml renamed to samples/async-philosophers/sample.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
sample:
2-
description: Philosphers, in Rust
3-
name: workq philosophers rust
2+
description: Async Philosphers, in Rust
3+
name: async philosophers rust
44
common:
55
harness: console
66
harness_config:
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
//! Async Semaphore based demo
2+
//!
3+
//! This implementation on the dining philosopher problem uses Zephyr semaphores to represent the
4+
//! forks. Each philosopher dines as per the algorithm a number of times, and when the are all
5+
//! finished, the test is considered successful. Deadlock will result in the primary thread not
6+
//! completing.
7+
//!
8+
//! Notably, this uses Rc and RefCell along with spawn_local to demonstrate that multiple async
9+
//! tasks run on the same worker do not need Send. It is just important that write operations on
10+
//! the RefCell do not `.await` or a panic is likely.
11+
12+
use embassy_executor::Spawner;
13+
use embassy_sync::{
14+
blocking_mutex::raw::CriticalSectionRawMutex,
15+
mutex::Mutex,
16+
semaphore::{FairSemaphore, Semaphore},
17+
};
18+
use embassy_time::Timer;
19+
use zephyr::{printkln, sync::Arc};
20+
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);
30+
31+
/// Number of iterations of each philospher.
32+
///
33+
/// Should be long enough to exercise the test, but too
34+
/// long and the test will timeout. The delay calculated will randomly be between 25 and 775, and
35+
/// there are two waits, so typically, each "eat" will take about a second.
36+
const EAT_COUNT: usize = 10;
37+
38+
#[embassy_executor::task]
39+
pub async fn phil(spawner: Spawner, stats_sig: &'static ResultSignal) {
40+
// Our overall stats.
41+
let stats = Arc::new(Mutex::new(Stats::default()));
42+
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();
52+
}
53+
54+
// Wait for them all to finish.
55+
DONE_SEM.acquire(NUM_PHIL).await.unwrap();
56+
57+
// Send the stats back.
58+
stats_sig.signal(stats);
59+
}
60+
61+
/// Simulate a single philospher.
62+
///
63+
/// The forks must be ordered with the first fork having th lowest number, otherwise this will
64+
/// likely deadlock.
65+
///
66+
/// This will run for EAT_COUNT times, and then return.
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+
) {
73+
for i in 0..EAT_COUNT {
74+
// Acquire the forks.
75+
// printkln!("Child {n} take left fork");
76+
forks[0].acquire(1).await.unwrap().disarm();
77+
// printkln!("Child {n} take right fork");
78+
forks[1].acquire(1).await.unwrap().disarm();
79+
80+
// printkln!("Child {n} eating");
81+
let delay = get_random_delay(n, 25);
82+
Timer::after(delay).await;
83+
stats.lock().await.record_eat(n, delay);
84+
85+
// Release the forks.
86+
// printkln!("Child {n} giving up forks");
87+
forks[1].release(1);
88+
forks[0].release(1);
89+
90+
let delay = get_random_delay(n, 25);
91+
Timer::after(delay).await;
92+
stats.lock().await.record_think(n, delay);
93+
94+
printkln!("Philospher {n} finished eating time {i}");
95+
}
96+
97+
DONE_SEM.release(1);
98+
}

samples/work-philosophers/src/lib.rs renamed to samples/async-philosophers/src/lib.rs

Lines changed: 29 additions & 46 deletions
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

samples/bench/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@ 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"] }
1717
critical-section = "1.1.2"
1818
heapless = "0.8"
1919
static_cell = "2.1"
2020

21+
embassy-executor = { version = "0.7.0", features = ["log", "task-arena-size-2048"] }
22+
embassy-sync = "0.6.2"
23+
embassy-futures = "0.1.1"
24+
25+
# Hard code the tick rate.
26+
embassy-time = { version = "0.4.0", features = ["tick-hz-10_000"] }
27+
2128
# Dependencies that are used by build.rs.
2229
[build-dependencies]
2330
zephyr-build = "0.1.0"

0 commit comments

Comments
 (0)