mlua/
thread.rs

1use std::fmt;
2use std::os::raw::{c_int, c_void};
3
4use crate::error::{Error, Result};
5use crate::function::Function;
6use crate::state::RawLua;
7use crate::traits::{FromLuaMulti, IntoLuaMulti};
8use crate::types::{LuaType, ValueRef};
9use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
10
11#[cfg(not(feature = "luau"))]
12use crate::{
13    debug::{Debug, HookTriggers},
14    types::HookKind,
15};
16
17#[cfg(feature = "async")]
18use {
19    futures_util::stream::Stream,
20    std::{
21        future::Future,
22        marker::PhantomData,
23        pin::Pin,
24        ptr::NonNull,
25        task::{Context, Poll, Waker},
26    },
27};
28
29/// Status of a Lua thread (coroutine).
30#[derive(Debug, Copy, Clone, Eq, PartialEq)]
31pub enum ThreadStatus {
32    /// The thread was just created or is suspended (yielded).
33    ///
34    /// If a thread is in this state, it can be resumed by calling [`Thread::resume`].
35    Resumable,
36    /// The thread is currently running.
37    Running,
38    /// The thread has finished executing.
39    Finished,
40    /// The thread has raised a Lua error during execution.
41    Error,
42}
43
44/// Internal representation of a Lua thread status.
45///
46/// The number in `New` and `Yielded` variants is the number of arguments pushed
47/// to the thread stack.
48#[derive(Clone, Copy)]
49enum ThreadStatusInner {
50    New(c_int),
51    Running,
52    Yielded(c_int),
53    Finished,
54    Error,
55}
56
57impl ThreadStatusInner {
58    #[cfg(feature = "async")]
59    #[inline(always)]
60    fn is_resumable(self) -> bool {
61        matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
62    }
63
64    #[cfg(feature = "async")]
65    #[inline(always)]
66    fn is_yielded(self) -> bool {
67        matches!(self, ThreadStatusInner::Yielded(_))
68    }
69}
70
71/// Handle to an internal Lua thread (coroutine).
72#[derive(Clone)]
73pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
74
75#[cfg(feature = "send")]
76unsafe impl Send for Thread {}
77#[cfg(feature = "send")]
78unsafe impl Sync for Thread {}
79
80/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
81///
82/// [`Future`]: std::future::Future
83/// [`Stream`]: futures_util::stream::Stream
84#[cfg(feature = "async")]
85#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
86#[must_use = "futures do nothing unless you `.await` or poll them"]
87pub struct AsyncThread<R> {
88    thread: Thread,
89    ret: PhantomData<fn() -> R>,
90    recycle: bool,
91}
92
93impl Thread {
94    /// Returns reference to the Lua state that this thread is associated with.
95    #[doc(hidden)]
96    #[inline(always)]
97    pub fn state(&self) -> *mut ffi::lua_State {
98        self.1
99    }
100
101    /// Resumes execution of this thread.
102    ///
103    /// Equivalent to [`coroutine.resume`].
104    ///
105    /// Passes `args` as arguments to the thread. If the coroutine has called [`coroutine.yield`],
106    /// it will return these arguments. Otherwise, the coroutine wasn't yet started, so the
107    /// arguments are passed to its main function.
108    ///
109    /// If the thread is no longer resumable (meaning it has finished execution or encountered an
110    /// error), this will return [`Error::CoroutineUnresumable`], otherwise will return `Ok` as
111    /// follows:
112    ///
113    /// If the thread calls [`coroutine.yield`], returns the values passed to `yield`. If the thread
114    /// `return`s values from its main function, returns those.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// # use mlua::{Error, Lua, Result, Thread};
120    /// # fn main() -> Result<()> {
121    /// # let lua = Lua::new();
122    /// let thread: Thread = lua.load(r#"
123    ///     coroutine.create(function(arg)
124    ///         assert(arg == 42)
125    ///         local yieldarg = coroutine.yield(123)
126    ///         assert(yieldarg == 43)
127    ///         return 987
128    ///     end)
129    /// "#).eval()?;
130    ///
131    /// assert_eq!(thread.resume::<u32>(42)?, 123);
132    /// assert_eq!(thread.resume::<u32>(43)?, 987);
133    ///
134    /// // The coroutine has now returned, so `resume` will fail
135    /// match thread.resume::<u32>(()) {
136    ///     Err(Error::CoroutineUnresumable) => {},
137    ///     unexpected => panic!("unexpected result {:?}", unexpected),
138    /// }
139    /// # Ok(())
140    /// # }
141    /// ```
142    ///
143    /// [`coroutine.resume`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.resume
144    /// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
145    pub fn resume<R>(&self, args: impl IntoLuaMulti) -> Result<R>
146    where
147        R: FromLuaMulti,
148    {
149        let lua = self.0.lua.lock();
150        let mut pushed_nargs = match self.status_inner(&lua) {
151            ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
152            _ => return Err(Error::CoroutineUnresumable),
153        };
154
155        let state = lua.state();
156        let thread_state = self.state();
157        unsafe {
158            let _sg = StackGuard::new(state);
159
160            let nargs = args.push_into_stack_multi(&lua)?;
161            if nargs > 0 {
162                check_stack(thread_state, nargs)?;
163                ffi::lua_xmove(state, thread_state, nargs);
164                pushed_nargs += nargs;
165            }
166
167            let _thread_sg = StackGuard::with_top(thread_state, 0);
168            let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
169            check_stack(state, nresults + 1)?;
170            ffi::lua_xmove(thread_state, state, nresults);
171
172            R::from_stack_multi(nresults, &lua)
173        }
174    }
175
176    /// Resumes execution of this thread, immediately raising an error.
177    ///
178    /// This is a Luau specific extension.
179    #[cfg(feature = "luau")]
180    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
181    pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
182    where
183        R: FromLuaMulti,
184    {
185        let lua = self.0.lua.lock();
186        match self.status_inner(&lua) {
187            ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
188            _ => return Err(Error::CoroutineUnresumable),
189        };
190
191        let state = lua.state();
192        let thread_state = self.state();
193        unsafe {
194            let _sg = StackGuard::new(state);
195
196            check_stack(state, 1)?;
197            error.push_into_stack(&lua)?;
198            ffi::lua_xmove(state, thread_state, 1);
199
200            let _thread_sg = StackGuard::with_top(thread_state, 0);
201            let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
202            check_stack(state, nresults + 1)?;
203            ffi::lua_xmove(thread_state, state, nresults);
204
205            R::from_stack_multi(nresults, &lua)
206        }
207    }
208
209    /// Resumes execution of this thread.
210    ///
211    /// It's similar to `resume()` but leaves `nresults` values on the thread stack.
212    unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
213        let state = lua.state();
214        let thread_state = self.state();
215        let mut nresults = 0;
216        #[cfg(not(feature = "luau"))]
217        let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
218        #[cfg(feature = "luau")]
219        let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
220        match ret {
221            ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
222            ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
223            ffi::LUA_ERRMEM => {
224                // Don't call error handler for memory errors
225                Err(pop_error(thread_state, ret))
226            }
227            _ => {
228                check_stack(state, 3)?;
229                protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
230                Err(pop_error(state, ret))
231            }
232        }
233    }
234
235    /// Gets the status of the thread.
236    pub fn status(&self) -> ThreadStatus {
237        match self.status_inner(&self.0.lua.lock()) {
238            ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable,
239            ThreadStatusInner::Running => ThreadStatus::Running,
240            ThreadStatusInner::Finished => ThreadStatus::Finished,
241            ThreadStatusInner::Error => ThreadStatus::Error,
242        }
243    }
244
245    /// Gets the status of the thread (internal implementation).
246    fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner {
247        let thread_state = self.state();
248        if thread_state == lua.state() {
249            // The thread is currently running
250            return ThreadStatusInner::Running;
251        }
252        let status = unsafe { ffi::lua_status(thread_state) };
253        let top = unsafe { ffi::lua_gettop(thread_state) };
254        match status {
255            ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
256            ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
257            ffi::LUA_OK => ThreadStatusInner::Finished,
258            _ => ThreadStatusInner::Error,
259        }
260    }
261
262    /// Sets a hook function that will periodically be called as Lua code executes.
263    ///
264    /// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
265    /// You can have multiple hooks for different threads.
266    ///
267    /// To remove a hook call [`Thread::remove_hook`].
268    ///
269    /// [`Lua::set_hook`]: crate::Lua::set_hook
270    #[cfg(not(feature = "luau"))]
271    #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
272    pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
273    where
274        F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
275    {
276        let lua = self.0.lua.lock();
277        unsafe {
278            lua.set_thread_hook(
279                self.state(),
280                HookKind::Thread(triggers, crate::types::XRc::new(callback)),
281            )
282        }
283    }
284
285    /// Removes any hook function from this thread.
286    #[cfg(not(feature = "luau"))]
287    #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
288    pub fn remove_hook(&self) {
289        let _lua = self.0.lua.lock();
290        unsafe {
291            ffi::lua_sethook(self.state(), None, 0, 0);
292        }
293    }
294
295    /// Resets a thread
296    ///
297    /// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
298    /// Returns a error in case of either the original error that stopped the thread or errors
299    /// in closing methods.
300    ///
301    /// In Luau: resets to the initial state of a newly created Lua thread.
302    /// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
303    ///
304    /// Other Lua versions can reset only new or finished threads.
305    ///
306    /// Sets a Lua function for the thread afterwards.
307    ///
308    /// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
309    pub fn reset(&self, func: Function) -> Result<()> {
310        let lua = self.0.lua.lock();
311        let thread_state = self.state();
312        unsafe {
313            let status = self.status_inner(&lua);
314            self.reset_inner(status)?;
315
316            // Push function to the top of the thread stack
317            ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
318
319            #[cfg(feature = "luau")]
320            {
321                // Inherit `LUA_GLOBALSINDEX` from the main thread
322                ffi::lua_xpush(lua.main_state(), thread_state, ffi::LUA_GLOBALSINDEX);
323                ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
324            }
325
326            Ok(())
327        }
328    }
329
330    unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> {
331        match status {
332            ThreadStatusInner::New(_) => {
333                // The thread is new, so we can just set the top to 0
334                ffi::lua_settop(self.state(), 0);
335                Ok(())
336            }
337            ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
338            ThreadStatusInner::Finished => Ok(()),
339            #[cfg(not(any(feature = "lua54", feature = "luau")))]
340            ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
341                Err(Error::runtime("cannot reset non-finished thread"))
342            }
343            #[cfg(any(feature = "lua54", feature = "luau"))]
344            ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
345                let thread_state = self.state();
346
347                #[cfg(all(feature = "lua54", not(feature = "vendored")))]
348                let status = ffi::lua_resetthread(thread_state);
349                #[cfg(all(feature = "lua54", feature = "vendored"))]
350                let status = {
351                    let lua = self.0.lua.lock();
352                    ffi::lua_closethread(thread_state, lua.state())
353                };
354                #[cfg(feature = "lua54")]
355                if status != ffi::LUA_OK {
356                    return Err(pop_error(thread_state, status));
357                }
358                #[cfg(feature = "luau")]
359                ffi::lua_resetthread(thread_state);
360
361                Ok(())
362            }
363        }
364    }
365
366    /// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
367    ///
368    /// Only resumable threads can be converted to [`AsyncThread`].
369    ///
370    /// `args` are pushed to the thread stack and will be used when the thread is resumed.
371    /// The object calls [`resume`] while polling and also allow to run Rust futures
372    /// to completion using an executor.
373    ///
374    /// Using [`AsyncThread`] as a [`Stream`] allow to iterate through [`coroutine.yield`]
375    /// values whereas [`Future`] version discards that values and poll until the final
376    /// one (returned from the thread function).
377    ///
378    /// [`Future`]: std::future::Future
379    /// [`Stream`]: futures_util::stream::Stream
380    /// [`resume`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
381    /// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// # use mlua::{Lua, Result, Thread};
387    /// use futures_util::stream::TryStreamExt;
388    /// # #[tokio::main]
389    /// # async fn main() -> Result<()> {
390    /// # let lua = Lua::new();
391    /// let thread: Thread = lua.load(r#"
392    ///     coroutine.create(function (sum)
393    ///         for i = 1,10 do
394    ///             sum = sum + i
395    ///             coroutine.yield(sum)
396    ///         end
397    ///         return sum
398    ///     end)
399    /// "#).eval()?;
400    ///
401    /// let mut stream = thread.into_async::<i64>(1)?;
402    /// let mut sum = 0;
403    /// while let Some(n) = stream.try_next().await? {
404    ///     sum += n;
405    /// }
406    ///
407    /// assert_eq!(sum, 286);
408    ///
409    /// # Ok(())
410    /// # }
411    /// ```
412    #[cfg(feature = "async")]
413    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
414    pub fn into_async<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
415    where
416        R: FromLuaMulti,
417    {
418        let lua = self.0.lua.lock();
419        if !self.status_inner(&lua).is_resumable() {
420            return Err(Error::CoroutineUnresumable);
421        }
422
423        let state = lua.state();
424        let thread_state = self.state();
425        unsafe {
426            let _sg = StackGuard::new(state);
427
428            let nargs = args.push_into_stack_multi(&lua)?;
429            if nargs > 0 {
430                check_stack(thread_state, nargs)?;
431                ffi::lua_xmove(state, thread_state, nargs);
432            }
433
434            Ok(AsyncThread {
435                thread: self,
436                ret: PhantomData,
437                recycle: false,
438            })
439        }
440    }
441
442    /// Enables sandbox mode on this thread.
443    ///
444    /// Under the hood replaces the global environment table with a new table,
445    /// that performs writes locally and proxies reads to caller's global environment.
446    ///
447    /// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox`].
448    ///
449    /// Please note that Luau links environment table with chunk when loading it into Lua state.
450    /// Therefore you need to load chunks into a thread to link with the thread environment.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// # use mlua::{Lua, Result};
456    /// # #[cfg(feature = "luau")]
457    /// # fn main() -> Result<()> {
458    /// let lua = Lua::new();
459    /// let thread = lua.create_thread(lua.create_function(|lua2, ()| {
460    ///     lua2.load("var = 123").exec()?;
461    ///     assert_eq!(lua2.globals().get::<u32>("var")?, 123);
462    ///     Ok(())
463    /// })?)?;
464    /// thread.sandbox()?;
465    /// thread.resume::<()>(())?;
466    ///
467    /// // The global environment should be unchanged
468    /// assert_eq!(lua.globals().get::<Option<u32>>("var")?, None);
469    /// # Ok(())
470    /// # }
471    ///
472    /// # #[cfg(not(feature = "luau"))]
473    /// # fn main() { }
474    /// ```
475    #[cfg(any(feature = "luau", doc))]
476    #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
477    pub fn sandbox(&self) -> Result<()> {
478        let lua = self.0.lua.lock();
479        let state = lua.state();
480        let thread_state = self.state();
481        unsafe {
482            check_stack(thread_state, 3)?;
483            check_stack(state, 3)?;
484            protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread_state))
485        }
486    }
487
488    /// Converts this thread to a generic C pointer.
489    ///
490    /// There is no way to convert the pointer back to its original value.
491    ///
492    /// Typically this function is used only for hashing and debug information.
493    #[inline]
494    pub fn to_pointer(&self) -> *const c_void {
495        self.0.to_pointer()
496    }
497}
498
499impl fmt::Debug for Thread {
500    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
501        fmt.debug_tuple("Thread").field(&self.0).finish()
502    }
503}
504
505impl PartialEq for Thread {
506    fn eq(&self, other: &Self) -> bool {
507        self.0 == other.0
508    }
509}
510
511impl LuaType for Thread {
512    const TYPE_ID: c_int = ffi::LUA_TTHREAD;
513}
514
515#[cfg(feature = "async")]
516impl<R> AsyncThread<R> {
517    #[inline(always)]
518    pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
519        self.recycle = recyclable;
520    }
521}
522
523#[cfg(feature = "async")]
524impl<R> Drop for AsyncThread<R> {
525    fn drop(&mut self) {
526        if self.recycle {
527            if let Some(lua) = self.thread.0.lua.try_lock() {
528                unsafe {
529                    let mut status = self.thread.status_inner(&lua);
530                    if matches!(status, ThreadStatusInner::Yielded(0)) {
531                        // The thread is dropped while yielded, resume it with the "terminate" signal
532                        ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
533                        if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
534                            // `new_status` should always be `ThreadStatusInner::Yielded(0)`
535                            status = new_status;
536                        }
537                    }
538
539                    // For Lua 5.4 this also closes all pending to-be-closed variables
540                    if self.thread.reset_inner(status).is_ok() {
541                        lua.recycle_thread(&mut self.thread);
542                    }
543                }
544            }
545        }
546    }
547}
548
549#[cfg(feature = "async")]
550impl<R: FromLuaMulti> Stream for AsyncThread<R> {
551    type Item = Result<R>;
552
553    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
554        let lua = self.thread.0.lua.lock();
555        let nargs = match self.thread.status_inner(&lua) {
556            ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
557            _ => return Poll::Ready(None),
558        };
559
560        let state = lua.state();
561        let thread_state = self.thread.state();
562        unsafe {
563            let _sg = StackGuard::new(state);
564            let _thread_sg = StackGuard::with_top(thread_state, 0);
565            let _wg = WakerGuard::new(&lua, cx.waker());
566
567            let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
568
569            if status.is_yielded() {
570                if nresults == 1 && is_poll_pending(thread_state) {
571                    return Poll::Pending;
572                }
573                // Continue polling
574                cx.waker().wake_by_ref();
575            }
576
577            check_stack(state, nresults + 1)?;
578            ffi::lua_xmove(thread_state, state, nresults);
579
580            Poll::Ready(Some(R::from_stack_multi(nresults, &lua)))
581        }
582    }
583}
584
585#[cfg(feature = "async")]
586impl<R: FromLuaMulti> Future for AsyncThread<R> {
587    type Output = Result<R>;
588
589    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
590        let lua = self.thread.0.lua.lock();
591        let nargs = match self.thread.status_inner(&lua) {
592            ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
593            _ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
594        };
595
596        let state = lua.state();
597        let thread_state = self.thread.state();
598        unsafe {
599            let _sg = StackGuard::new(state);
600            let _thread_sg = StackGuard::with_top(thread_state, 0);
601            let _wg = WakerGuard::new(&lua, cx.waker());
602
603            let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
604
605            if status.is_yielded() {
606                if !(nresults == 1 && is_poll_pending(thread_state)) {
607                    // Ignore values returned via yield()
608                    cx.waker().wake_by_ref();
609                }
610                return Poll::Pending;
611            }
612
613            check_stack(state, nresults + 1)?;
614            ffi::lua_xmove(thread_state, state, nresults);
615
616            Poll::Ready(R::from_stack_multi(nresults, &lua))
617        }
618    }
619}
620
621#[cfg(feature = "async")]
622#[inline(always)]
623unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
624    ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0
625}
626
627#[cfg(feature = "async")]
628struct WakerGuard<'lua, 'a> {
629    lua: &'lua RawLua,
630    prev: NonNull<Waker>,
631    _phantom: PhantomData<&'a ()>,
632}
633
634#[cfg(feature = "async")]
635impl<'lua, 'a> WakerGuard<'lua, 'a> {
636    #[inline]
637    pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
638        let prev = lua.set_waker(NonNull::from(waker));
639        Ok(WakerGuard {
640            lua,
641            prev,
642            _phantom: PhantomData,
643        })
644    }
645}
646
647#[cfg(feature = "async")]
648impl Drop for WakerGuard<'_, '_> {
649    fn drop(&mut self) {
650        self.lua.set_waker(self.prev);
651    }
652}
653
654#[cfg(test)]
655mod assertions {
656    use super::*;
657
658    #[cfg(not(feature = "send"))]
659    static_assertions::assert_not_impl_any!(Thread: Send);
660    #[cfg(feature = "send")]
661    static_assertions::assert_impl_all!(Thread: Send, Sync);
662    #[cfg(all(feature = "async", not(feature = "send")))]
663    static_assertions::assert_not_impl_any!(AsyncThread<()>: Send);
664    #[cfg(all(feature = "async", feature = "send"))]
665    static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync);
666}