thread: -eventmark and send -async

thread: -eventmark and send -async describes how these two features can interact to cause a deadlock.

Reported here .

Description

PYK 2015-04-12:

thread::configure $thread -eventmark ... limits the number of asynchronous calls to the thread. If thread A and thread B are configured with an -eventmark, and thread A uses the variant of thread::send -async that specifies a variable in which to store the result of the call, a deadlock can occcur if As -eventmark limit is reached in the meantime. Apparently, the response from B to A that stores the result in the specified variable is subject to the queue limit of A. Because asynchronous calls become blocking when a thread's queue limit is reached, A ends up blocked in an -async call to B, which in turn is blocked waiting for As queue to die down so that it can write its response.

I think this is a bug because with -eventmark, the intent is normally to throttle incoming work, and not to limit responses to calls to other threads, and it can be a real head-scratcher when such a deadlock occurs in a non-trivial threaded program.

In versions of thread that behave like this, the rule of the to is to not execute thread::send -async ... varname] from a thread that has been configured with a non-zero -eventmark value.

Here's a script that illustrates the issue:

#! /bin/env tclsh

package require Thread

set consumer [thread::create {
    proc eat value {
        puts [list [thread::id] receive $value]
    }
    thread::wait
}]
thread::configure $consumer -eventmark 10 

set filter [thread::create {
    proc eat value {
        variable consumer
                
                # This command causes the threads to hang.
        thread::send -async $consumer [list eat [expr {$value * 2}]] [namespace current]::reply

                # Replace the previous command with this one, and the deadlock resolves.
        #thread::send -async $consumer [list eat [expr {$value * 2}]] 
    }
    thread::wait
}]
thread::send $filter [list variable consumer $consumer]
thread::configure $filter -eventmark 10 

for {set i 0} {$i < 50} {incr i} {
    set thread [thread::create {
        proc go {} {
            variable filter
            set value 1
            while 1 {
                incr value 2
                #puts [list sending $value to $filter]
                thread::send -async $filter [list eat $value]
            }
        }
        thread::wait
    }]
    thread::send -async $thread [list variable filter $filter]
    thread::send -async $thread {after 0 [list after idle go]}
}

vwait forever