summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tclIORChan.c66
-rw-r--r--tests/ioCmd.test53
2 files changed, 105 insertions, 14 deletions
diff --git a/generic/tclIORChan.c b/generic/tclIORChan.c
index be82b48..cebc33f 100644
--- a/generic/tclIORChan.c
+++ b/generic/tclIORChan.c
@@ -54,6 +54,8 @@ static int ReflectGetOption(ClientData clientData,
static int ReflectSetOption(ClientData clientData,
Tcl_Interp *interp, const char *optionName,
const char *newValue);
+static void TimerRunRead(ClientData clientData);
+static void TimerRunWrite(ClientData clientData);
/*
* The C layer channel type/driver definition used by the reflection. This is
@@ -112,6 +114,17 @@ typedef struct {
int dead; /* Boolean signal that some operations
* should no longer be attempted. */
+ Tcl_TimerToken readTimer; /*
+ A token for the timer that is scheduled in
+ order to call Tcl_NotifyChannel when the
+ channel is readable
+ */
+ Tcl_TimerToken writeTimer; /*
+ A token for the timer that is scheduled in
+ order to call Tcl_NotifyChannel when the
+ channel is writable
+ */
+
/*
* Note regarding the usage of timers.
*
@@ -121,11 +134,9 @@ typedef struct {
*
* See 'rechan', 'memchan', etc.
*
- * Here this is _not_ required. Interest in events is posted to the Tcl
- * level via 'watch'. And posting of events is possible from the Tcl level
- * as well, via 'chan postevent'. This means that the generation of all
- * events, fake or not, timer based or not, is completely in the hands of
- * the Tcl level. Therefore no timer here.
+ * A timer is used here as well in order to ensure at least on pass through
+ * the event loop when a channel becomes ready. See issues 67a5eabbd3d1 and
+ * ef28eb1f1516.
*/
} ReflectedChannel;
@@ -920,7 +931,18 @@ TclChanPostEventObjCmd(
#if TCL_THREADS
if (rcPtr->owner == rcPtr->thread) {
#endif
- Tcl_NotifyChannel(chan, events);
+ if (events & TCL_READABLE) {
+ if (rcPtr->readTimer == NULL) {
+ rcPtr->readTimer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME,
+ TimerRunRead, rcPtr);
+ }
+ }
+ if (events & TCL_WRITABLE) {
+ if (rcPtr->writeTimer == NULL) {
+ rcPtr->writeTimer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME,
+ TimerRunWrite, rcPtr);
+ }
+ }
#if TCL_THREADS
} else {
ReflectEvent *ev = ckalloc(sizeof(ReflectEvent));
@@ -968,6 +990,24 @@ TclChanPostEventObjCmd(
#undef EVENT
}
+static void
+TimerRunRead(
+ ClientData clientData)
+{
+ ReflectedChannel *rcPtr = clientData;
+ rcPtr->readTimer = NULL;
+ Tcl_NotifyChannel(rcPtr->chan, TCL_READABLE);
+}
+
+static void
+TimerRunWrite(
+ ClientData clientData)
+{
+ ReflectedChannel *rcPtr = clientData;
+ rcPtr->writeTimer = NULL;
+ Tcl_NotifyChannel(rcPtr->chan, TCL_WRITABLE);
+}
+
/*
* Channel error message marshalling utilities.
*/
@@ -1161,6 +1201,12 @@ ReflectClose(
ckfree(tctPtr);
((Channel *)rcPtr->chan)->typePtr = NULL;
}
+ if (rcPtr->readTimer != NULL) {
+ Tcl_DeleteTimerHandler(rcPtr->readTimer);
+ }
+ if (rcPtr->writeTimer != NULL) {
+ Tcl_DeleteTimerHandler(rcPtr->writeTimer);
+ }
Tcl_EventuallyFree(rcPtr, (Tcl_FreeProc *) FreeReflectedChannel);
return EOK;
}
@@ -1230,6 +1276,12 @@ ReflectClose(
ckfree(tctPtr);
((Channel *)rcPtr->chan)->typePtr = NULL;
}
+ if (rcPtr->readTimer != NULL) {
+ Tcl_DeleteTimerHandler(rcPtr->readTimer);
+ }
+ if (rcPtr->writeTimer != NULL) {
+ Tcl_DeleteTimerHandler(rcPtr->writeTimer);
+ }
Tcl_EventuallyFree(rcPtr, (Tcl_FreeProc *) FreeReflectedChannel);
return (result == TCL_OK) ? EOK : EINVAL;
}
@@ -2131,6 +2183,8 @@ NewReflectedChannel(
rcPtr->chan = NULL;
rcPtr->interp = interp;
rcPtr->dead = 0;
+ rcPtr->readTimer = 0;
+ rcPtr->writeTimer = 0;
#if TCL_THREADS
rcPtr->thread = Tcl_GetCurrentThread();
#endif
diff --git a/tests/ioCmd.test b/tests/ioCmd.test
index 9c93102..b15be21 100644
--- a/tests/ioCmd.test
+++ b/tests/ioCmd.test
@@ -930,6 +930,17 @@ proc onfinal {} {
if {[lindex $hargs 0] ne "finalize"} {return}
return -code return ""
}
+
+proc onwatch {} {
+ upvar args hargs
+ lassign $hargs watch chan eventspec
+ if {$watch ne "watch"} return
+ foreach spec $eventspec {
+ chan postevent $chan $spec
+ }
+ return
+}
+
}
# Set everything up in the main thread.
@@ -2002,28 +2013,29 @@ test iocmd-31.6 {chan postevent, posted events do happen} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return}
set c [chan create {r w} foo]
- note [fileevent $c readable {note TOCK}]
- set stop [after 10000 {note TIMEOUT}]
+ set tock {}
+ note [fileevent $c readable {lappend res TOCK; set tock 1}]
+ set stop [after 10000 {lappend res TIMEOUT; set tock 1}]
after 1000 {note [chan postevent $c r]}
- vwait ::res
+ vwait ::tock
catch {after cancel $stop}
close $c
rename foo {}
set res
-} -result {{watch rc* read} {} TOCK {} {watch rc* {}}}
+} -result {{watch rc* read} {} {} TOCK {watch rc* {}}}
test iocmd-31.7 {chan postevent, posted events do happen} -match glob -body {
set res {}
proc foo {args} {oninit; onfinal; track; return}
set c [chan create {r w} foo]
- note [fileevent $c writable {note TOCK}]
- set stop [after 10000 {note TIMEOUT}]
+ note [fileevent $c writable {lappend res TOCK; set tock 1}]
+ set stop [after 10000 {lappend res TIMEOUT; set tock 1}]
after 1000 {note [chan postevent $c w]}
- vwait ::res
+ vwait ::tock
catch {after cancel $stop}
close $c
rename foo {}
set res
-} -result {{watch rc* write} {} TOCK {} {watch rc* {}}}
+} -result {{watch rc* write} {} {} TOCK {watch rc* {}}}
test iocmd-31.8 {chan postevent after close throws error} -match glob -setup {
proc foo {args} {oninit; onfinal; track; return}
proc dummy args { return }
@@ -2036,6 +2048,31 @@ test iocmd-31.8 {chan postevent after close throws error} -match glob -setup {
rename foo {}
rename dummy {}
} -returnCodes error -result {can not find reflected channel named "rc*"}
+test iocmd-31.9 {
+ chan postevent
+
+ call to current coroutine
+
+ see 67a5eabbd3d1
+} -match glob -body {
+ set res {}
+ proc foo {args} {oninit; onwatch; onfinal; track; return}
+ set c [chan create {r w} foo]
+ after 0 [list ::apply [list c {
+ coroutine c1 ::apply [list c {
+ chan event $c readable [list [info coroutine]]
+ yield
+ set ::done READING
+ } [namespace current]] $c
+ } [namespace current]] $c]
+ set stop [after 10000 {set done TIMEOUT}]
+ vwait ::done
+ catch {after cancel $stop}
+ lappend res $done
+ close $c
+ rename foo {}
+ set res
+} -result {{watch rc* read} READING {watch rc* {}}}
# --- === *** ###########################
# 'Pull the rug' tests. Create channel in a interpreter A, move to