some_executor/task.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Task management and execution primitives for the some_executor framework.
4//!
5//! This module provides the core types and traits for creating, configuring, and managing
6//! asynchronous tasks within the executor ecosystem. Tasks are the fundamental unit of
7//! asynchronous work that executors poll to completion.
8//!
9//! # Overview
10//!
11//! The task system is built around several key concepts:
12//!
13//! - **[`Task`]**: A top-level future with metadata like labels, priorities, and hints
14//! - **[`SpawnedTask`]/[`SpawnedLocalTask`]**: Tasks that have been spawned onto an executor
15//! - **[`TaskID`]**: Unique identifiers for tracking tasks across the system
16//! - **[`Configuration`]**: Runtime hints and scheduling preferences for tasks
17//!
18//! # Task Lifecycle
19//!
20//! 1. **Creation**: Tasks are created with a future, label, and configuration
21//! 2. **Spawning**: Tasks are spawned onto an executor, producing a spawned task and observer
22//! 3. **Polling**: The executor polls the spawned task until completion
23//! 4. **Completion**: The task's result is sent to any attached observers
24//!
25//! # Examples
26//!
27//! ## Basic Task Creation and Spawning
28//!
29//! ```
30//! use some_executor::task::{Task, Configuration};
31//! use some_executor::current_executor::current_executor;
32//! use some_executor::SomeExecutor;
33//! use some_executor::observer::Observer;
34//!
35//! # async fn example() {
36//! let task = Task::without_notifications(
37//! "my-task".to_string(),
38//! Configuration::default(),
39//! async {
40//! println!("Hello from task!");
41//! 42
42//! },
43//! );
44//!
45//! let mut executor = current_executor();
46//! let observer = executor.spawn(task).detach();
47//!
48//! // Task runs asynchronously
49//! # }
50//! ```
51//!
52//! ## Task Configuration
53//!
54//! ```
55//! use some_executor::task::{Task, ConfigurationBuilder};
56//! use some_executor::hint::Hint;
57//! use some_executor::Priority;
58//!
59//! # async fn example() {
60//! let config = ConfigurationBuilder::new()
61//! .hint(Hint::CPU)
62//! .priority(Priority::unit_test())
63//! .build();
64//!
65//! let task = Task::without_notifications(
66//! "high-priority-cpu".to_string(),
67//! config,
68//! async {
69//! // Some CPU-intensive operation
70//! let mut sum = 0u64;
71//! for i in 0..1000000 {
72//! sum += i;
73//! }
74//! },
75//! );
76//! # }
77//! ```
78//!
79//! ## Task-Local Variables
80//!
81//! Tasks have access to task-local variables that provide context during execution.
82//! * [`TASK_LABEL`] - The task's label
83//! * [`TASK_ID`] - The task's unique identifier
84//! * [`IS_CANCELLED`] - Cancellation status
85//!
86
87mod config;
88mod convenience;
89mod dyn_spawned;
90mod objsafe;
91mod spawn;
92mod spawned;
93mod task_local;
94
95// Re-exports from submodules
96pub use convenience::DefaultFuture;
97pub use dyn_spawned::{DynLocalSpawnedTask, DynSpawnedTask};
98pub use objsafe::{
99 BoxedLocalFuture, BoxedLocalObserverNotifier, BoxedSendFuture, BoxedSendObserverNotifier,
100 ObjSafeLocalTask, ObjSafeStaticTask, ObjSafeTask,
101};
102pub use spawn::{
103 SpawnLocalObjSafeResult, SpawnLocalResult, SpawnObjSafeResult, SpawnResult,
104 SpawnStaticObjSafeResult, SpawnStaticResult,
105};
106pub use spawned::{SpawnedLocalTask, SpawnedStaticTask, SpawnedTask};
107pub use task_local::{
108 IS_CANCELLED, InFlightTaskCancellation, TASK_EXECUTOR, TASK_ID, TASK_LABEL,
109 TASK_LOCAL_EXECUTOR, TASK_PRIORITY, TASK_STATIC_EXECUTOR,
110};
111
112use crate::Priority;
113use crate::hint::Hint;
114use std::convert::Infallible;
115use std::fmt::Debug;
116use std::future::Future;
117use std::sync::atomic::AtomicU64;
118
119/// A unique identifier for a task.
120///
121/// `TaskID` provides a way to uniquely identify and track tasks throughout their lifecycle.
122/// Each task is assigned a unique ID when created, which remains constant even as the task
123/// moves between executors or changes state.
124///
125/// Task IDs are globally unique within a process and are generated using an atomic counter,
126/// ensuring thread-safe ID allocation.
127///
128/// # Examples
129///
130/// ```
131/// use some_executor::task::{Task, TaskID, Configuration};
132///
133/// let task = Task::without_notifications(
134/// "example".to_string(),
135/// Configuration::default(),
136/// async { 42 },
137/// );
138///
139/// let id = task.task_id();
140///
141/// // Task IDs can be converted to/from u64 for serialization
142/// let id_value = id.to_u64();
143/// let restored_id = TaskID::from_u64(id_value);
144/// assert_eq!(id, restored_id);
145/// ```
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub struct TaskID(u64);
148
149impl TaskID {
150 /// Creates a task ID from a u64 value.
151 ///
152 /// This method is primarily intended for deserialization or restoring task IDs
153 /// that were previously obtained via [`to_u64`](Self::to_u64).
154 ///
155 /// # Arguments
156 ///
157 /// * `id` - A u64 value representing a task ID
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// use some_executor::task::TaskID;
163 ///
164 /// let id = TaskID::from_u64(12345);
165 /// assert_eq!(id.to_u64(), 12345);
166 /// ```
167 pub fn from_u64(id: u64) -> Self {
168 TaskID(id)
169 }
170
171 /// Converts the task ID to a u64 value.
172 ///
173 /// This method is useful for serialization, logging, or any scenario where
174 /// you need to represent the task ID as a primitive value.
175 ///
176 /// # Examples
177 ///
178 /// ```
179 /// use some_executor::task::TaskID;
180 ///
181 /// let id = TaskID::from_u64(42);
182 /// let value = id.to_u64();
183 /// assert_eq!(value, 42);
184 /// ```
185 pub fn to_u64(self) -> u64 {
186 self.0
187 }
188}
189
190impl<F: Future, N> From<&Task<F, N>> for TaskID {
191 fn from(task: &Task<F, N>) -> Self {
192 task.task_id()
193 }
194}
195
196static TASK_IDS: AtomicU64 = AtomicU64::new(0);
197
198/// A top-level future with associated metadata for execution.
199///
200/// `Task` wraps a future with additional information that helps executors make
201/// scheduling decisions and provides context during execution. Unlike raw futures,
202/// tasks carry metadata such as:
203///
204/// - A human-readable label for debugging and monitoring
205/// - Execution hints (e.g., whether the task will block)
206/// - Priority information for scheduling
207/// - Optional notification callbacks for completion
208/// - A unique task ID for tracking
209///
210/// # Type Parameters
211///
212/// * `F` - The underlying future type
213/// * `N` - The notification type (use `Infallible` if no notifications are needed)
214///
215/// # Examples
216///
217/// ## Creating a simple task
218///
219/// ```
220/// use some_executor::task::{Task, Configuration};
221/// use std::convert::Infallible;
222///
223/// let task: Task<_, Infallible> = Task::without_notifications(
224/// "fetch-data".to_string(),
225/// Configuration::default(),
226/// async {
227/// // Simulate fetching data
228/// "data from server"
229/// },
230/// );
231/// ```
232///
233/// ## Creating a task with notifications
234///
235/// ```
236/// use some_executor::task::{Task, Configuration};
237/// use some_executor::observer::ObserverNotified;
238///
239/// # struct MyNotifier;
240/// # impl ObserverNotified<String> for MyNotifier {
241/// # fn notify(&mut self, value: &String) {}
242/// # }
243/// let notifier = MyNotifier;
244///
245/// let task = Task::with_notifications(
246/// "process-request".to_string(),
247/// Configuration::default(),
248/// Some(notifier),
249/// async {
250/// // Process and return result
251/// "processed".to_string()
252/// }
253/// );
254/// ```
255#[derive(Debug)]
256#[must_use]
257pub struct Task<F, N>
258where
259 F: Future,
260{
261 future: F,
262 hint: Hint,
263 label: String,
264 poll_after: crate::sys::Instant,
265 notifier: Option<N>,
266 priority: Priority,
267 task_id: TaskID,
268}
269
270impl<F: Future, N> Task<F, N> {
271 /// Creates a new task with optional completion notifications.
272 ///
273 /// This constructor creates a task that can optionally notify an observer when it completes.
274 /// If you don't need notifications, consider using [`without_notifications`](Self::without_notifications) instead.
275 ///
276 /// # Arguments
277 ///
278 /// * `label` - A human-readable label for debugging and monitoring
279 /// * `configuration` - Runtime hints and scheduling preferences
280 /// * `notifier` - Optional observer to notify on task completion
281 /// * `future` - The async computation to execute
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// use some_executor::task::{Task, Configuration};
287 /// use some_executor::observer::ObserverNotified;
288 ///
289 /// # struct MyNotifier;
290 /// # impl ObserverNotified<i32> for MyNotifier {
291 /// # fn notify(&mut self, value: &i32) {}
292 /// # }
293 ///
294 /// let task = Task::with_notifications(
295 /// "compute-result".to_string(),
296 /// Configuration::default(),
297 /// Some(MyNotifier),
298 /// async { 42 }
299 /// );
300 /// ```
301 pub fn with_notifications(
302 label: String,
303 configuration: Configuration,
304 notifier: Option<N>,
305 future: F,
306 ) -> Self
307 where
308 F: Future,
309 {
310 let task_id = TaskID(TASK_IDS.fetch_add(1, std::sync::atomic::Ordering::Relaxed));
311
312 assert_ne!(task_id.0, u64::MAX, "TaskID overflow");
313 Task {
314 label,
315 future,
316 hint: configuration.hint(),
317 poll_after: configuration.poll_after(),
318 priority: configuration.priority(),
319 notifier,
320 task_id,
321 }
322 }
323
324 /// Returns the execution hint for this task.
325 ///
326 /// Hints provide guidance to executors about the expected behavior of the task,
327 /// such as whether it will block or complete quickly.
328 pub fn hint(&self) -> Hint {
329 self.hint
330 }
331
332 /// Returns the human-readable label for this task.
333 ///
334 /// Labels are useful for debugging, monitoring, and identifying tasks in logs.
335 pub fn label(&self) -> &str {
336 self.label.as_ref()
337 }
338
339 /// Returns the priority of this task.
340 ///
341 /// Priority influences scheduling decisions when multiple tasks are ready to run.
342 pub fn priority(&self) -> priority::Priority {
343 self.priority
344 }
345
346 /// Returns the earliest time this task should be polled.
347 ///
348 /// Executors must not poll the task before this time. This can be used to
349 /// implement delayed execution or rate limiting.
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use some_executor::task::{Task, ConfigurationBuilder};
355 /// use some_executor::Instant;
356 /// use std::time::Duration;
357 ///
358 /// let config = ConfigurationBuilder::new()
359 /// .poll_after(Instant::now() + Duration::from_secs(5))
360 /// .build();
361 ///
362 /// let task = Task::without_notifications(
363 /// "delayed-task".to_string(),
364 /// config,
365 /// async { println!("This runs after 5 seconds"); },
366 /// );
367 /// ```
368 pub fn poll_after(&self) -> crate::sys::Instant {
369 self.poll_after
370 }
371
372 /// Returns the unique identifier for this task.
373 ///
374 /// Task IDs remain constant throughout the task's lifecycle and can be used
375 /// for tracking, debugging, and correlation.
376 pub fn task_id(&self) -> TaskID {
377 self.task_id
378 }
379
380 /// Consumes the task and returns the underlying future.
381 ///
382 /// This is useful when you need direct access to the future, but note that
383 /// you'll lose access to the task's metadata.
384 pub fn into_future(self) -> F {
385 self.future
386 }
387}
388
389//infalliable notification methods
390
391impl<F: Future> Task<F, Infallible> {
392 /// Creates a task without completion notifications.
393 ///
394 /// This is a convenience constructor for tasks that don't need observers.
395 /// It's equivalent to calling [`with_notifications`](Self::with_notifications) with `None`
396 /// but avoids the need to specify the notification type parameter.
397 ///
398 /// # Arguments
399 ///
400 /// * `label` - A human-readable label for debugging and monitoring
401 /// * `configuration` - Runtime hints and scheduling preferences
402 /// * `future` - The async computation to execute
403 ///
404 /// # Examples
405 ///
406 /// ```
407 /// use some_executor::task::{Task, Configuration};
408 ///
409 /// let task = Task::without_notifications(
410 /// "background-work".to_string(),
411 /// Configuration::default(),
412 /// async {
413 /// println!("Working in the background");
414 /// 42
415 /// }
416 /// );
417 /// ```
418 pub fn without_notifications(label: String, configuration: Configuration, future: F) -> Self {
419 Task::with_notifications(label, configuration, None, future)
420 }
421}
422
423/// Configuration options for spawning a task.
424///
425/// `Configuration` encapsulates the runtime preferences and scheduling hints for a task.
426/// These settings help executors make informed decisions about how and when to run tasks.
427///
428/// # Examples
429///
430/// ## Using the default configuration
431///
432/// ```
433/// use some_executor::task::{Task, Configuration};
434///
435/// let task = Task::without_notifications(
436/// "simple".to_string(),
437/// Configuration::default(),
438/// async { "done" },
439/// );
440/// ```
441///
442/// ## Creating a custom configuration
443///
444/// ```
445/// use some_executor::task::{Configuration, ConfigurationBuilder};
446/// use some_executor::hint::Hint;
447/// use some_executor::Priority;
448///
449/// // Build a configuration for a high-priority CPU task
450/// let config = ConfigurationBuilder::new()
451/// .hint(Hint::CPU)
452/// .priority(Priority::unit_test())
453/// .build();
454///
455/// // Or create directly
456/// let config = Configuration::new(
457/// Hint::CPU,
458/// Priority::unit_test(),
459/// some_executor::Instant::now()
460/// );
461/// ```
462pub use config::{Configuration, ConfigurationBuilder};
463
464/* boilerplates
465
466configuration - default
467
468*/
469
470/*
471I don't think it makes sense to support Clone on Task.
472That eliminates the need for PartialEq, Eq, Hash. We have ID type for this.
473
474I suppose we could implement Default with a blank task...
475
476 */
477
478/*
479Support from for the Future type
480 */
481
482impl<F: Future, N> From<F> for Task<F, N> {
483 fn from(future: F) -> Self {
484 Task::with_notifications("".to_string(), Configuration::default(), None, future)
485 }
486}
487
488/*
489Support AsRef for the underlying future type
490 */
491
492impl<F: Future, N> AsRef<F> for Task<F, N> {
493 fn as_ref(&self) -> &F {
494 &self.future
495 }
496}
497
498/*
499Support AsMut for the underlying future type
500 */
501impl<F: Future, N> AsMut<F> for Task<F, N> {
502 fn as_mut(&mut self) -> &mut F {
503 &mut self.future
504 }
505}
506
507/*
508Analogously, for spawned task...
509 */
510
511//taskID. I think we want to support various conversions to and from u64
512
513impl From<u64> for TaskID {
514 /**
515 Equivalent to [TaskID::from_u64].
516 */
517 fn from(id: u64) -> Self {
518 TaskID::from_u64(id)
519 }
520}
521
522impl From<TaskID> for u64 {
523 /**
524 Equivalent to [TaskID::to_u64].
525 */
526 fn from(id: TaskID) -> u64 {
527 id.to_u64()
528 }
529}
530
531impl AsRef<u64> for TaskID {
532 /**
533 Equiv + use<E, R, F, N> + use<E, R, F, N>alent to [TaskID::to_u64].
534 */
535 fn as_ref(&self) -> &u64 {
536 &self.0
537 }
538}
539
540/*dyn traits boilerplate
541
542Don't want to implement eq, etc. at this time –use task ID.
543
544AsRef / sure, why not
545 */
546
547#[cfg(test)]
548mod tests {
549 use crate::observer::{FinishedObservation, Observer, ObserverNotified};
550 use crate::task::{DynLocalSpawnedTask, DynSpawnedTask, SpawnedTask, Task};
551 use crate::{SomeExecutor, SomeLocalExecutor, task_local};
552 use std::any::Any;
553 use std::convert::Infallible;
554 use std::future::Future;
555 use std::pin::Pin;
556
557 #[cfg_attr(not(target_arch = "wasm32"), test)]
558 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
559 fn test_create_task() {
560 let task: Task<_, Infallible> =
561 Task::with_notifications("test".to_string(), Default::default(), None, async {});
562 assert_eq!(task.label(), "test");
563 }
564
565 #[cfg_attr(not(target_arch = "wasm32"), test)]
566 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
567 fn test_create_no_notify() {
568 let t = Task::without_notifications("test".to_string(), Default::default(), async {});
569 assert_eq!(t.label(), "test");
570 }
571 #[cfg_attr(not(target_arch = "wasm32"), test)]
572 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
573 fn test_send() {
574 task_local!(
575 static FOO: u32;
576 );
577
578 let scoped = FOO.scope(42, async {});
579
580 fn assert_send<T: Send>(_: T) {}
581 assert_send(scoped);
582 }
583
584 #[cfg_attr(not(target_arch = "wasm32"), test)]
585 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
586 fn test_dyntask_objsafe() {
587 let _d: &dyn DynSpawnedTask<Infallible>;
588 }
589
590 #[cfg_attr(not(target_arch = "wasm32"), test)]
591 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
592 fn test_send_task() {
593 #[allow(unused)]
594 fn task_check<F: Future + Send, N: Send>(task: Task<F, N>) {
595 fn assert_send<T: Send>(_: T) {}
596 assert_send(task);
597 }
598 #[allow(unused)]
599 fn task_check_sync<F: Future + Sync, N: Sync>(task: Task<F, N>) {
600 fn assert_sync<T: Sync>(_: T) {}
601 assert_sync(task);
602 }
603 #[allow(unused)]
604 fn task_check_unpin<F: Future + Unpin, N: Unpin>(task: Task<F, N>) {
605 fn assert_unpin<T: Unpin>(_: T) {}
606 assert_unpin(task);
607 }
608
609 #[allow(unused)]
610 fn spawn_check<F: Future + Send, E: SomeExecutor + Send>(
611 task: Task<F, Infallible>,
612 exec: &mut E,
613 ) where
614 F::Output: Send,
615 {
616 let spawned: SpawnedTask<F, Infallible, E> = task.spawn(exec).0;
617 fn assert_send<T: Send>(_: T) {}
618 assert_send(spawned);
619 }
620
621 #[allow(unused)]
622 fn spawn_check_sync<F: Future + Sync, E: SomeExecutor>(
623 task: Task<F, Infallible>,
624 exec: &mut E,
625 ) where
626 F::Output: Send,
627 E::ExecutorNotifier: Sync,
628 {
629 let spawned: SpawnedTask<F, Infallible, E> = task.spawn(exec).0;
630 fn assert_sync<T: Sync>(_: T) {}
631 assert_sync(spawned);
632 }
633
634 #[allow(unused)]
635 fn spawn_check_unpin<F: Future + Unpin, E: SomeExecutor + Unpin>(
636 task: Task<F, Infallible>,
637 exec: &mut E,
638 ) {
639 let spawned: SpawnedTask<F, Infallible, E> = task.spawn(exec).0;
640 fn assert_unpin<T: Unpin>(_: T) {}
641 assert_unpin(spawned);
642 }
643 }
644
645 #[cfg_attr(not(target_arch = "wasm32"), test)]
646 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
647 fn test_local_executor() {
648 #[allow(unused)]
649 struct ExLocalExecutor<'future>(
650 Vec<Pin<Box<dyn DynLocalSpawnedTask<ExLocalExecutor<'future>> + 'future>>>,
651 );
652
653 impl<'future> std::fmt::Debug for ExLocalExecutor<'future> {
654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655 f.debug_struct("ExLocalExecutor")
656 .field("tasks", &format!("{} tasks", self.0.len()))
657 .finish()
658 }
659 }
660
661 impl<'existing_tasks, 'new_task> SomeLocalExecutor<'new_task> for ExLocalExecutor<'existing_tasks>
662 where
663 'new_task: 'existing_tasks,
664 {
665 type ExecutorNotifier = Infallible;
666
667 fn spawn_local<F: Future + 'new_task, Notifier: ObserverNotified<F::Output>>(
668 &mut self,
669 task: Task<F, Notifier>,
670 ) -> impl Observer<Value = F::Output>
671 where
672 Self: Sized,
673 F::Output: 'static,
674 /* I am a little uncertain whether this is really required */
675 <F as Future>::Output: Unpin,
676 {
677 let (spawn, observer) = task.spawn_local(self);
678 let pinned_spawn = Box::pin(spawn);
679 self.0.push(pinned_spawn);
680 observer
681 }
682
683 async fn spawn_local_async<
684 F: Future + 'new_task,
685 Notifier: ObserverNotified<F::Output>,
686 >(
687 &mut self,
688 task: Task<F, Notifier>,
689 ) -> impl Observer<Value = F::Output>
690 where
691 Self: Sized,
692 F::Output: 'static,
693 {
694 let (spawn, observer) = task.spawn_local(self);
695 let pinned_spawn = Box::pin(spawn);
696 self.0.push(pinned_spawn);
697 observer
698 }
699
700 fn spawn_local_objsafe(
701 &mut self,
702 task: Task<
703 Pin<Box<dyn Future<Output = Box<dyn Any>>>>,
704 Box<dyn ObserverNotified<dyn Any + 'static>>,
705 >,
706 ) -> Box<dyn Observer<Value = Box<dyn Any>, Output = FinishedObservation<Box<dyn Any>>>>
707 {
708 let (spawn, observer) = task.spawn_local_objsafe(self);
709 let pinned_spawn = Box::pin(spawn);
710 self.0.push(pinned_spawn);
711 Box::new(observer)
712 }
713
714 fn spawn_local_objsafe_async<'s>(
715 &'s mut self,
716 task: Task<
717 Pin<Box<dyn Future<Output = Box<dyn Any>>>>,
718 Box<dyn ObserverNotified<dyn Any + 'static>>,
719 >,
720 ) -> Box<
721 dyn Future<
722 Output = Box<
723 dyn Observer<
724 Value = Box<dyn Any>,
725 Output = FinishedObservation<Box<dyn Any>>,
726 >,
727 >,
728 > + 's,
729 > {
730 let (spawn, observer) = task.spawn_local_objsafe(self);
731 let pinned_spawn = Box::pin(spawn);
732 self.0.push(pinned_spawn);
733 #[allow(clippy::type_complexity)]
734 let boxed = Box::new(observer) as Box<dyn Observer<Value = _, Output = _>>;
735 Box::new(std::future::ready(boxed))
736 }
737
738 fn executor_notifier(&mut self) -> Option<Self::ExecutorNotifier> {
739 todo!()
740 }
741 }
742 }
743
744 #[cfg_attr(not(target_arch = "wasm32"), test)]
745 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
746 fn test_into_objsafe_local_non_send() {
747 use crate::task::Configuration;
748 use std::rc::Rc;
749
750 // Create a non-Send type (Rc cannot be sent across threads)
751 let non_send_data = Rc::new(42);
752
753 // Create a future that captures the non-Send data
754 let non_send_future = async move {
755 let _captured = non_send_data; // This makes the future !Send
756 "result"
757 };
758
759 // Create a task with the non-Send future
760 let task = Task::without_notifications(
761 "non-send-task".to_string(),
762 Configuration::default(),
763 non_send_future,
764 );
765
766 // Convert to objsafe local task - this should work because we don't require Send
767 let objsafe_task = task.into_objsafe_local();
768
769 // Verify the task properties are preserved
770 assert_eq!(objsafe_task.label(), "non-send-task");
771 assert_eq!(objsafe_task.hint(), crate::hint::Hint::default());
772 assert_eq!(objsafe_task.priority(), crate::Priority::Unknown);
773
774 // This would not compile with into_objsafe() because the future is !Send:
775 // let _would_fail = task.into_objsafe(); // <- This line would cause compile error
776 // but it works with into_objsafe_local() because we don't require Send bounds
777 }
778
779 #[cfg_attr(not(target_arch = "wasm32"), test)]
780 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
781 fn test_nested_spawn_local_current_no_panic() {
782 // This test verifies that the nested submission bug is fixed
783 // The key is that it should not panic with BorrowMutError anymore
784
785 use crate::thread_executor::thread_local_executor;
786 use std::sync::{
787 Arc,
788 atomic::{AtomicBool, Ordering},
789 };
790
791 let nested_call_worked = Arc::new(AtomicBool::new(false));
792 let nested_call_worked_clone = nested_call_worked.clone();
793
794 // This should not panic with BorrowMutError
795 thread_local_executor(|_executor_rc1| {
796 // This nested call should work now
797 thread_local_executor(|_executor_rc2| {
798 // If we get here without panicking, the fix worked!
799 nested_call_worked_clone.store(true, Ordering::Relaxed);
800 });
801 });
802
803 // If we reach here, the BorrowMutError is fixed
804 assert!(
805 nested_call_worked.load(Ordering::Relaxed),
806 "Nested call should have succeeded"
807 );
808 }
809}