View Ticket
Not logged in
Ticket UUID: b78d94c049af5090a9ef1c4aaa8059aa45b98b59
Title: Thread pool panics if stdin closed and IO performed in initcmd
Type: Bug Version: 8.6 - Linux
Submitter: aleteoryx Created on: 2025-02-22 03:01:37
Subsystem: 49. Threading Assigned To: nobody
Priority: 5 Medium Severity: Minor
Status: Closed Last Modified: 2025-02-24 23:20:20
Resolution: Invalid Closed By: sebres
    Closed on: 2025-02-24 23:20:20
Description:
The following code panics with "Tcl_RegisterChannel: duplicate channel names". The IO call in the initcmd does not matter; I discovered the issue with a `package require` call.

```
package require Thread

close stdin

set pool [tpool::create -initcmd {
  open tmpfile w
}]
tpool::post $pool {}
```
User Comments: sebres added on 2025-02-24 23:20:20:

Well, it looks like this is indeed an issue of tcl (not the thread-module).

Here is the experimental commit that solves the issue - [93d9a20635]. Although I'm not quite convinced with this solution.

In general the usage of standard channels across threads is not well thought out in tcl-IO:

  • the standard channels (by their names) are "shared" across the threads, however not the instances of them (which are quasi duplicates each thread);
  • so every thread initializes its own instance, so that if a thread uses some of standard channel first time, it gets its own instance (for the same thing)
  • the close of standard channel in one thread will do that not for the other thread, if both already touched it, but new thread may miss it
  • the whole thing is additionally platform-specific (and may deviate e. g. by windows if it is closes in main thread, the new thread cannot obtain a new standard handle in new thread, on linux it'd be able to do that.
  • to handle it properly, one has to know how many threads using some standard channel (e. g. N threads in use, but M thread ever touched some channel), inclusive possible timing issues on some race condition.

Basically the proper and consistent solution can be:

  1. either real sharing of standard channels across the threads (and common global array of them), what would be not so simple since tcl doesn't know about "sharing" of channels at all;
  2. or fully independent standard channels across the threads (may be again platform-specific thing or at least backwards incompatible);
  3. and/or complete avoidance of implicit reassignment of standard channels by open after close (need then some command to do that explicitly if wanted).
In any case it needs a clear definition what shall happen multi-threaded by closing of standard channels (in "shared" and "unshared" state), because at the moment it is rather an UB, and "duplicate channel names" error is rather an after-effect.


chw added on 2025-02-22 15:18:18:
There's now an open ticket

https://core.tcl-lang.org/thread/info/8c7d212ebece48e3

in the thread package for further discussion.

chw added on 2025-02-22 14:54:15:
I believe this is a real legit bug, since the Thread package
still shouldn't produce a crash. The problem is, that the
thread functions of both the tpool::create and thread::create
command do not proper setup the TSD of the I/O subsystem
before calling Tcl_CreateInterp() etc. This might be
remedied by calling the Tcl_GetStdChannel() function for
all standard channels before Tcl_CreateInterp().

sebres added on 2025-02-22 13:17:02:

It's not a bug, it's a documented feature... If the standard channels get closed, they will be reopen by next channel open command (chan/file/pipe/socket/whatever). Just in your case the unexpected thing - you do that not from main thread (and the channel is opened not for read)...

See https://stackoverflow.com/a/68869664 for more detailed answer to similar question.

No idea what are you trying to do by closing of stdin, but if it is something like signal to caller EOF, you can simply reopen new (dummy) stdin implicitly after `close stdin`, for instance with `chan pipe` command.
For instance, trying something like this in tcl-shell, you'd see that read side of pipe channels becomes (new) stdin:

    % close stdin; puts [chan pipe]
    stdin file3
Hereafter everything shall work as expected with the threads (and registering channels doesn't get confused).

By the way, this is Tcl- not thread-repository, so basically every thread-related stuff belongs to thread-repository.