1use std::cell::Cell;
27use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
28use std::sync::OnceLock;
29
30use crate::state_stub::{lua_CFunction, upvalue_index, LuaState, LuaStateStubExt as _};
31use lua_types::{error::LuaError, gc::GcRef, value::LuaValue, LuaStatus, LuaThreadClose, LuaType};
32
33thread_local! {
34 static THREAD_CLOSE_SUPPRESS: Cell<u32> = const { Cell::new(0) };
52}
53
54static CHAINING_HOOK_INSTALLED: OnceLock<()> = OnceLock::new();
56
57struct SuppressGuard;
65
66impl SuppressGuard {
67 fn new() -> Self {
68 THREAD_CLOSE_SUPPRESS.with(|c| c.set(c.get() + 1));
69 SuppressGuard
70 }
71}
72
73impl Drop for SuppressGuard {
74 fn drop(&mut self) {
75 THREAD_CLOSE_SUPPRESS.with(|c| c.set(c.get().saturating_sub(1)));
76 }
77}
78
79fn ensure_chaining_panic_hook() {
106 CHAINING_HOOK_INSTALLED.get_or_init(|| {
107 let previous = std::panic::take_hook();
108 std::panic::set_hook(Box::new(move |info| {
109 let suppress = info.payload().downcast_ref::<LuaThreadClose>().is_some()
110 && THREAD_CLOSE_SUPPRESS.with(|c| c.get()) > 0;
111 if !suppress {
112 previous(info);
113 }
114 }));
115 });
116}
117
118const COS_RUN: i32 = 0;
122
123const COS_DEAD: i32 = 1;
125
126const COS_YIELD: i32 = 2;
128
129const COS_NORM: i32 = 3;
131
132const STAT_NAMES: [&[u8]; 4] = [b"running", b"dead", b"suspended", b"normal"];
135
136pub const CO_FUNCS: &[(&[u8], lua_CFunction)] = &[
143 (b"create", co_create),
144 (b"resume", co_resume),
145 (b"running", co_running),
146 (b"status", co_status),
147 (b"wrap", co_wrap),
148 (b"yield", co_yield),
149 (b"isyieldable", co_isyieldable),
150 (b"close", co_close),
151];
152
153fn get_co(state: &mut LuaState) -> Result<GcRef<lua_types::value::LuaThread>, LuaError> {
164 let co = state.to_thread(1);
165 if let Some(co) = co {
166 return Ok(co);
167 }
168 Err(thread_arg_error(state, 1))
169}
170
171fn thread_arg_error(state: &mut LuaState, arg: i32) -> LuaError {
176 use lua_types::LuaVersion;
177 let version = state.global().lua_version;
178 if matches!(version, LuaVersion::V51 | LuaVersion::V52) {
179 return lua_vm::debug::arg_error_impl(state, arg, b"coroutine expected");
180 }
181 if matches!(version, LuaVersion::V53) {
182 return lua_vm::debug::arg_error_impl(state, arg, b"thread expected");
183 }
184 let got = state.value_at(arg);
185 let got_name = match state.full_type_name(&got) {
186 Ok(n) => n,
187 Err(e) => return e,
188 };
189 let mut extramsg = b"thread expected, got ".to_vec();
190 extramsg.extend_from_slice(&got_name);
191 lua_vm::debug::arg_error_impl(state, arg, &extramsg)
192}
193
194fn get_opt_co(state: &mut LuaState) -> Result<GcRef<lua_types::value::LuaThread>, LuaError> {
195 if matches!(state.global().lua_version, lua_types::LuaVersion::V55)
196 && state.type_at(1) == LuaType::None
197 {
198 let id = state.global().current_thread_id;
199 return state
200 .global()
201 .thread_value_for(id)
202 .ok_or_else(|| LuaError::runtime(format_args!("current thread is not registered")));
203 }
204 get_co(state)
205}
206
207fn aux_status(state: &mut LuaState, co: &GcRef<lua_types::value::LuaThread>) -> i32 {
223 let co_id = co.id;
224 let entry_rc = {
225 let g = state.global();
226 if co_id == g.current_thread_id {
227 return COS_RUN;
228 }
229 if co_id == g.main_thread_id {
230 return COS_NORM;
231 }
232 match g.threads.get(&co_id) {
233 Some(e) => e.state.clone(),
234 None => return COS_DEAD,
235 }
236 };
237 let co_state = match entry_rc.try_borrow() {
238 Ok(state) => state,
239 Err(_) => {
240 return COS_NORM;
244 }
245 };
246 let raw_status = co_state.status;
247 if raw_status == LuaStatus::Yield as u8 {
248 return COS_YIELD;
249 }
250 if raw_status != LuaStatus::Ok as u8 {
251 return COS_DEAD;
252 }
253 let has_frames = co_state.ci.as_usize() > 0;
254 if has_frames {
255 return COS_NORM;
256 }
257 let ci_func = co_state.call_info[0].func.0;
258 let top = co_state.top.0;
259 let lua_gettop = top as i64 - ci_func as i64 - 1;
260 if lua_gettop == 0 {
261 COS_DEAD
262 } else {
263 COS_YIELD
264 }
265}
266
267fn aux_resume(state: &mut LuaState, co: GcRef<lua_types::value::LuaThread>, narg: i32) -> i32 {
292 let co_id = co.id;
293 let entry_rc = {
294 let g = state.global();
295 match g.threads.get(&co_id) {
296 Some(e) => e.state.clone(),
297 None => {
298 let is_main = co_id == g.main_thread_id;
299 drop(g);
300 if is_main {
301 let msg = non_suspended_resume_message(state);
302 push_lit_or_nil(state, msg);
303 } else {
304 push_lit_or_nil(state, b"cannot resume dead coroutine");
305 }
306 return -1;
307 }
308 }
309 };
310 let parent_thread_id = state.global().current_thread_id;
311 let top_before = state.get_top();
312 if top_before < narg {
313 push_lit_or_nil(state, b"not enough arguments to resume");
314 return -1;
315 }
316 let first_arg_idx = top_before - narg + 1;
317 let mut args = pop_resume_value_buf(state);
318 args.extend((first_arg_idx..=top_before).map(|i| state.value_at(i)));
319 lua_vm::api::set_top(state, (top_before - narg) as i32).ok();
320
321 let mut parent_open_upval_slots = pop_resume_slot_buf(state);
322 parent_open_upval_slots.extend(state.openupval.iter().filter_map(|uv| {
323 uv.try_open_payload()
324 .map(|(thread_id, idx)| (thread_id as u64, idx))
325 }));
326 {
327 let mut g = state.global_mut();
328 for (tid, idx) in &parent_open_upval_slots {
329 let val = state.get_at(*idx);
330 g.cross_thread_upvals.insert((*tid, *idx), val);
331 }
332 }
333
334 push_parent_gc_snapshot(state);
335
336 let (status, results_or_err): (LuaStatus, Vec<LuaValue>) = {
337 let mut co_state = match entry_rc.try_borrow_mut() {
338 Ok(b) => b,
339 Err(_) => {
340 pop_parent_gc_snapshot(state);
341 let mut g = state.global_mut();
342 for (tid, idx) in &parent_open_upval_slots {
343 g.cross_thread_upvals.remove(&(*tid, *idx));
344 }
345 drop(g);
346 return_resume_slot_buf(state, parent_open_upval_slots);
347 return_resume_value_buf(state, args);
348 let msg = non_suspended_resume_message(state);
349 push_lit_or_nil(state, msg);
350 return -1;
351 }
352 };
353 if co_state.check_stack(narg + 1).is_err() {
354 drop(co_state);
355 pop_parent_gc_snapshot(state);
356 let mut g = state.global_mut();
357 for (tid, idx) in &parent_open_upval_slots {
358 g.cross_thread_upvals.remove(&(*tid, *idx));
359 }
360 drop(g);
361 return_resume_slot_buf(state, parent_open_upval_slots);
362 return_resume_value_buf(state, args);
363 push_lit_or_nil(state, b"too many arguments to resume");
364 return -1;
365 }
366 for v in args.drain(..) {
367 co_state.push(v);
368 }
369 return_resume_value_buf(state, args);
370 co_state.global_mut().current_thread_id = co_id;
371 let mut nres: i32 = 0;
372 ensure_chaining_panic_hook();
373 let resume_result = {
374 let _suppress = SuppressGuard::new();
375 catch_unwind(AssertUnwindSafe(|| {
376 lua_vm::do_::lua_resume(&mut *co_state, Some(state), narg, &mut nres)
377 }))
378 };
379 co_state.global_mut().current_thread_id = parent_thread_id;
380 let status = match resume_result {
381 Ok(status) => status,
382 Err(payload) => {
383 if let Some(close) = payload.downcast_ref::<LuaThreadClose>() {
384 close.0
385 } else {
386 resume_unwind(payload);
387 }
388 }
389 };
390 let co_top = co_state.top_idx().0 as i32;
391 let ci_func = co_state.current_call_info().func.0 as i32;
392 let count = if status == LuaStatus::Ok || status == LuaStatus::Yield {
393 nres
394 } else {
395 1
396 };
397 let start = co_top - count;
398 let mut vals = pop_resume_value_buf(state);
399 vals.extend((start..co_top).map(|i| co_state.get_at(lua_vm::state::StackIdx(i as u32))));
400 let new_co_top = if status == LuaStatus::Ok || status == LuaStatus::Yield {
401 (co_top - count).max(ci_func + 1)
402 } else {
403 co_top - count
404 };
405 co_state.set_top(lua_vm::state::StackIdx(new_co_top.max(0) as u32));
406 (status, vals)
407 };
408
409 pop_parent_gc_snapshot(state);
411
412 {
413 let mut flush = pop_resume_flush_buf(state);
414 let mut g = state.global_mut();
415 for (tid, idx) in &parent_open_upval_slots {
416 if let Some(v) = g.cross_thread_upvals.remove(&(*tid, *idx)) {
417 flush.push((*idx, v));
418 }
419 }
420 drop(g);
421 for (idx, v) in flush.drain(..) {
422 state.set_at(idx, v);
423 }
424 return_resume_flush_buf(state, flush);
425 }
426 return_resume_slot_buf(state, parent_open_upval_slots);
427
428 let mut results_or_err = results_or_err;
429 match status {
430 LuaStatus::Ok | LuaStatus::Yield => {
431 if state.check_stack(results_or_err.len() as i32 + 1).is_err() {
432 return_resume_value_buf(state, results_or_err);
433 push_lit_or_nil(state, b"too many results to resume");
434 return -1;
435 }
436 let n = results_or_err.len();
437 for v in results_or_err.drain(..) {
438 state.push(v);
439 }
440 return_resume_value_buf(state, results_or_err);
441 n as i32
442 }
443 _ => {
444 for v in results_or_err.drain(..) {
445 state.push(v);
446 }
447 return_resume_value_buf(state, results_or_err);
448 -1
449 }
450 }
451}
452
453fn push_parent_gc_snapshot(state: &mut LuaState) {
454 let top = (state.top_idx().0 as usize).min(state.stack.len());
455 let (mut stack_snapshot, mut upval_snapshot) = {
456 let mut g = state.global_mut();
457 (
458 g.snapshot_stack_pool.pop().unwrap_or_default(),
459 g.snapshot_upval_pool.pop().unwrap_or_default(),
460 )
461 };
462 stack_snapshot.extend(state.stack[..top].iter().map(|sv| sv.val));
463 upval_snapshot.extend(state.openupval.iter().cloned());
464 let mut g = state.global_mut();
465 g.suspended_parent_stacks.push(stack_snapshot);
466 g.suspended_parent_open_upvals.push(upval_snapshot);
467}
468
469fn pop_parent_gc_snapshot(state: &mut LuaState) {
470 let mut g = state.global_mut();
471 if let Some(mut v) = g.suspended_parent_open_upvals.pop() {
472 v.clear();
473 g.snapshot_upval_pool.push(v);
474 }
475 if let Some(mut v) = g.suspended_parent_stacks.pop() {
476 v.clear();
477 g.snapshot_stack_pool.push(v);
478 }
479}
480
481fn pop_resume_slot_buf(state: &mut LuaState) -> Vec<(u64, lua_vm::state::StackIdx)> {
486 state.global_mut().resume_upval_slot_pool.pop().unwrap_or_default()
487}
488
489fn return_resume_slot_buf(state: &mut LuaState, mut buf: Vec<(u64, lua_vm::state::StackIdx)>) {
492 buf.clear();
493 state.global_mut().resume_upval_slot_pool.push(buf);
494}
495
496fn pop_resume_value_buf(state: &mut LuaState) -> Vec<LuaValue> {
500 state.global_mut().resume_value_pool.pop().unwrap_or_default()
501}
502
503fn return_resume_value_buf(state: &mut LuaState, mut buf: Vec<LuaValue>) {
506 buf.clear();
507 state.global_mut().resume_value_pool.push(buf);
508}
509
510fn pop_resume_flush_buf(state: &mut LuaState) -> Vec<(lua_vm::state::StackIdx, LuaValue)> {
514 state.global_mut().resume_flush_pool.pop().unwrap_or_default()
515}
516
517fn return_resume_flush_buf(state: &mut LuaState, mut buf: Vec<(lua_vm::state::StackIdx, LuaValue)>) {
520 buf.clear();
521 state.global_mut().resume_flush_pool.push(buf);
522}
523
524#[cfg(feature = "debug")]
542pub(crate) struct RootedThreadBorrow<'a> {
543 inner: std::cell::RefMut<'a, LuaState>,
544}
545
546#[cfg(feature = "debug")]
547impl RootedThreadBorrow<'_> {
548 pub(crate) fn resnapshot(&mut self) {
552 let top = (self.inner.top_idx().0 as usize).min(self.inner.stack.len());
553 let stack_copy: Vec<LuaValue> = self.inner.stack[..top].iter().map(|sv| sv.val).collect();
554 let upval_copy: Vec<GcRef<lua_types::UpVal>> = self.inner.openupval.to_vec();
555 let mut g = self.inner.global_mut();
556 if let Some(slot) = g.suspended_parent_stacks.last_mut() {
557 slot.clear();
558 slot.extend(stack_copy);
559 }
560 if let Some(slot) = g.suspended_parent_open_upvals.last_mut() {
561 slot.clear();
562 slot.extend(upval_copy);
563 }
564 }
565}
566
567#[cfg(feature = "debug")]
568impl std::ops::Deref for RootedThreadBorrow<'_> {
569 type Target = LuaState;
570 fn deref(&self) -> &LuaState {
571 &self.inner
572 }
573}
574
575#[cfg(feature = "debug")]
576impl std::ops::DerefMut for RootedThreadBorrow<'_> {
577 fn deref_mut(&mut self) -> &mut LuaState {
578 &mut self.inner
579 }
580}
581
582#[cfg(feature = "debug")]
583impl Drop for RootedThreadBorrow<'_> {
584 fn drop(&mut self) {
585 let mut g = self.inner.global_mut();
586 if let Some(mut v) = g.suspended_parent_open_upvals.pop() {
587 v.clear();
588 g.snapshot_upval_pool.push(v);
589 }
590 if let Some(mut v) = g.suspended_parent_stacks.pop() {
591 v.clear();
592 g.snapshot_stack_pool.push(v);
593 }
594 }
595}
596
597#[cfg(feature = "debug")]
601pub(crate) fn borrow_thread_rooted<'a>(
602 state: &mut LuaState,
603 cell: &'a std::cell::RefCell<LuaState>,
604) -> RootedThreadBorrow<'a> {
605 let inner = cell.borrow_mut();
606 let top = (inner.top_idx().0 as usize).min(inner.stack.len());
607 let (mut stack_snapshot, mut upval_snapshot) = {
608 let mut g = state.global_mut();
609 (
610 g.snapshot_stack_pool.pop().unwrap_or_default(),
611 g.snapshot_upval_pool.pop().unwrap_or_default(),
612 )
613 };
614 stack_snapshot.extend(inner.stack[..top].iter().map(|sv| sv.val));
615 upval_snapshot.extend(inner.openupval.iter().cloned());
616 let mut g = state.global_mut();
617 g.suspended_parent_stacks.push(stack_snapshot);
618 g.suspended_parent_open_upvals.push(upval_snapshot);
619 drop(g);
620 RootedThreadBorrow { inner }
621}
622
623fn non_suspended_resume_message(state: &LuaState) -> &'static [u8] {
630 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
631 b"cannot resume running coroutine"
632 } else {
633 b"cannot resume non-suspended coroutine"
634 }
635}
636
637fn push_lit_or_nil(state: &mut LuaState, bytes: &[u8]) {
639 match state.intern_str(bytes) {
640 Ok(s) => state.push(LuaValue::Str(s)),
641 Err(_) => state.push(LuaValue::Nil),
642 }
643}
644
645pub fn co_resume(state: &mut LuaState) -> Result<usize, LuaError> {
659 let co = get_co(state)?;
660 let narg = state.get_top() - 1;
661 let r = aux_resume(state, co, narg);
662 if r < 0 {
663 if state.sandbox_aborting() {
664 let top = state.get_top();
665 let err_val = state.value_at(top);
666 return Err(LuaError::from_value(err_val));
667 }
668 state.push(LuaValue::Bool(false));
669 state.insert(-2)?;
670 Ok(2)
671 } else {
672 state.push(LuaValue::Bool(true));
673 state.insert(-(r + 1))?;
674 Ok((r + 1) as usize)
675 }
676}
677
678fn aux_wrap(state: &mut LuaState) -> Result<usize, LuaError> {
686 let up = state.value_at(upvalue_index(1));
687 let co = match up {
688 LuaValue::Thread(t) => t,
689 _ => {
690 return Err(LuaError::runtime(format_args!(
691 "coroutine.wrap: upvalue is not a thread"
692 )))
693 }
694 };
695 let narg = state.get_top();
696 let r = aux_resume(state, co.clone(), narg);
697 if r < 0 {
698 let top = state.get_top();
699 let mut err_val = state.value_at(top);
700 if aux_status(state, &co) == COS_DEAD {
701 let old_err = state.pop();
702 let nclose = close_suspended_or_dead(state, co)?;
703 err_val = if nclose >= 2 {
704 let top = state.get_top();
705 state.value_at(top)
706 } else {
707 old_err
708 };
709 state.pop_n(nclose);
710 }
711 Err(LuaError::from_value(err_val))
712 } else {
713 Ok(r as usize)
714 }
715}
716
717pub fn co_create(state: &mut LuaState) -> Result<usize, LuaError> {
723 state.check_arg_type(1, LuaType::Function)?;
724 if matches!(state.global().lua_version, lua_types::LuaVersion::V51)
730 && state.is_c_function_at(1)
731 {
732 return Err(lua_vm::debug::arg_error_impl(
733 state,
734 1,
735 b"Lua function expected",
736 ));
737 }
738 let body = state.value_at(1);
739 let _nl = state.new_thread(Some(body))?;
740 Ok(1)
741}
742
743pub fn co_wrap(state: &mut LuaState) -> Result<usize, LuaError> {
751 co_create(state)?;
752 state.push_cclosure(aux_wrap, 1)?;
753 Ok(1)
754}
755
756pub fn co_yield(state: &mut LuaState) -> Result<usize, LuaError> {
763 let n = state.get_top();
764 let r = lua_vm::do_::lua_yieldk(state, n, 0, None)?;
765 Ok(r as usize)
766}
767
768pub fn co_status(state: &mut LuaState) -> Result<usize, LuaError> {
773 let co = get_co(state)?;
774 let idx = aux_status(state, &co) as usize;
775 let name: &[u8] = STAT_NAMES[idx];
776 let interned = state.intern_str(name)?;
777 state.push(LuaValue::Str(interned));
778 Ok(1)
779}
780
781pub fn co_isyieldable(state: &mut LuaState) -> Result<usize, LuaError> {
785 let is_yieldable = if matches!(state.type_at(1), LuaType::None) {
786 state.is_yieldable()
787 } else {
788 let co = get_co(state)?;
789 let co_id = co.id;
790 let (is_main, is_current) = {
791 let g = state.global();
792 (co_id == g.main_thread_id, co_id == g.current_thread_id)
793 };
794 if is_main {
795 false
796 } else if is_current {
797 state.is_yieldable()
798 } else {
799 let entry_rc = {
800 let g = state.global();
801 g.threads
802 .get(&co_id)
803 .expect("thread value carries an id that must resolve in GlobalState::threads")
804 .state
805 .clone()
806 };
807 let target_is_yieldable = match entry_rc.try_borrow() {
808 Ok(b) => b.is_yieldable(),
809 Err(_) => false,
810 };
811 target_is_yieldable
812 }
813 };
814 state.push(LuaValue::Bool(is_yieldable));
815 Ok(1)
816}
817
818pub fn co_running(state: &mut LuaState) -> Result<usize, LuaError> {
830 let is_main = state.push_thread()?;
831 if matches!(state.global().lua_version, lua_types::LuaVersion::V51) {
832 if is_main {
833 state.pop_n(1);
834 state.push(LuaValue::Nil);
835 }
836 return Ok(1);
837 }
838 state.push(LuaValue::Bool(is_main));
839 Ok(2)
840}
841
842pub fn co_close(state: &mut LuaState) -> Result<usize, LuaError> {
850 lua_vm::state::inc_c_stack(state)?;
851 let result = (|| {
852 let co = get_opt_co(state)?;
853 let status = aux_status(state, &co);
854 match status {
855 COS_DEAD | COS_YIELD => close_suspended_or_dead(state, co),
856 _ => {
857 if matches!(state.global().lua_version, lua_types::LuaVersion::V55)
858 && status == COS_RUN
859 && state.global().closing_thread_id == Some(co.id)
860 {
861 state.push(LuaValue::Bool(true));
862 return Ok(1);
863 }
864 if matches!(state.global().lua_version, lua_types::LuaVersion::V55)
865 && status == COS_RUN
866 && co.id == state.global().main_thread_id
867 {
868 return Err(LuaError::runtime(format_args!("cannot close main thread")));
869 }
870 if matches!(state.global().lua_version, lua_types::LuaVersion::V55)
871 && status == COS_RUN
872 && co.id == state.global().current_thread_id
873 {
874 state.global_mut().closing_thread_id = Some(co.id);
875 let in_status = state.status as i32;
876 let s = lua_vm::state::reset_thread(state, in_status);
877 state.global_mut().closing_thread_id = None;
878 state.n_ccalls = state.n_ccalls.saturating_sub(1);
879 std::panic::panic_any(LuaThreadClose(LuaStatus::from_raw(s)));
880 }
881 let name = if status == COS_RUN {
882 "running"
883 } else {
884 "normal"
885 };
886 Err(LuaError::runtime(format_args!(
887 "cannot close a {} coroutine",
888 name
889 )))
890 }
891 }
892 })();
893 state.n_ccalls -= 1;
894 result
895}
896
897fn close_suspended_or_dead(
899 state: &mut LuaState,
900 co: GcRef<lua_types::value::LuaThread>,
901) -> Result<usize, LuaError> {
902 let co_id = co.id;
903 let entry_rc_opt = {
904 let g = state.global();
905 g.threads.get(&co_id).map(|e| e.state.clone())
906 };
907 let entry_rc = match entry_rc_opt {
908 Some(rc) => rc,
909 None => {
910 state.push(LuaValue::Bool(true));
911 return Ok(1);
912 }
913 };
914 let parent_thread_id = state.global().current_thread_id;
915 let caller_c_calls = state.c_calls();
916
917 let mut parent_open_upval_slots = pop_resume_slot_buf(state);
918 parent_open_upval_slots.extend(state.openupval.iter().filter_map(|uv| {
919 uv.try_open_payload()
920 .map(|(thread_id, idx)| (thread_id as u64, idx))
921 }));
922 {
923 let mut g = state.global_mut();
924 for (tid, idx) in &parent_open_upval_slots {
925 let val = state.get_at(*idx);
926 g.cross_thread_upvals.insert((*tid, *idx), val);
927 }
928 }
929
930 push_parent_gc_snapshot(state);
931
932 let (status, err_value): (i32, Option<LuaValue>) = {
933 let mut co_state = entry_rc.borrow_mut();
934 co_state.global_mut().current_thread_id = co_id;
935 co_state.global_mut().closing_thread_id = Some(co_id);
936 co_state.n_ccalls = caller_c_calls;
937 let in_status = co_state.status as i32;
938 let s = lua_vm::state::reset_thread(&mut *co_state, in_status);
939 co_state.global_mut().closing_thread_id = None;
940 co_state.global_mut().current_thread_id = parent_thread_id;
941 if s == LuaStatus::Ok as i32 {
942 (s, None)
943 } else {
944 let top = co_state.top_idx().0;
945 if top > 0 {
946 let err = co_state.get_at(lua_vm::state::StackIdx(top - 1));
947 co_state.set_top(lua_vm::state::StackIdx(top - 1));
948 (s, Some(err))
949 } else {
950 (s, Some(LuaValue::Nil))
951 }
952 }
953 };
954
955 pop_parent_gc_snapshot(state);
956
957 {
958 let mut flush = pop_resume_flush_buf(state);
959 let mut g = state.global_mut();
960 for (tid, idx) in &parent_open_upval_slots {
961 if let Some(v) = g.cross_thread_upvals.remove(&(*tid, *idx)) {
962 flush.push((*idx, v));
963 }
964 }
965 drop(g);
966 for (idx, v) in flush.drain(..) {
967 state.set_at(idx, v);
968 }
969 return_resume_flush_buf(state, flush);
970 }
971 return_resume_slot_buf(state, parent_open_upval_slots);
972
973 if status == LuaStatus::Ok as i32 {
974 state.push(LuaValue::Bool(true));
975 Ok(1)
976 } else {
977 state.push(LuaValue::Bool(false));
978 if let Some(v) = err_value {
979 state.push(v);
980 } else {
981 state.push(LuaValue::Nil);
982 }
983 Ok(2)
984 }
985}
986
987pub fn open_coroutine(state: &mut LuaState) -> Result<usize, LuaError> {
993 use lua_types::LuaVersion;
997 let version = state.global().lua_version;
998 let has_close = !matches!(version, LuaVersion::V51 | LuaVersion::V52 | LuaVersion::V53);
999 let has_isyieldable = !matches!(version, LuaVersion::V51 | LuaVersion::V52);
1003 if has_close && has_isyieldable {
1004 state.new_lib(CO_FUNCS)?;
1005 } else {
1006 let filtered: Vec<(&[u8], lua_CFunction)> = CO_FUNCS
1007 .iter()
1008 .filter(|(name, _)| {
1009 (has_close || *name != b"close".as_slice())
1010 && (has_isyieldable || *name != b"isyieldable".as_slice())
1011 })
1012 .copied()
1013 .collect();
1014 state.new_lib(&filtered)?;
1015 }
1016 Ok(1)
1017}