Tcl Source Code

View Ticket
Login
Ticket UUID: f9800d52bd61f240938dbd3c3c6364c8a6184194
Title: vwait is not NRE-enabled, and yieldto cannot find the right splicing spot
Type: Bug Version:
Submitter: pooryorick Created on: 2021-06-19 21:36:06
Subsystem: - New Builtin Commands Assigned To: pooryorick
Priority: 5 Medium Severity: Critical
Status: Closed Last Modified: 2021-06-23 13:01:38
Resolution: Fixed Closed By: jan.nijtmans
    Closed on: 2021-06-23 13:01:38
Description:

At least as of [07f12cf7209a23fa3], core-8-branch, the script below aborts. The trouble begins with the C stack being busy because c2 has been called from with vwait, which is not yet NRE-enabled. Although the error message talks about tailcall, it is really about yieldto, which calls TclSetTailcall.

proc main_coro {} {
	set done [catch {
		coroutine c1 p0 
		yieldto c1
		coroutine c2 p1
		after 0 [list [namespace which c2]]
		vwait forever 
	} cres copts]
}

proc p0 {} {
	yield
	::tailcall main
}

proc p1 {} {
	yield
	set this [info coroutine]
	set script [list ::yieldto [list $this]]
	yieldto try $script on error {cres copt} "
		puts \[list uh-oh \$cres]
		[list $this]
	"
	set script [list ::yieldto $this]
	yieldto ::try $script
}


coroutine main main_coro

Output:

uh-oh {cannot yield: C stack busy}
tailcall cannot find the right splicing spot: should not happen!
Aborted (core dumped)

User Comments: jan.nijtmans added on 2021-06-23 13:01:38:

Merged (again) to core-8-branch (and trunk). Appears to work now.

Thanks for the fix, and the updated testcase!


jan.nijtmans added on 2021-06-21 14:54:08:

'Fix' reverted from core-8-branch, because it broke the build.

See: https://github.com/tcltk/tcl/runs/2872891537?check_suite_focus=true

coroutine.test
Test file error: cannot yield: C stack busy
    while executing
"yieldto c1"
    ("try" body line 1)
    invoked from within
"c2"
    ("after" script)
cannot yield: C stack busy
    while executing
"yieldto c1"
    ("try" body line 1)
    ("after" script)


pooryorick added on 2021-06-20 23:10:45:

Fixed in [c75d4c3be05d5e91].


pooryorick added on 2021-06-20 23:10:13:

The problem is that TclSetTailcall splices the command to be yielded to into the right place, but when TclNRCoroutineActivateCallback decides the C stack is busy, it doesn't go back and undo the splice. At that point, the splicing spot is already taken and any other attempt to yieldto produces the error, "tailcall cannot find the right splicing spot: should not happen!". From here on out, that command that hasn't been unspliced behaves like a tailcall: When the coroutine ends that command takes its place. Tailcall itself doesn't have the same problem as yieldto because tailcall uses a slightly different mechanism to register the tailcall. The solution is to undo that splice of the pending command if yieldto can't proceed.


pooryorick added on 2021-06-20 18:28:46:

Here is a slightly smaller script to reproduce the issue:

proc p0 {} {
	yield
	yieldto c1
	after 0 c2 
	vwait forever
}

proc p1 {} {
	yield
	tailcall c0
}

proc p2 {} {
	yield
	after 0 [list [info coroutine]]
	yieldto try {yieldto c1} on error {} c1
	yieldto try [list yieldto c1]
}
coroutine c0 p0
coroutine c1 p1
coroutine c2 p2
after 0 [list [namespace which c0]]
vwait forever


dkf added on 2021-06-20 07:45:06:

Well, this shouldn't crash. It probably should be an error though; I can't comprehend any way to make vwait itself be anything other than NRE-unaware.


pooryorick added on 2021-06-20 06:41:00:

sebres noted this issue in [cc592ec70de012df].