Skip to main content

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}