diff --git a/src/kyron/BUILD b/src/kyron/BUILD index e745648..3e07b14 100644 --- a/src/kyron/BUILD +++ b/src/kyron/BUILD @@ -100,3 +100,15 @@ rust_binary( visibility = ["//visibility:public"], deps = _EXAMPLE_DEPS, ) + +rust_binary( + name = "safety_task", + srcs = [ + "examples/safety_task.rs", + ], + proc_macro_deps = [ + "//src/kyron-macros:runtime_macros", + ], + visibility = ["//visibility:public"], + deps = _EXAMPLE_DEPS, +) diff --git a/src/kyron/examples/safety_task.rs b/src/kyron/examples/safety_task.rs new file mode 100644 index 0000000..3435ed8 --- /dev/null +++ b/src/kyron/examples/safety_task.rs @@ -0,0 +1,69 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +use kyron::prelude::*; +use kyron::safety; +use kyron::spawn_on_dedicated; +use kyron_foundation::prelude::*; + +async fn failing_safety_task() -> Result<(), String> { + info!("Worker-N: failing_safety_task"); + Err("Intentional failure".to_string()) +} + +async fn passing_safety_task() -> Result<(), String> { + info!("Worker-N: passing_safety_task"); + Ok(()) +} + +async fn passing_non_safety_task() -> Result<(), String> { + info!("Dedicated worker (dw1): passing_non_safety_task"); + Ok(()) +} + +fn main() { + tracing_subscriber::fmt() + .with_target(false) // Optional: Remove module path + .with_max_level(Level::DEBUG) + .with_thread_ids(true) + .with_thread_names(true) + .init(); + + // Create runtime + let (builder, _engine_id) = kyron::runtime::RuntimeBuilder::new().with_engine( + ExecutionEngineBuilder::new() + .task_queue_size(256) + .enable_safety_worker(ThreadParameters::default()) + .with_dedicated_worker("dw1".into(), ThreadParameters::default()) + .workers(2), + ); + + let mut runtime = builder.build().unwrap(); + // Put programs into runtime and run them + runtime.block_on(async move { + let handle1 = safety::spawn(failing_safety_task()); + let handle2 = safety::spawn(passing_safety_task()); + let handle3 = spawn_on_dedicated(passing_non_safety_task(), "dw1".into()); + + info!("=============================== Spawned all tasks ==============================="); + + let _ = handle1.await; + info!("Safety worker: Since safety task fails, safety worker executes parent task from this statement onwards."); + let _ = handle2.await; + let _ = handle3.await; + + info!("Safety worker: Program finished running."); + }); + + info!("Exit."); +} diff --git a/src/kyron/src/scheduler/context.rs b/src/kyron/src/scheduler/context.rs index 1a90f92..b0d376a 100644 --- a/src/kyron/src/scheduler/context.rs +++ b/src/kyron/src/scheduler/context.rs @@ -318,6 +318,9 @@ pub(crate) struct WorkerContext { /// Helper flag to check if safety was enabled in runtime builder is_safety_enabled: bool, + /// This flag is used to schedule parent task of failing safety task into safety worker + schedule_safety: Cell, + wakeup_time: Cell>, } @@ -399,6 +402,7 @@ impl ContextBuilder { worker_id: Cell::new(self.worker_id.expect("Worker type must be set in context builder!")), handler: RefCell::new(Some(Rc::new(self.handle.expect("Handler type must be set in context builder!")))), is_safety_enabled: self.is_with_safety, + schedule_safety: Cell::new(false), wakeup_time: Cell::new(None), drivers: Some(self.drivers), } @@ -444,6 +448,24 @@ pub(crate) fn ctx_get_worker_id() -> WorkerId { }) } +/// +/// Set schedule safety flag +/// +#[allow(dead_code)] // To avoid error when runtime mocking feature is enabled +pub(crate) fn ctx_set_schedule_safety(val: bool) { + CTX.try_with(|ctx| ctx.borrow().as_ref().expect("Called before CTX init?").schedule_safety.set(val)) + .unwrap_or_default(); +} + +/// +/// Get schedule safety flag and clear +/// +#[allow(dead_code)] +pub(crate) fn ctx_get_schedule_safety() -> bool { + CTX.try_with(|ctx| ctx.borrow().as_ref().expect("Called before CTX init?").schedule_safety.replace(false)) + .unwrap_or_default() +} + /// /// Check if safety was enabled /// diff --git a/src/kyron/src/scheduler/join_handle.rs b/src/kyron/src/scheduler/join_handle.rs index bcb0ebd..37e4372 100644 --- a/src/kyron/src/scheduler/join_handle.rs +++ b/src/kyron/src/scheduler/join_handle.rs @@ -13,6 +13,7 @@ use kyron_foundation::prelude::*; use kyron_foundation::{not_recoverable_error, prelude::CommonErrors}; +use crate::scheduler::task::task_context::TaskContext; use crate::{ futures::{FutureInternalReturn, FutureState}, TaskRef, @@ -74,12 +75,45 @@ impl Future for JoinHandle { if was_set { FutureInternalReturn::default() } else { + // Check whether there is safety error for the completed task and this task is running on async worker + // if this task is already running on safety worker/dedicated worker, do not set the flag to schedule on safety worker. + if self.for_task.get_task_safety_error() && TaskContext::is_task_running_on_async_worker() { + // Set the flag to wake this task into safety worker + TaskContext::set_flag_to_wake_parent_task_into_safety(); + waker.wake_by_ref(); + FutureInternalReturn::polled() + } else { + let mut ret: Result = Err(CommonErrors::NoData); + let ret_as_ptr = &mut ret as *mut _; + self.for_task.get_return_val(ret_as_ptr as *mut u8); + + match ret { + Ok(v) => FutureInternalReturn::ready(Ok(v)), + Err(CommonErrors::OperationAborted) => FutureInternalReturn::ready(Err(CommonErrors::OperationAborted)), + Err(e) => { + not_recoverable_error!(with e, "There has been an error in a task that is not recoverable ({})!"); + } + } + } + } + } + FutureState::Polled => { + let waker = cx.waker(); + + // Set the waker, return values tells what have happen and took care about correct synchronization + let was_set = self.for_task.set_join_handle_waker(waker.clone()); + + if was_set { + FutureInternalReturn::default() + } else { + // Safety belows forms AqrRel so waker is really written before we do marking let mut ret: Result = Err(CommonErrors::NoData); let ret_as_ptr = &mut ret as *mut _; self.for_task.get_return_val(ret_as_ptr as *mut u8); match ret { Ok(v) => FutureInternalReturn::ready(Ok(v)), + Err(CommonErrors::NoData) => FutureInternalReturn::polled(), Err(CommonErrors::OperationAborted) => FutureInternalReturn::ready(Err(CommonErrors::OperationAborted)), Err(e) => { not_recoverable_error!(with e, "There has been an error in a task that is not recoverable ({})!"); @@ -87,21 +121,6 @@ impl Future for JoinHandle { } } } - FutureState::Polled => { - // Safety belows forms AqrRel so waker is really written before we do marking - let mut ret: Result = Err(CommonErrors::NoData); - let ret_as_ptr = &mut ret as *mut _; - self.for_task.get_return_val(ret_as_ptr as *mut u8); - - match ret { - Ok(v) => FutureInternalReturn::ready(Ok(v)), - Err(CommonErrors::NoData) => FutureInternalReturn::polled(), - Err(CommonErrors::OperationAborted) => FutureInternalReturn::ready(Err(CommonErrors::OperationAborted)), - Err(e) => { - not_recoverable_error!(with e, "There has been an error in a task that is not recoverable ({})!"); - } - } - } FutureState::Finished => { not_recoverable_error!("Future polled after it finished!"); } @@ -256,6 +275,40 @@ mod tests { assert_eq!(poller.poll(), ::core::task::Poll::Ready(Ok(0))); } } + + #[test] + fn test_join_handle_waker_is_set_in_polled_state_also() { + let scheduler = create_mock_scheduler(); + + { + // Data is present before first poll of join handle + let task = ArcInternal::new(AsyncTask::new(box_future(test_function::()), 1, scheduler.clone())); + + let handle = JoinHandle::::new(TaskRef::new(task.clone())); + + let mut poller = TestingFuturePoller::new(handle); + + let waker_mock1 = TrackableWaker::new(); + let waker1 = waker_mock1.get_waker(); + + let waker_mock2 = TrackableWaker::new(); + let waker2 = waker_mock2.get_waker(); + + let _ = poller.poll_with_waker(&waker1); + // Now in polled state, poll again with waker2 + let _ = poller.poll_with_waker(&waker2); + { + let waker = noop_waker(); + let mut cx = Context::from_waker(&waker); + task.poll(&mut cx); // task done + } + + assert!(!waker_mock1.was_waked()); + // this should be TRUE + assert!(waker_mock2.was_waked()); + assert_eq!(poller.poll(), ::core::task::Poll::Ready(Ok(0))); + } + } } #[cfg(test)] @@ -277,8 +330,9 @@ mod tests { #[test] fn test_join_handler_mt_get_result() { - let builder = Builder::new(); - + let mut builder = Builder::new(); + // Limit preemption to avoid loom error "Model exceeded maximum number of branches." + builder.preemption_bound = Some(4); builder.check(|| { let scheduler = create_mock_scheduler(); @@ -299,22 +353,17 @@ mod tests { let waker_mock = TrackableWaker::new(); let waker = waker_mock.get_waker(); - let mut was_pending = false; - loop { match poller.poll_with_waker(&waker) { Poll::Ready(v) => { assert_eq!(v, Ok(1234)); - - if was_pending { - assert!(waker_mock.was_waked()); - } + // Note: + // Cannot check whether the waker was woken or not since the waker is set in the join handle poll every time if task is not yet done. + // So depending on the interleaving, the task may finish before the waker is set. break; } - Poll::Pending => { - was_pending = true; - } + Poll::Pending => {} } loom::hint::spin_loop(); } diff --git a/src/kyron/src/scheduler/safety_waker.rs b/src/kyron/src/scheduler/safety_waker.rs index 977c518..7c0c21b 100644 --- a/src/kyron/src/scheduler/safety_waker.rs +++ b/src/kyron/src/scheduler/safety_waker.rs @@ -12,6 +12,7 @@ // use super::task::async_task::*; +use crate::scheduler::task::task_context::TaskContext; use core::task::{RawWaker, RawWakerVTable, Waker}; fn clone_waker(data: *const ()) -> RawWaker { @@ -30,6 +31,9 @@ fn wake(data: *const ()) { let task_header_ptr = data as *const TaskHeader; let task_ref = unsafe { TaskRef::from_raw(task_header_ptr) }; + // Just clear the flag which might have been set by async worker before calling wake/wake_by_ref + // for the scenario where the join handle poll is executed by safety worker and waker is set + TaskContext::clear_schedule_safety_flag(); task_ref.schedule_safety(); } @@ -37,6 +41,8 @@ fn wake_by_ref(data: *const ()) { let task_header_ptr = data as *const TaskHeader; let task_ref = unsafe { TaskRef::from_raw(task_header_ptr) }; + // Just clear the flag which might have been set by async worker before calling wake/wake_by_ref + TaskContext::clear_schedule_safety_flag(); task_ref.schedule_safety_by_ref(); ::core::mem::forget(task_ref); // don't touch refcount from our data since this is done by drop_waker @@ -55,11 +61,9 @@ static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_waker, wake, wake_by_r /// /// Waker will store internally a pointer to the ref counted Task. /// -pub(crate) unsafe fn create_safety_waker(waker: Waker) -> Waker { - let raw_waker = RawWaker::new(waker.data(), &VTABLE); - - // Forget original as we took over the ownership, so ref count - ::core::mem::forget(waker); +pub(crate) fn create_safety_waker(ptr: TaskRef) -> Waker { + let ptr = TaskRef::into_raw(ptr); // Extracts the pointer from TaskRef not decreasing it's reference count. Since we have a clone here, ref cnt was already increased + let raw_waker = RawWaker::new(ptr as *const (), &VTABLE); // Convert RawWaker to Waker unsafe { Waker::from_raw(raw_waker) } diff --git a/src/kyron/src/scheduler/task/async_task.rs b/src/kyron/src/scheduler/task/async_task.rs index 7e2617c..b5affb2 100644 --- a/src/kyron/src/scheduler/task/async_task.rs +++ b/src/kyron/src/scheduler/task/async_task.rs @@ -13,8 +13,8 @@ use super::task_state::*; use crate::core::types::*; -use crate::scheduler::safety_waker::create_safety_waker; use crate::scheduler::scheduler_mt::SchedulerTrait; +use crate::scheduler::task::task_context::TaskContext; use ::core::future::Future; use ::core::mem; use ::core::ops::{Deref, DerefMut}; @@ -82,8 +82,8 @@ pub(crate) struct TaskHeader { pub(in crate::scheduler) state: TaskState, #[allow(dead_code)] id: TaskId, - - vtable: &'static TaskVTable, // API entrypoint to typed task + is_safety_error: UnsafeCell, // Flag to indicate whether task resulted in safety error + vtable: &'static TaskVTable, // API entrypoint to typed task } impl TaskHeader { @@ -97,6 +97,7 @@ impl TaskHeader { Self { state: TaskState::new(), id: TaskId::new(worker_id), + is_safety_error: UnsafeCell::new(false), vtable: create_task_vtable::(), } } @@ -111,9 +112,20 @@ impl TaskHeader { Self { state: TaskState::new(), id: TaskId::new(worker_id), + is_safety_error: UnsafeCell::new(false), vtable: create_task_s_vtable::(), } } + + pub(crate) fn set_safety_error(&self) { + self.is_safety_error.with_mut(|ptr| { + unsafe { *ptr = true }; + }) + } + + pub(crate) fn get_safety_error(&self) -> bool { + self.is_safety_error.with(|ptr| unsafe { *ptr }) + } } #[derive(PartialEq, Debug)] @@ -203,13 +215,20 @@ where } pub(crate) fn set_waker(&self, waker: Waker) -> bool { - unsafe { - self.handle_waker.with_mut(|ptr| { - *ptr = Some(waker); - }) + // Safety: Unset join handle flag before setting waker, the flag would have been set previously in the first poll of join handle. + // If flag is not cleared, another worker finishing the task will see the flag set and + // read the waker to call wake() while it is written here + if self.header.state.unset_join_handle() { + unsafe { + self.handle_waker.with_mut(|ptr| { + *ptr = Some(waker); + }) + }; + + return self.header.state.set_join_handle(); } - self.header.state.set_waker() // Safety: makes sure storing waker is not reordered behind this operation + false } /// @@ -300,6 +319,10 @@ where }); res.unwrap_or_else(|| { + if is_safety_err && self.is_with_safety { + // Set saftey error flag which would be checked in JoinHandle poll() to schedule parent task into safety worker + self.header.set_safety_error(); + } // Finish when future was done, but not under opened writable cell that can cause bugs if someone reorder some instructions, here we are sure let status = self.header.state.transition_to_completed(); match status { @@ -308,12 +331,9 @@ where self.handle_waker.with_mut(|ptr: *mut Option| match unsafe { (*ptr).take() } { Some(v) => { if is_safety_err && self.is_with_safety { - unsafe { - create_safety_waker(v).wake(); - } - } else { - v.wake(); + TaskContext::set_flag_to_wake_parent_task_into_safety(); } + v.wake(); } None => not_recoverable_error!("We shall never be here if we have HadConnectedJoinHandle set!"), }) @@ -613,6 +633,14 @@ impl TaskRef { snapshot.is_completed() || snapshot.is_canceled() } + + pub(crate) fn get_task_safety_error(&self) -> bool { + unsafe { self.header.as_ref().get_safety_error() } + } + + pub(crate) fn is_safety_notified(&self) -> bool { + unsafe { self.header.as_ref().state.get().is_safety_notified() } + } } impl Drop for TaskRef { diff --git a/src/kyron/src/scheduler/task/mod.rs b/src/kyron/src/scheduler/task/mod.rs index 786b74a..25ce757 100644 --- a/src/kyron/src/scheduler/task/mod.rs +++ b/src/kyron/src/scheduler/task/mod.rs @@ -12,4 +12,5 @@ // pub mod async_task; +pub mod task_context; pub mod task_state; diff --git a/src/kyron/src/scheduler/task/task_context.rs b/src/kyron/src/scheduler/task/task_context.rs new file mode 100644 index 0000000..0bd5fb5 --- /dev/null +++ b/src/kyron/src/scheduler/task/task_context.rs @@ -0,0 +1,40 @@ +// +// Copyright (c) 2025 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// +// +// SPDX-License-Identifier: Apache-2.0 +// + +#[cfg(not(any(test, feature = "runtime-api-mock")))] +use crate::scheduler::context::{ctx_get_schedule_safety, ctx_get_worker_id, ctx_set_schedule_safety}; +use crate::scheduler::workers::worker_types::WorkerType; +#[cfg(any(test, feature = "runtime-api-mock"))] +use crate::testing::mock::{ctx_get_schedule_safety, ctx_get_worker_id, ctx_set_schedule_safety}; + +/// Contains functions to set/get a flag in worker's context. +/// The flag is used to schedule parent task of failing safety task into safety worker. +pub(crate) struct TaskContext {} + +impl TaskContext { + pub(crate) fn set_flag_to_wake_parent_task_into_safety() { + ctx_set_schedule_safety(true); + } + + pub(crate) fn clear_schedule_safety_flag() { + ctx_set_schedule_safety(false); + } + + pub(crate) fn should_wake_task_into_safety() -> bool { + ctx_get_schedule_safety() + } + + pub(crate) fn is_task_running_on_async_worker() -> bool { + ctx_get_worker_id().typ() == WorkerType::Async + } +} diff --git a/src/kyron/src/scheduler/task/task_state.rs b/src/kyron/src/scheduler/task/task_state.rs index a8dc305..56734df 100644 --- a/src/kyron/src/scheduler/task/task_state.rs +++ b/src/kyron/src/scheduler/task/task_state.rs @@ -127,6 +127,11 @@ impl TaskStateSnapshot { self.0 |= TASK_JOIN_HANDLE_ATTACHED; } + #[inline(always)] + pub(crate) fn unset_join_handle(&mut self) { + self.0 &= !TASK_JOIN_HANDLE_ATTACHED; + } + #[inline(always)] pub(crate) fn set_running(&mut self) { let mask = TASK_STATE_RUNNING | TASK_STATE_IDLE; @@ -274,7 +279,7 @@ impl TaskState { /// /// Returns result of transition /// - pub(crate) fn set_waker(&self) -> bool { + pub(crate) fn set_join_handle(&self) -> bool { self.fetch_update_with_return(|old: TaskStateSnapshot| { if old.is_completed() || old.is_canceled() { return (None, false); @@ -286,6 +291,25 @@ impl TaskState { }) } + /// + /// Unset join handle attached flag if task is not already completed or canceled. + /// + pub(crate) fn unset_join_handle(&self) -> bool { + self.fetch_update_with_return(|old: TaskStateSnapshot| { + if old.is_completed() || old.is_canceled() { + return (None, false); + } + + if !old.has_join_handle() { + return (None, true); + } + + let mut new = old; + new.unset_join_handle(); + (Some(new), true) + }) + } + /// /// Return true if task was notified before, otherwise false /// diff --git a/src/kyron/src/scheduler/waker.rs b/src/kyron/src/scheduler/waker.rs index e26444c..7d2fd19 100644 --- a/src/kyron/src/scheduler/waker.rs +++ b/src/kyron/src/scheduler/waker.rs @@ -14,6 +14,7 @@ use kyron_foundation::prelude::FoundationAtomicPtr; use super::task::async_task::*; +use crate::scheduler::task::task_context::TaskContext; use core::task::{RawWaker, RawWakerVTable, Waker}; fn clone_waker(data: *const ()) -> RawWaker { @@ -31,15 +32,21 @@ fn clone_waker(data: *const ()) -> RawWaker { fn wake(data: *const ()) { let task_header_ptr = data as *const TaskHeader; let task_ref = unsafe { TaskRef::from_raw(task_header_ptr) }; - - task_ref.schedule(); + if TaskContext::should_wake_task_into_safety() { + task_ref.schedule_safety(); + } else { + task_ref.schedule(); + } } fn wake_by_ref(data: *const ()) { let task_header_ptr = data as *const TaskHeader; let task_ref = unsafe { TaskRef::from_raw(task_header_ptr) }; - - task_ref.schedule_by_ref(); + if TaskContext::should_wake_task_into_safety() { + task_ref.schedule_safety_by_ref(); + } else { + task_ref.schedule_by_ref(); + } ::core::mem::forget(task_ref); // don't touch refcount from our data since this is done by drop_waker } diff --git a/src/kyron/src/scheduler/workers/safety_worker.rs b/src/kyron/src/scheduler/workers/safety_worker.rs index 57e316d..702f35d 100644 --- a/src/kyron/src/scheduler/workers/safety_worker.rs +++ b/src/kyron/src/scheduler/workers/safety_worker.rs @@ -23,9 +23,9 @@ use crate::{ scheduler::{ context::{ctx_initialize, ContextBuilder}, driver::Drivers, + safety_waker::create_safety_waker, scheduler_mt::{AsyncScheduler, DedicatedScheduler}, task::async_task::TaskPollResult, - waker::create_waker, }, TaskRef, }; @@ -170,7 +170,7 @@ impl WorkerInner { } fn run_task(&mut self, task: TaskRef) { - let waker = create_waker(task.clone()); + let waker = create_safety_waker(task.clone()); let mut ctx = Context::from_waker(&waker); match task.poll(&mut ctx) { TaskPollResult::Done => { diff --git a/src/kyron/src/scheduler/workers/worker.rs b/src/kyron/src/scheduler/workers/worker.rs index 10ab490..03310b9 100644 --- a/src/kyron/src/scheduler/workers/worker.rs +++ b/src/kyron/src/scheduler/workers/worker.rs @@ -23,7 +23,7 @@ use kyron_foundation::threading::thread_wait_barrier::ThreadReadyNotifier; use crate::scheduler::{ context::{ctx_get_worker_id, ctx_initialize, ContextBuilder}, - scheduler_mt::AsyncScheduler, + scheduler_mt::{AsyncScheduler, SchedulerTrait}, task::async_task::*, workers::{spawn_thread, ThreadParameters}, }; @@ -221,8 +221,12 @@ impl WorkerInner { // Literally nothing to do ;) } TaskPollResult::Notified => { - // For now stupid respawn - self.scheduler.spawn_from_runtime(task, &self.producer_consumer); + if task.is_safety_notified() { + self.scheduler.respawn_into_safety(task); + } else { + // For now stupid respawn + self.scheduler.spawn_from_runtime(task, &self.producer_consumer); + } } } } diff --git a/src/kyron/src/testing/mock.rs b/src/kyron/src/testing/mock.rs index 301ef29..cf1d20b 100644 --- a/src/kyron/src/testing/mock.rs +++ b/src/kyron/src/testing/mock.rs @@ -16,7 +16,12 @@ use crate::{ core::types::{box_future, FutureBox, UniqueWorkerId}, futures::reusable_box_future::ReusableBoxFuture, - scheduler::{join_handle::JoinHandle, task::async_task::TaskRef, waker::create_waker}, + scheduler::{ + join_handle::JoinHandle, + task::async_task::TaskRef, + waker::create_waker, + workers::worker_types::{WorkerId, WorkerType}, + }, testing::*, }; use ::core::{cell::RefCell, future::Future, sync::atomic, task::Context}; @@ -225,6 +230,23 @@ where } } +// Thread-local storage to set/get schedule safety flag +use ::core::cell::Cell; +thread_local! { + static CTX_SCHEDULE_SAFETY: Cell = const { Cell::new(false) }; +} + +pub fn ctx_set_schedule_safety(val: bool) { + CTX_SCHEDULE_SAFETY.set(val); +} + +pub fn ctx_get_schedule_safety() -> bool { + CTX_SCHEDULE_SAFETY.replace(true) +} +#[allow(private_interfaces)] +pub fn ctx_get_worker_id() -> WorkerId { + WorkerId::new(UniqueWorkerId::from("test_worker"), 0, 0, WorkerType::Async) +} /// /// Spawns a given `future` into runtime and let it execute on dedicated worker using `worker_id`. /// This function allocates a `future` dynamically using [`Box`] diff --git a/src/kyron/src/testing/mod.rs b/src/kyron/src/testing/mod.rs index 580ccbc..119a411 100644 --- a/src/kyron/src/testing/mod.rs +++ b/src/kyron/src/testing/mod.rs @@ -61,7 +61,9 @@ impl SchedulerTrait for SchedulerSyncMock { } fn respawn_into_safety(&self, _task: TaskRef) { - todo!() + //todo!() + *self.mtx.lock().unwrap() = true; + self.cv.notify_one(); } } diff --git a/tests/test_cases/tests/runtime/worker/test_safety_worker.py b/tests/test_cases/tests/runtime/worker/test_safety_worker.py index 555ce76..cfd54cf 100644 --- a/tests/test_cases/tests/runtime/worker/test_safety_worker.py +++ b/tests/test_cases/tests/runtime/worker/test_safety_worker.py @@ -120,7 +120,6 @@ def test_safety_enabled( # region safety worker core -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestTaskHandling(CitScenario): @pytest.fixture(scope="class") def scenario_name(self) -> str: @@ -169,7 +168,6 @@ def test_safety_worker_uniqueness( ) -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestNestedTaskHandling(CitScenario): @pytest.fixture(scope="class") def scenario_name(self) -> str: @@ -425,7 +423,6 @@ def results( @pytest.mark.root_required @pytest.mark.skipif("WSL" in platform(), reason="Not supported on WSL") -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSafeWorkerThreadParameters(ThreadParametersBase): @pytest.fixture(scope="class") def scheduler(self) -> str: @@ -481,7 +478,6 @@ def test_safety_worker_thread_params( @pytest.mark.root_required @pytest.mark.skipif("WSL" in platform(), reason="Not supported on WSL") -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSafeWorkerMissingThreadParameterScheduler(ThreadParametersNegativeBase): @pytest.fixture(scope="class") def priority(self) -> int: @@ -502,7 +498,6 @@ def test_config(self, priority: int) -> dict[str, Any]: @pytest.mark.root_required @pytest.mark.skipif("WSL" in platform(), reason="Not supported on WSL") -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSafeWorkerMissingThreadParameterPriority(ThreadParametersNegativeBase): @pytest.fixture(scope="class") def scheduler(self) -> str: @@ -523,7 +518,6 @@ def test_config(self, scheduler: str) -> dict[str, Any]: @pytest.mark.root_required @pytest.mark.skipif("WSL" in platform(), reason="Not supported on WSL") -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSafeWorkerMissingThreadParameters(ThreadParametersNegativeBase): @pytest.fixture(scope="class") def test_config(self) -> dict[str, Any]: @@ -540,7 +534,6 @@ def test_config(self) -> dict[str, Any]: # region spawn methods -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSpawnFromBoxed(CitScenario): @pytest.fixture(scope="class") def scenario_name(self) -> str: @@ -569,14 +562,12 @@ def test_safety_worker_handler( ) -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSpawnFromReusable(TestSpawnFromBoxed): @pytest.fixture(scope="class") def scenario_name(self) -> str: return "runtime.worker.safety_worker.spawn_from_reusable" -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSpawnOnDedicated(CitScenario): @pytest.fixture(scope="class") def scenario_name(self) -> str: @@ -610,14 +601,12 @@ def test_task_to_worker_assignment( assert len(all_thread_ids) == 3, "Each task should be executed on its own thread" -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSpawnFromBoxedOnDedicated(CitScenario): @pytest.fixture(scope="class") def scenario_name(self) -> str: return "runtime.worker.safety_worker.spawn_from_boxed_on_dedicated" -@pytest.mark.xfail(reason="https://github.com/qorix-group/inc_orchestrator_internal/issues/397") class TestSpawnFromReusableOnDedicated(CitScenario): @pytest.fixture(scope="class") def scenario_name(self) -> str: