Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

coroutines and tk's event loop

540 views
Skip to first unread message

Brad Lanam

unread,
Nov 12, 2015, 5:06:23 PM11/12/15
to
Using a coroutine in conjunction with a button works fine:
ttk::button .b -text example -command [list startCoroutine myCommand]
I have various dialogs working as coroutines this way.

But is there any easy magic (if I have to say that, obviously the answer is 'no') to use coroutines working easily in Tk elsewhere?

I have a routine (which I want to just have as fileevent ... [info coroutine]; yield; gets) that is eventually called both from the mainline code and also from a fileevent socket handler. I believe starting the main process as a coroutine worked for the mainline code, but I could not figure out at what level the socket handler needed to have the coroutine started or if it was even possible.

miguel.sofer

unread,
Nov 13, 2015, 10:26:00 AM11/13/15
to
There is no startCoroutine command in either Tcl or Tcllib. What package
does that come from?

Brad Lanam

unread,
Nov 13, 2015, 1:43:08 PM11/13/15
to
On Friday, November 13, 2015 at 7:26:00 AM UTC-8, miguel.sofer wrote:
> There is no startCoroutine command in either Tcl or Tcllib. What package
> does that come from?

It's just an example, it should not be taken literally. The wiki generally uses a routine called 'spawn'. I believe tcllib has something that does the same thing.

Here is example code. Try input from stdin -- the coroutine works as the yield will break out back to the main event loop as the coroutine is started at the top level.

Try using the socket. The coroutine will fail (obviously, there's no coroutine set up). Where do I set up a coroutine that works in a socket handler? Is there a way in tk (using uplevel magic?) to set up the coroutine to always yield to the main event loop?

This is an <b>example</b>. I'm not actually using stdin, I'm sending a command to another process via a socket and getting a response back.

<pre>
#!/usr/bin/tclsh

package require Tk

variable vars

namespace eval utils {
variable ccounter 0

proc startCoroutine { cmd args } {
variable ccounter

set nm $cmd-coro-$ccounter
incr ccounter
coroutine $nm $cmd {*}$args
}
}

namespace eval testcoro {
proc mycoro { } {
set coro [info coroutine]
fileevent stdin readable $coro
yield
fileevent stdin readable {}
gets stdin value
return $value
}
}

proc mainSocketHandler { handler sock addr port } {
chan configure $sock -blocking false
chan configure $sock -buffering none
fileevent $sock readable [list $handler $sock]
}

proc socketHandler { sock } {
while { [gets $sock cmd] >= 0 } {
puts "socket got $cmd"
set shv [testcoro::mycoro]
puts "shv:$shv"
}
}

proc main { } {
variable vars

set vars(sock) [socket -myaddr localhost \
-server [list mainSocketHandler socketHandler] 35000]
chan configure stdin -blocking false
set v [testcoro::mycoro]
puts "v:$v"
}
utils::startCoroutine main
</pre>

Brad Lanam

unread,
Nov 13, 2015, 2:11:47 PM11/13/15
to
Ideally, within Tk, I would like to have a way to set up a procedure as a coroutine and be able to specify that it yields to the main event loop.

As it is right now, I might have:
get_length() calls get()
get_time() calls get()
get_other() calls get()
and get() should be a coroutine that waits on a fileevent.

But get_length(), get_time() and get_other() might be eventually called from various places within my program. From within mainline code, a button handler, a socket handler.

As it is right now, I have to change the calling code in various places to set up the coroutine, and it would be better to be able to set up get() as a coroutine and specify the level to where it yields. Of course this is specific to Tk.

miguel.sofer

unread,
Nov 13, 2015, 3:42:03 PM11/13/15
to
I am not 100% I understood the question, so I may be answering something
else ... if so, sorry.

If I understood your problem, you just have to save the coroutine's name
and set a fileevent to call it on any socket, just like you do on stdin.
There is nothing specific to Tk in here, and I can't see why you believe
that it is.

The coroutine will yield to whomever called it - if it was called from
the event loop (via fileevent), it will yield to it. Hence your intent
to "be able to specify that it yields to the main event loop" makes no
sense in Tcl's coro model. The only sense I could make out of that would
be that you are asking to 'yieldto update', which makes me shout BUT
DON'T DO THAT!

You may have complicated things for yourself by using that
startCoroutine proc that essentially throws away the coroutine's
invocation command name. You will need that name if you want to have
different things invoking it - eg, fileevents of both stdin and a socket.

In any case, that sample code does not make clear (to me) what it is
that you are trying to achieve - it just records how you tried and
failed. Could you describe in words what it is that you are trying to do?

Brad Lanam

unread,
Nov 13, 2015, 4:48:21 PM11/13/15
to
On Friday, November 13, 2015 at 12:42:03 PM UTC-8, miguel.sofer wrote:
> I am not 100% I understood the question, so I may be answering something
> else ... if so, sorry.

So changing this line in the mainSocketHandler() seems to work.
fileevent $sock readable [list utils::startCoroutine $handler $sock]

> Hence your intent to "be able to specify that it yields to the main event loop"...

The intention is to make the program stop and wait for a response without stopping the event loop (e.g. in dialog.tcl, remove the vwait and replace it with yield. vwait reenters the main event loop, yield does not (Actually, dialog.tcl can be fixed to use yield since the button handlers run at the top level)). If it does not yield to the main event loop, then the program will not wait.

I certainly don't claim to understand coroutines or all their uses, but it seems I have a different use case than what you expect.

Donal K. Fellows

unread,
Nov 15, 2015, 12:21:15 PM11/15/15
to
On 13/11/2015 21:48, Brad Lanam wrote:
> The intention is to make the program stop and wait for a response
> without stopping the event loop

If that is the intention, then the part that is stopping should be a
coroutine. What you don't do is say “oh, I've unexpectedly reached the
point where I need to stop: create a coroutine now”. Instead, you say
“this is a flow of processing that may need to stop: create a coroutine
to run it in”. Thus, if you were making a network server, you might put
the code to handle each connection in its own coroutine; the callback
from [socket -server] would be responsible for creating the coroutine
and would let that handle everything.

Donal.
--
Donal Fellows — Tcl user, Tcl maintainer, TIP editor.

Brad Lanam

unread,
Nov 15, 2015, 1:01:22 PM11/15/15
to
Yes, thanks. That's an excellent explanation and what I finally figured out.

Personally, I think using a coroutine for synchronous processing ends up being rather a maintenance problem (if the need-to-stop-point is deep in the call tree). I'm thinking more of the big picture than my specific code.

I think I would rather have vwait fixed to work in parallel (call it pvwait to ensure backward compatibility).

My code did have a small wait loop and would have only required the coroutine in two places, but I've decided to go with vwait (this code is in a separate daemon now, so vwait should easy to maintain and keep non-nested). It doesn't require changes to the calling code.

Christian Gollwitzer

unread,
Nov 15, 2015, 2:13:35 PM11/15/15
to
Am 15.11.15 um 19:01 schrieb Brad Lanam:
> I think I would rather have vwait fixed to work in parallel (call it pvwait to ensure backward compatibility).

Do you know about the coroutine package in tcllib? It does exactly that,
fixing vwait, after, gets etc. to become coroutine-aware.


Christian

Brad Lanam

unread,
Nov 15, 2015, 2:35:27 PM11/15/15
to
I'm aware of that, and it does not do exactly that.

Whereas the coroutine aware vwait can be used in parallel, it does not resolve the issue of changing the calling code in multiple places (to set up the coroutines) in order to support synchronous waits in the code.

If there was a true parallel vwait, I wouldn't have to change the calling code at all.


Les Cargill

unread,
Nov 15, 2015, 3:01:12 PM11/15/15
to
I would use vwait:

proc myCoRoutine { args } {
set yesWeWantATimer 0
while { 1 } {
if {$yesWeWantATimer} {
after 1000 [ list set ::AGlobalVar ]
}
vwait ::AGlobalVar
...
}
}

after 0 myCoRoutine

...

button .b -text "A button" -command [ list set ::AGlobalVar 1 ]

This is straight Tcl/Tk and has no package dependencies other then Tk .


--
Les Cargill

tombert

unread,
Nov 16, 2015, 1:55:16 AM11/16/15
to

Brad Lanam

unread,
Nov 16, 2015, 11:36:21 AM11/16/15
to
On Sunday, November 15, 2015 at 10:55:16 PM UTC-8, tombert wrote:
> http://www.tcl.tk/cgi-bin/tct/tip/374.html

I hadn't seen that, thanks.

As some people perceive the coroutine-enable vwait as a parallel vwait replacement (as demonstrated in the comment in the TIP), that could push the TIP towards being rejected.

I think that would be unfortunate, as a true parallel vwait would have much lower maintenance costs than enabling coroutines at the event loop level throughout a large code base.

tombert

unread,
Nov 16, 2015, 2:05:15 PM11/16/15
to
> As some people perceive the coroutine-enable vwait as a parallel vwait replacement (as demonstrated in the comment in the TIP), that could push the TIP towards being rejected.

Agreed

> I think that would be unfortunate, as a true parallel vwait would have much lower maintenance costs than enabling coroutines at the event loop level throughout a large code base.

A few thoughts I had in mind:

*) Coroutines are, as I perceive it in my environment, hard to grasp.
*) A "stackless vwait" looks to me much more straight forward.
*) Old TCL code could easily make use of "stackless vwait" without being totally rewritten.
*) Coroutines add overhead to the scripting level where the power of coroutines is not needed.
*) The flexibility of what can be done with coroutine is much higher compared to "stackless vwait".
*) "stackless vwait" could help TCL beginners write simple event driven code.
*) Many of my not so experienced colleagues made the "first in - last out" mistake.

Brad Lanam

unread,
Nov 16, 2015, 3:30:02 PM11/16/15
to
Well, if anyone cares, I vote 'yes' for this TIP #374.

I think a simple 'pvwait' parallel vwait command that acts as a 'whoever-comes-first' as outlined in the TIP would be all that is needed. This will work as people often expect vwait to work.

Yes, vwait could just be converted to be parallel, but that could conceivably break existing systems that are currently working with the nested vwait.

2010? Looks like the TIP system needs a priority queue (always wanted to implement one of those).

miguel.sofer

unread,
Nov 17, 2015, 7:47:20 AM11/17/15
to
A personal thought on this, reflecting my own views and definitely *not*
the view of the TCT ( I haven't even consulted with my colleagues).

Indeed, the existence of a pure-script version that works properly is a
reason to reject a TIP, that in my need would only be overridden by
performance considerations. A pure-script version has the following
advantages: it is portable across all platform from the get go, it has
an infinitely greater set of possible maintainers and contributors, it
does not require a TIP to bless it or its modifications, it is not tied
to the glacial times of the TCT. Note that very few scripts are
considered part of the core, and that some of us would prefer to take
some of those out of the TCT's power (notably http).

TIPs without a ref implementation rarely come even close to a vote, and
nobody seems to want this bad enough to develop one. A TIP is certainly
NOT a mandate to the TCT to develop the thing or even vote on it, it is
just a request to the community. You could well consider that the lack
of a reference implementation is just an incomplete request that cannot
move forward in the process.

TIP 374 does not have a reference implementation - not even in a script
version, as it lacks precisely what you people most seem to want. My
take as a maintainer and main author of the NRE/coroutine parts of the
core, is that it seems to be far from a trivial matter. I'd be happy to
work with anyone who shows up as main author of a reference
implementation, but it is not one of my priorities.

Since 2010 nobody has come up with a reference implementation of TIP
374, so that we do not even know if it is possible without breaking too
many things. I for one refuse to consider "I want a pony" requests that
do not have a technical champion behind them, except if I also want that
particular pony (or a somewhat similar one) and willingly take a
champion role: {*}, [apply] and [coroutine] come to mind.

The TIP system may or may not need a priority queue; an orthogonal
issue. What we see here is that TIP 374 needs a reference
implementation, or at least a complete man page that resolves all the
technical choices and compromises that should be taken. It is not the
TIP system which is at fault here, it is rather the fact that this
particular TIP seems to be abandoned by lack of interest.

The easiest way forward would be for someone to do whatever is missing
in the pure script ref implementation, making notes of the technical
compromises that were taken (and why) and also the points that a C
version in the core could solve better. A good first step to guide the
effort could be preparing a more or less complete man page for the tip's
commands. None of these two task requires knowledge of C or the core; it
is just a complete description of the requested feature, down to the
last detail.

Cheers

Miguel

miguel.sofer

unread,
Nov 17, 2015, 9:16:28 AM11/17/15
to
(BCC'ed to different people I thought could be interested)

Hmmm ... maybe the tip could evolve to a completely different matter:
that we provide a ptclsh that would be essentially the same as tclsh,
with the sole exception that the main loop is already a coroutine
launched from the event loop.

This itself can possibly be done as a script that runs at launch time. I
do believe that Colin McCormack has (at least big parts) of this pig in
the slaughterhouse - or even packaged as a tasty sausage. Pinging him too.

IIUC, the pure tcl version would do the rest, especially tcllib's
::coroutine::auto.

Thoughts on this?

Cheers

Miguel

Twylite

unread,
Nov 17, 2015, 10:05:51 AM11/17/15
to
Hi,

> *) A "stackless vwait" looks to me much more straight forward.
> *) Coroutines add overhead to the scripting level where the power of coroutines is not needed.
> *) The flexibility of what can be done with coroutine is much higher compared to "stackless vwait".

"stackless vwait" requires coroutines.

Your program executes within a call stack: you have a current stack frame containing your local variables, a parent frame to which you will [return], and when you call a command/proc you will push a new child frame onto the stack.

So when you call [vwaitStackless] what happens? You have two options:

(1) [vwaitStackless] is a child frame that contains an event loop. That's what legacy [vwait] does, and the reason it doesn't work is that a nested event creates further child frames which may in turn enter a [vwait] event loop, and now you can't wake up your first vwait without vanishing the second event's frames from the stack. So you end up with deadlock.

(2) [vwaitStackless] cannot be a child frame on the call stack, but you also cannot disappear your call stack because you want to resume it later. So you need to 'return into another call stack'. That's a [yield] option, which allows you to jump out of a coroutine leaving its call stack intact, so that you can jump back in later.

You cannot get a 'stackless vwait' without having multiple call stacks (whether those are coroutines, fibers, or threads), or redesigning your code so that you don't expect it to suspend/[yield] in the middle of a proc.

Regards,
Twylite



Colin Macleod

unread,
Nov 17, 2015, 10:31:35 AM11/17/15
to
On Tuesday, 17 November 2015 15:05:51 UTC, Twylite wrote:
> "stackless vwait" requires coroutines.
>
This seems exactly right to me - you can't get a "stackless vwait" for free. Enabling this sort of thing was why I originally hacked up the code in http://wiki.tcl.tk/21555 that formed the basis of the tcllib coroutine module.

Colin.

Twylite

unread,
Nov 17, 2015, 10:39:15 AM11/17/15
to
Hi,

> Hmmm ... maybe the tip could evolve to a completely different matter:
> that we provide a ptclsh that would be essentially the same as tclsh,
> with the sole exception that the main loop is already a coroutine
> launched from the event loop.

A few thoughts:

tclsh doesn't have an event loop. Tk has an event loop; or you can use e.g. [vwait] in tclsh to stop you running off the end of the script and terminating the process. I'm not sure you mean by "main loop"?

If a script is queued for execution via the event loop (e.g. [after], [fileevent], etc.) does it implicitly execute in a coroutine? If so, which one?

My recent thinking on making coroutines easier to use is to look at them from the perspective of dynamic scope. It seems apparent to me that most use of coroutines (that I have encountered) falls into one of two categories:

(1) Suspendable tasks (i.e. "Fibers", see https://en.wikipedia.org/wiki/Fiber_(computer_science) ): you want to write a task using sequential code (rather than a state machine or similar) but at certain points you need to wait for events (e.g. input/output) or allow other code a change to execute (cooperative multitasking). Suspending a task switches you to a different dynamic scope. Tasks don't tend to yield values when they suspend, and don't tend to expect arguments when they resume. Tasks typically arrange for themselves to be resumed (e.g. via [fileevent] or by integrating with a scheduler (in the simplest case: [after])).

(2) Generators (see https://en.wikipedia.org/wiki/Generator_(computer_programming) ): from a design perspective a generator runs in the dynamic scope of the caller (that is, the caller isn't intending to switch out to another task; the fact that you do is a consequence of implementation), and the caller is expecting to get a return value (yielded by the generator). Generators may expect arguments when they resume (they are called like any other proc), and don't arrange to resume themselves.

Most of this conversation is about suspendable tasks. I think that an abstraction that implements suspendable tasks ("fibers"?) on top of coroutines, with integration to vwait/after/fileevent, will be an attractive and understandable solution.

Regards,
Twylite

Donal K. Fellows

unread,
Nov 17, 2015, 10:47:58 AM11/17/15
to
On 17/11/2015 14:16, miguel.sofer wrote:
> Hmmm ... maybe the tip could evolve to a completely different matter:
> that we provide a ptclsh that would be essentially the same as tclsh,
> with the sole exception that the main loop is already a coroutine
> launched from the event loop.

That TIP itself is utterly confusing to me. :-) But what you're saying
is much less so. Running the sourcing of the main script within a
coroutine context with an event loop running externally to that, and
having standard event loop entering commands ([vwait], [update] in Tcl)
do so by suspending the current coroutine, that would all be entirely
possible and would be largely transparent to existing code.

I'm still trying to grok what happens when you've got coroutines that
are used as generators in there as well. Maybe I'm confused and it will
all Just Work, but I can't quite see enough to say that for sure offhand.

Christian Gollwitzer

unread,
Nov 17, 2015, 10:50:45 AM11/17/15
to
Am 17.11.15 um 15:16 schrieb miguel.sofer:
> (BCC'ed to different people I thought could be interested)
>
> Hmmm ... maybe the tip could evolve to a completely different matter:
> that we provide a ptclsh that would be essentially the same as tclsh,
> with the sole exception that the main loop is already a coroutine
> launched from the event loop.

I admit that I do not understand this matter deeply enough. But if this
is the only missing thing, can you not fake it by something like

proc mainloop {} {
yield
update
}

coroutine m mainloop

??

Christian

Twylite

unread,
Nov 17, 2015, 11:07:21 AM11/17/15
to
On Tuesday, 17 November 2015 17:47:58 UTC+2, Donal K. Fellows wrote:
> Running the sourcing of the main script within a
> coroutine context with an event loop running externally to that, and
> having standard event loop entering commands ([vwait], [update] in Tcl)
> do so by suspending the current coroutine, that would all be entirely
> possible and would be largely transparent to existing code.


lassign {} ::a ::b
after 1000 {
set ::a 1
vwait ::b
}
vwait ::a
set ::b 2

In what coroutine does that after-script execute? If the coroutine must be explicitly created then (a) what does running the main script in a coroutine really buy us? and (b) this implies that scripts executed from the event loop still execute in a blockable 'main call stack', so the developer still needs to be aware of how/where execution started to know if it is safe/possible to [vwait] or [yield].

A solution may be to have the event loop spawn a new coroutine for each script it evaluates. I haven't thought through the consequences of that.

Twylite

unread,
Nov 17, 2015, 11:19:39 AM11/17/15
to
On Tuesday, 17 November 2015 17:50:45 UTC+2, Christian Gollwitzer wrote:
> I admit that I do not understand this matter deeply enough. But if this
> is the only missing thing, can you not fake it by something like

If you normally run 'tclsh main.tcl' you can fake it by something like:
# main-coro.tcl
package require coroutine::auto
after 0 [list coroutine m source main.tcl]
vwait forever
then run 'tclsh main-coro.tcl'

(except that doesn't work because coro_auto is broken, at least on Win32).

Brad Lanam

unread,
Nov 17, 2015, 11:21:31 AM11/17/15
to
On Tuesday, November 17, 2015 at 8:07:21 AM UTC-8, Twylite wrote:
> lassign {} ::a ::b
> after 1000 {
> set ::a 1
> vwait ::b
> }
> vwait ::a
> set ::b 2
>
> In what coroutine does that after-script execute? If the coroutine must be explicitly created then (a) what does running the main script in a coroutine really buy us? and (b) this implies that scripts executed from the event loop still execute in a blockable 'main call stack', so the developer still needs to be aware of how/where execution started to know if it is safe/possible to [vwait] or [yield].

This is the problem. There are multiple points where the event loop is reentered (main, fileevent, after, etc.) and all of these have to be a coroutine. If I only had to change main() to be a coroutine, it would
not be so bad, but all the other commands that reenter the event loop
must also be coroutines.

> A solution may be to have the event loop spawn a new coroutine for each script it evaluates. I haven't thought through the consequences of that.

I would worry about the overhead with tight event loops (e.g. with fileevent) where performance is needed.

Twylite

unread,
Nov 17, 2015, 11:45:57 AM11/17/15
to
On Tuesday, 17 November 2015 17:47:58 UTC+2, Donal K. Fellows wrote:
> I'm still trying to grok what happens when you've got coroutines that
> are used as generators in there as well. Maybe I'm confused and it will
> all Just Work, but I can't quite see enough to say that for sure offhand.

Coroutine-aware [vwait] is a microprotocol: it arranges for the current coroutine to be resumed (via [trace add variable]) then [yield]s (with no return value). It may expect to be resumed with arguments (so it should only be resumed by the code it set up for that purpose).

A coroutine [yield]s or [return]s or [error]s to its caller (i.e. the code that created or resumed the coroutine).

So calling [vwait] from a generator will [yield] an empty string to the caller of the generator (whereas the generator thought it was suspending to the event loop), and any attempt to call the generator normally (i.e. as a generator) will resume it into the coro-aware-[vwait] code and break the generator. In short you can't [vwait] in a generator (using the current microprotocol in the tcllib coroutine package).

If the event loop was a named coroutine it *may* be possible to build a coroutine-aware [vwait] using [yieldto] that Does The Right Thing. Generator code would still be broken to the extent that it wouldn't be reentrant (i.e. coroA calls generatorA calls [vwait]; event loop wakes coroB which calls generatorA and FAIL).

Any solution I've thought up gets tied in knots by the following code:

set ::x 0
coroutine streamX apply {{varname} {
yield [info coroutine]
while {1} {
vwait $varname
yield [set $varname]
}
}} ::x
after 200 { set ::x 1 }
after 300 { set ::x 2 }
after 100 {
puts [streamX] ;# suspends THIS coroutine until ::x is set a second time, then displays the second value of ::x
}
puts [streamX] ;# suspends THIS coroutine until ::x is set the first time, then displays the first new value of ::x

Regards,
Twylite

miguel.sofer

unread,
Nov 17, 2015, 12:26:58 PM11/17/15
to
On 11/17/2015 01:21 PM, Brad Lanam wrote:
> On Tuesday, November 17, 2015 at 8:07:21 AM UTC-8, Twylite wrote:
>> lassign {} ::a ::b
>> after 1000 {
>> set ::a 1
>> vwait ::b
>> }
>> vwait ::a
>> set ::b 2
>>
>> In what coroutine does that after-script execute? If the coroutine must be explicitly created then (a) what does running the main script in a coroutine really buy us? and (b) this implies that scripts executed from the event loop still execute in a blockable 'main call stack', so the developer still needs to be aware of how/where execution started to know if it is safe/possible to [vwait] or [yield].
>
> This is the problem. There are multiple points where the event loop is reentered (main, fileevent, after, etc.) and all of these have to be a coroutine. If I only had to change main() to be a coroutine, it would
> not be so bad, but all the other commands that reenter the event loop
> must also be coroutines.

This where you are wrong: if they are called from the main script which
is a coroutine, they already are in a coroutine: that of it's caller. I
insist that what you were trying to do does NOT require rewriting
everything.




miguel.sofer

unread,
Nov 17, 2015, 12:53:47 PM11/17/15
to
On 11/17/2015 01:07 PM, Twylite wrote:
> On Tuesday, 17 November 2015 17:47:58 UTC+2, Donal K. Fellows wrote:
>> Running the sourcing of the main script within a
>> coroutine context with an event loop running externally to that, and
>> having standard event loop entering commands ([vwait], [update] in Tcl)
>> do so by suspending the current coroutine, that would all be entirely
>> possible and would be largely transparent to existing code.
>
>
> lassign {} ::a ::b
> after 1000 {
> set ::a 1
> vwait ::b
> }
> vwait ::a
> set ::b 2
>
> In what coroutine does that after-script execute? If the coroutine must be explicitly created then (a) what does running the main script in a coroutine really buy us? and (b) this implies that scripts executed from the event loop still execute in a blockable 'main call stack', so the developer still needs to be aware of how/where execution started to know if it is safe/possible to [vwait] or [yield].

If you have not created another one in which you may be running, that
runs in the "main" coroutine (new!). It would in principle be something
like starting with a script that does basically:

coroutine ::tcl::NO_TOUCHY::# ::apply [list {} {
after 1 {source ::tcl::NO_TOUCHY::TheMainScriptThatRunsTheThing}
while 1 yield;
}
vwait ::tcl::NO_TOUCHY::DON'T_MUCK_WITH_THIS_VAR

You are expected not to interfere with that coroutine or touch the
variable; that can easily be arranged in a C version to be impossible
from a script.

Note that a good real version needs to have a well thought out bgerror,
among other details.

Note that for interactive use TheMainScriptThatRunsTheThing could well
run an interactive session, or otherwise run from stdin - ie, generally
work as a tclsh with a main coroutine and a running event loop. Details
need to be thought out and coded, very preliminary thoughts here. It
also doesn't need to be sourced, it can be hard-coded in here.


> A solution may be to have the event loop spawn a new coroutine for each script it evaluates. I haven't thought through the consequences of that.

My gut says "bad idea", brain didn't yet process ...


Brad Lanam

unread,
Nov 17, 2015, 4:38:06 PM11/17/15
to
You're talking about some other idea in your head.

All I care about is code that works.
Show me code that calls a low-level synchronous wait-for-response function from both main and from a fileevent that works.

I tested this, I wrote code, I know what it requires from empirical testing.
I don't care if it is already in a coroutine if it doesn't work.

Andreas Leitgeb

unread,
Nov 19, 2015, 3:20:00 PM11/19/15
to
While reading this thread I posed myself the question:

What if the current coroutine is already called from another coroutine,
and the outer coroutine doesn't expect its callee to yield prematurely?

So I came up with this version of co_vwait:

proc co_vwait {var} {
set cc [info coroutine]
if {$cc ne ""} {
# we're in a coroutine
yieldto co_vwait_int $var $cc; # pass on this coro's name
# upon resume: just finish
} else {
# not called from any coroutine:
tailcall vwait $var
}
}
proc co_vwait_int {var ic} {
# parameter ic: next inner coroutine
set cc [info coroutine]
if {$cc ne ""} {
# we're still in a coroutine
yieldto co_vwait_int $var $cc; # pass on this coro's name
tailcall $ic; # upon resume: resume inner coro
} else {
# unrolled all the coro-"stack"
# now schedule handler for resumption:
trace add variable ::$var write [list co_vwait_callback $var $ic]
}
}
proc co_vwait_callback {var cmd args} {
after cancel [list $cmd]; # avoid cumulation...
after 1 [list $cmd];# new event handler will do this
trace remove variable ::$var write [lrange [info level 0] 0 2]
}

Haven't looked into tcllib, so if that is already "nested-coro"-able,
then I just re-invented the wheel ;-)

It's a bit of a pity that there is no 'event' for variable change.
there is only trace which can schedule a timer event with some
hassles.

pal...@yahoo.com

unread,
Nov 20, 2015, 4:40:18 AM11/20/15
to
On Tuesday, November 17, 2015 at 9:09:15 PM UTC+5:30, Twylite wrote:
> Most of this conversation is about suspendable tasks. I think that an
> abstraction that implements suspendable tasks ("fibers"?) on top of
> coroutines, with integration to vwait/after/fileevent, will be an attractive
> and understandable solution.

This is what I'm targeting with http://tclfiber.sourceforge.net/ though that implementation is somewhat out of date. I haven't gotten around to uploading a current snapshot.

/Ashok

Donal K. Fellows

unread,
Nov 20, 2015, 6:50:42 AM11/20/15
to
On 19/11/2015 20:17, Andreas Leitgeb wrote:
> What if the current coroutine is already called from another coroutine,
> and the outer coroutine doesn't expect its callee to yield prematurely?

That's exactly the core of my worries. It might be that I'm worrying
over nothing, or it might be a real problem. I think we need some
evidence of what happens in reality instead of just windy speculation.

Andreas Leitgeb

unread,
Nov 23, 2015, 3:46:37 PM11/23/15
to
Donal K. Fellows <donal.k...@manchester.ac.uk> wrote:
> On 19/11/2015 20:17, Andreas Leitgeb wrote:
>> What if the current coroutine is already called from another coroutine,
>> and the outer coroutine doesn't expect its callee to yield prematurely?
> That's exactly the core of my worries. It might be that I'm worrying
> over nothing, or it might be a real problem.

Not sure, if your concern is about non-modal vwait as such, or about
my solution that yields the whole "stack" of active coroutines and
stacks it up again on resumption.

> I think we need some
> evidence of what happens in reality instead of just windy speculation.

Since coroutines are still rather new in Tcl, probably very little
of that happens in reality, yet.

0 new messages