From 044907f34579d3b64afa81854a8b9ffce76562ad Mon Sep 17 00:00:00 2001 From: dkf Date: Sat, 11 Jul 2009 17:11:44 +0000 Subject: Substantially increased the discussion of issues and work-arounds relating to nested vwaits, following discussion on the tcl-core mailing list on the topic. --- ChangeLog | 6 ++++ doc/vwait.n | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index f278101..015eb9a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2009-07-11 Donal K. Fellows + + * doc/vwait.n: Substantially increased the discussion of issues and + work-arounds relating to nested vwaits, following discussion on the + tcl-core mailing list on the topic. + 2009-07-10 Pat Thoyts * tests/zlib.test: ZlibTransformClose may be called with a NULL diff --git a/doc/vwait.n b/doc/vwait.n index 43a2618..ed0bac2 100644 --- a/doc/vwait.n +++ b/doc/vwait.n @@ -4,7 +4,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: vwait.n,v 1.11 2009/07/11 11:26:20 dkf Exp $ +'\" RCS: @(#) $Id: vwait.n,v 1.12 2009/07/11 17:11:44 dkf Exp $ '\" .so man.macros .TH vwait n 8.0 Tcl "Tcl Built-In Commands" @@ -33,7 +33,7 @@ if an event handler sets \fIvarName\fR and then itself calls \fBvwait\fR to wait for a different variable, then it may not return for a long time. During this time the top-level \fBvwait\fR is blocked waiting for the event handler to complete, so it cannot -return either. +return either. (See the \fBNESTED VWAITS BY EXAMPLE\fR below.) .PP To be clear, \fImultiple \fBvwait\fI calls will nest and will not happen in parallel\fR. The outermost call to \fBvwait\fR will not return until all the @@ -127,6 +127,115 @@ coroutine task apply {{} { yield }} .CE +.SS "NESTED VWAITS BY EXAMPLE" +.PP +This example demonstrates what can happen when the \fBvwait\fR command is +nested. The script will never finish because the waiting for the \fIa\fR +variable never finishes; that \fBvwait\fR command is still waiting for a +script scheduled with \fBafter\fR to complete, which just happens to be +running an inner \fBvwait\fR (for \fIb\fR) even though the event that the +outer \fBvwait\fR was waiting for (the setting of \fIa\fR) has occurred. +.PP +.CS +after 500 { + puts "waiting for b" + \fBvwait\fR b + puts "b was set" +} +after 1000 { + puts "setting a" + set a 10 +} +puts "waiting for a" +\fBvwait\fR a +puts "a was set" +puts "setting b" +set b 42 +.CE +.PP +If you run the above code, you get this output: +.PP +.CS +waiting for a +waiting for b +setting a +.CE +.PP +The script will never print +.QW "a was set" +until after it has printed +.QW "b was set" +because of the nesting of \fBvwait\fR commands, and yet \fIb\fR will not be +set until after the outer \fBvwait\fR returns, so the script has deadlocked. +The only ways to avoid this are to either structure the overall program in +continuation-passing style or to use \fBcoroutine\fR to make the continuations +implicit. The first of these options would be written as: +.PP +.CS +after 500 { + puts "waiting for b" + trace add variable b write {apply {args { + global a b + trace remove variable ::b write [lrange [info level 0] 0 1] + puts "b was set" + set ::done ok + }}} +} +after 1000 { + puts "setting a" + set a 10 +} +puts "waiting for a" +trace add variable a write {apply {args { + global a b + trace remove variable a write [lrange [info level 0] 0 1] + puts "a was set" + puts "setting b" + set b 42 +}}} +\fBvwait\fR done +.CE +.PP +The second option, with \fBcoroutine\fR and some helper procedures, is done +like this: +.PP +.CS +# A coroutine-based wait-for-variable command +proc waitvar globalVar { + trace add variable ::$globalVar write \e + [list apply {{v c args} { + trace remove variable $v write [lrange [info level 0] 0 3] + after 0 $c + }} ::$globalVar [info coroutine]] + yield +} +# A coroutine-based wait-for-some-time command +proc waittime ms { + after $ms [info coroutine] + yield +} + +coroutine task-1 eval { + puts "waiting for a" + waitvar a + puts "a was set" + puts "setting b" + set b 42 +} +coroutine task-2 eval { + waittime 500 + puts "waiting for b" + waitvar b + puts "b was set" + set done ok +} +coroutine task-3 eval { + waittime 1000 + puts "setting a" + set a 10 +} +\fBvwait\fR done +.CE .SH "SEE ALSO" global(n), update(n) .SH KEYWORDS -- cgit v0.12