FCC: imap://vano@mail.mipt.ru/Sent X-Identity-Key: id1 X-Account-Key: account1 From: Ivan Pozdeev Subject: [Python-Dev] Tkinter threading model description and fix plan To: python-dev@python.org References: <20180502233705.7249da5f@fsol> <20180503000153.6f8d3b33@fsol> <20180503022619.GB9562@ando.pearwood.info> <20180504154243.GA9539@ando.pearwood.info> <93bae6b3-f5f5-ab7d-a375-a6b1e8971d4b@mail.mipt.ru> <20180506013950.GO9562@ando.pearwood.info> Message-ID: <06b82f8a-2b0b-7c76-81b5-d7756fc4b2a8@mail.mipt.ru> Date: Mon, 14 May 2018 06:32:12 +0300 X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0; attachmentreminder=0; deliveryformat=4 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Thunderbird/52.7.0 MIME-Version: 1.0 In-Reply-To: Content-Type: multipart/mixed; boundary="------------8D3691C81316FDBC42577CF6" Content-Language: en-US This is a multi-part message in MIME format. --------------8D3691C81316FDBC42577CF6 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: 8bit On 06.05.2018 23:10, Chris Angelico wrote:
On Mon, May 7, 2018 at 6:04 AM, Terry Reedy <tjreedy@udel.edu> wrote:
On 5/6/2018 10:03 AM, Chris Angelico wrote:
If it were up to me, I would deprecate non-threaded mode immediately,
Given that 99% of tkinter users do not need threaded tcl, why cut some of
them off?
"Non-threaded" really just means "non-thread-safe". 
Far from it. That's what I was talking about when I said that "no-one really knows how to do things right".
I'm trying to concentrate on the fixes as I said but just can't have people act on false premises here. I kinda hoped to come through by myself, but if you wish to act, too, we need to synchronize our visions so as not to disrupt each other's efforts. Maybe we'll even be able to collaborate.

Following is a complete description of the situation, and the fixing plan that I have in mind. Read carefully and ask about any items that you didn't understand -- to avoid any more misunderstanding.
In brackets, there are references; all code references are against `master` as of this writing.

---

Tkinter is _designed_ to be thread-safe, both with threaded and nonthreaded Tcl. From a Tkinter user's perspective, there's almost no difference: the API is the same, there are only a few small differences in behaviour (details below):

* First, I found a claim of thread safety right in the docs in the end: https://docs.python.org/3/library/tk.html .

* See the attached message for details about how Tkinter works with nonthreaded Tcl, complete with source references. (The mailing list that I posted it to is currently down, so can't give a web link. The list is https://sourceforge.net/p/tcl/mailman/tcl-core and the message date is 16 Apr 2018 18:05:56 +0300 -- in case python-dev archives don't save attachments.)

* In that message, I didn't elaborate how it works with threaded Tcl ('cuz that was off topic). The difference is:

    * a threaded Tcl interpreter is tied to an OS thread ( https://www.tcl.tk/doc/howto/thread_model.html )

    * so when making a Tkinter call to an interpreter from a thread other than the thread it's associated with ("the interpreter thread" hereinafter), Tkinter rather adds an event into the interpreter's queue that would execute the required code, then waits for result (Tcl's C API allows this) (e.g. _tkinter.c:1467 and _tkinter.c:Tkapp_ThreadSend)

    * the interpreter's event loop picks the event up eventually and executes the task, after which the call in the original thread unblocks and returns the result to its caller (_tkinter.c:Tkapp_ThreadSend)

    * under the hood, Tkinter reuses most nonthreaded versions of functions for threaded mode (an extremily elegant solution, avoids double duty in module maintenance):

        * If the current thread is the interpreter's thread, the execution simply falls through to the nonthreaded part. (e.g. _tkinter.c:1499)

        * A global lock that the nonthreaded parts wrap all Tcl calls into (the "Tcl lock") is made into a no-op when threaded Tcl is detected (_tkinter.c:637); the threaded parts use different locks instead (didn't look into details; see e.g. _tkinter.c:*Proc fns)

Tkinter's functionality for Py2 and Py3 is the same ( https://github.com/RedFantom/mtTkinter/issues/5#issuecomment-372524084 ), the key difference is the flavor of Tcl that the official releases bundle. Tkinter detects the Tcl flavor dynamically; in the source, it always defines TCL_THREADS that enables the Tcl thread API in tcl.h ( https://github.com/RedFantom/mtTkinter/issues/5#issuecomment-371796879 ) -- but I doubt it's possible to subsitute tcl*.dll with another flavor after the build: the DLLs for different flavors have different names and probably a different set of exports.

---

From a Tkinter user's perspective, the differences between threaded and nonthreaded Tcl are:

* If there are multiple interpreters (i.e. Tk() objects -- each of them embeds a Tkapp instance that embeds a Tcl interpreter instance):

    * In nonthreaded Tcl, only one Tcl call can be active at a time, globally (_tkinter.c:165)

    * In threaded Tcl, one call to each interpreter can be active at a time ( A Tcl interpreter only has one line of execution )

    This is transparent to the programmer though, and none of the tutorials ever touch multiple interpreters.

* In threaded Tcl, any calls from outside the interpreter's thread require the corresponding interpreter's `mainloop()` to be running

From Tkinter development perspective, due to the aforementioned code reuse, dropping support for nonthreaded Tcl would only reduce the code by a small margin (Tcl flavor detection code, the "Tcl lock").

---

The reality is that with neither flavor of Tcl is Tkinter completely thread-safe, but with threaded flavor, it's more so:

* with nonthreaded Tcl, making concurrent Tcl calls leads to crashes due to incorrect management of the "Tcl lock" as per https://bugs.python.org/issue33257
* with threaded Tcl, the only issue that I found so far is that a few APIs must be called from the interpreter's thread (https://bugs.python.org/issue33412#msg316152; so far, I know `mainloop()` and `destroy()` to be this) -- while most can be called from anywhere. Whether the exceptions are justified is a matter of discussion (e.g. at first glance, `destroy()` can be fixed).

---

The fixing plan as I sketched it:

* Fix the initial bug of https://bugs.python.org/issue33257 , then, separately, the https://bugs.python.org/issue33257#msg315878 bug.

* Document the current behaviour by rewriting https://docs.python.org/3/library/tkinter.html .

   Since I've recently learned Tkinter, I can say which critical information is missing and which existing one proved useless.

   Principles:
    * include fundamental information critical to understand the module's behaviour
    * concentrate on reference documentation for the module because it's more important to have than a user guide ( https://meta.serverfault.com/questions/8934/what-to-do-with-questions-when-the-answer-is-in-a-man-page#comment22241_8938 )
    * split off/drop anything unrelated to the above two

   Resulting scheme:
   0. Intro: +supported Tcl/Tk versions and the two flavors; the fact that Tkinter adds its own logic, and the Tkinter doc concentrates on that logic and refers to Tcl/Tk docs for details that are taken from there without changes.
   * Move external links section to be bottom.
   1. Architecture. "Unlike most other GUI toolkits, Tcl/Tk consists of a few separate modules with a clear distinction between them, and this is non-transparect to the user:..." Base on https://docs.python.org/3/library/tkinter.html#how-tk-and-tkinter-are-related , but focus on what implements what rather than what _calls_ what; drop Xlib entry (transparent implementation detail, not required for understanding).
   2. Threading model. The above-described general idea and user-visible limitations what can be called where and when.
   3. References for `tkinter`, `tkinter.Tk()`, `_tkinter.Tkapp` (public interface only -- `call()` at least). Mention which functions cannot be called from other threads. Do not mention the two issue33257 bugs.
   * Move widget reference to another page.
   * Drop Python-Tcl mappings unless the reference sections need them as supplemental reference.
   * Drop tutorial: too primitive to be useful. Move tutorials to another page like https://docs.python.org/3/library/logging.html does.
   * Drop https://docs.python.org/3/library/tk.html -- proved hard to find. Make https://docs.python.org/3/library/tkinter.html the head page instead.

* Discuss which of the described behaviour is as intended and which is a bug. Proceed with fixing.
When tkinter is import and a root is created, tkinter cannot know
whether the user is going to later make failing calls from threads.  Tkinter
has traditional been slow to remove support of old versions; it still
supports 8.4.  It will eventually become a moot point, at least on Windows,
as current Windows installers install threaded tcl.  I presume the same is
true for the new Mac installers.  I have no idea what people have on linux.
That's what I'm hoping for, yes. Eventually threaded will be the only
way to do things.

ChrisA
_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: https://mail.python.org/mailman/options/python-dev/vano%40mail.mipt.ru

-- 
Regards,
Ivan
--------------8D3691C81316FDBC42577CF6 Content-Type: message/rfc822; name="Re [TCLCORE] Calling into Tcl interpreter while a Tcl_CmdProc is active.eml" Content-Transfer-Encoding: 8bit Content-Disposition: attachment; filename*0="Re [TCLCORE] Calling into Tcl interpreter while a Tcl_CmdPr"; filename*1="oc is active.eml" Subject: Re: [TCLCORE] Calling into Tcl interpreter while a Tcl_CmdProc is active To: Donald Porter Cc: tcl-core@lists.sourceforge.net References: From: Ivan Pozdeev Message-ID: <5fdc7b51-dc8c-4f1a-700e-f4c5b97350f9@mail.ru> Date: Mon, 16 Apr 2018 18:05:56 +0300 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Thunderbird/52.7.0 MIME-Version: 1.0 In-Reply-To: Content-Type: multipart/alternative; boundary="------------88B5608F8C619935285AF187" Content-Language: en-US This is a multi-part message in MIME format. --------------88B5608F8C619935285AF187 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit Thank you all for your consideration. In the SO question, I couldn't describe the whole situation due to the site's policy -- and because I didn't want to bother you with more information than absolutely necessary. Looks like a full description did turn out to be necessary for you to make any sense of it. I hope it will resolve all the confusion and misunderstanding that the replies showed. In the description, I'll be giving short pointers to relevant code parts. At the end, there's a summary of them with URLs. On 15.04.2018 21:09, Donald Porter wrote: > Your question seems confused. Are there, or are there not multiple threads of execution when you have trouble? In this message you say “without threads” but the referenced descriptions appear to disagree. In brief, there are multiple threads of execution but Python does its best to make this irrelevant for Tcl (that's why I took the liberty to omit this fact in the SO post). Python uses Tcl through a middleware module called "Tkinter" (_tkinter.c ; the Python parts are irrelevant here). It is designed to work with both threaded and non-threaded versions of Tcl. The most probable reason why both versions are (still) supported is compatibility and backward compatibility (e.g. the 2.7 branch claims full backward compatibility since 2010). The official Windows releases are built against 8.5.19.0 nonthreaded (Python 2.x) and 8.6.6.0 threaded (Python 3.x) but the code would accept anything 8.4.2.0+ that it's built against (I'm almost sure that only the above versions are tested by the core team though). For POSIX, it's probably built against whatever version a distribution provides. In both threaded and non-threaded cases, the module strives to completely lift any thread limitations -- that is, allow any Tkinter calls from any Python threads. For that, when it creates an interpreter (Tkapp_New), it checks whether it's threaded, and sets a flag. All the public methods of Tkapp (Tkapp_methods[]) check that flag and fire one of the two parts of themselves. A "threaded" part checks the current thread, and if it's different than the thread the target interpreter is associated with, it forwards the call into the appropriate thread (I didn't look into specifics as it's irrelevant to the bug, but it seems to work smoothly). A "non-threaded" version (which is where the issue that I'm fixing happens) assumes that a non-threaded interpreter doesn't know or care about threads (like any other C library with no thread support).  It simply wraps any Tcl calls (to any interpreter) with a global lock named the "Tcl lock" (see a comment at _tkinter.c:148 and any usages of the macros described there). (More specifically, it declares to try to only do that for library calls that deal with "an interpreter's and/or global state". But after plenty of time with Tkinter, I've yet to see a single Tcl library call that doesn't.) So, whatever OS threads the calls to a nonthreaded interpreter happen to run in, from the interpreter's point of view, it all looks like a single thread of execution (that may just happen to change its ID between calls; but if the above assumption is correct, this shouldn't matter). All that Tkinter needs to do is to ensure that calls are sequential and in correct order. (As a side note, any calls into Python's machinery must also be wrapped with a lock, the (in)famous Global Interpreter Lock. This is why most macros that handle the Tcl lock also handle the Python lock.) Now, this is where the bug lies: sometimes, the Tcl lock isn't held when it should be, leading to race conditions if there are other threads waiting to make a call (and in my test examples, I make sure that there always are). There are two potential kinds of race conditions here: 1)A library call is simply made before another one returns; 2)A library call is made in sequence, but after the previous call, the interpreter and/or library is in an inappropriate state to accept it. **I don't know -- and want to know -- if Tcl actually has type 2 situations** -- these would be additional restrictions on when I can release the Tcl lock. I've already fixed type 1 race conditions in cases where Python simply makes a sequence of interdependent calls into Tcl and gets a result. I'll call these "Tcl interpreter calls" (Tkapp_Call et al.). A "call" constitutes of a `Tcl_Eval` or similar and a few auxiliary library calls like arguments allocation/deallocation and `Tcl_GetResult`. The fix was to hold the Tcl lock for the entire sequence. With the fix, up to 40 threads can be calling ` create line` concurrently (TkinterCrash2-2.py), and it all goes without a hitch. So far so good. Now, there's a case when during such an "interpreter call", Tcl calls back into Python, via a custom Tcl command implemented by Tkinter (PythonCmd) that runs a Python payload, then returns back into Tcl. This is where I'm stuck. The command's implementation wishes to "lend" the interpreter (by releasing the lock) to any other outstanding "calls" by other threads, only to reacquire it at the end to `Tcl_SetResult` and return. If the above "single thread illusion" is correct, it shouldn't matter which threads and in which order make the "calls", as long as the interpreter is in an appropriate state to accept new "calls" at the start of each one. If the Python payload wishes to make a "call", too, if will reacquire and release the lock for that, too, as usual. This looks good on paper. Unexpectedly, the example that uses these custom commands (TkinterHandlers.py) abort()s shortly, from inside Tcl, with a message on stderr: "TclStackFree: incorrect freePtr. Call out of sequence?" Due to success with the previous fix  -- which seems to have confirmed the "single thread illusion"'s soundness, -- and due to the message, I theorized that during a custom command's execution, the interpreter is somehow "not ready to accept new "calls"" as it's waiting for the custom command to return or something (i.e. there's a type 2 race condition, and I need to do something first to make the interpreter "ready", and at the end, undo this change). So, the question arises: if this theory is correct, how to make the interpreter "ready"? And if it's actually "ready", and something else is causing the abort(), what is the immediate cause, and what should I look for in Tkinter<->Tcl interaction to point me towards the root cause? Maybe Tcl has some tracing facilities that would give some helpful info? > If the program contains multiple threads, use a thread-enabled build of the Tcl library [*], and write your code to honor Tcl’s apartment model. That is, each (Tcl_Interp *) value is passed to routines of the Tcl library only by the same thread that originally receives that (Tcl_Interp *) value from a Tcl_CreateInterp() call. The description of the locking protocol suggests a violation of the apartment model. A locking protocol "allowing other calls to the same interpreter in the meantime” makes no sense. If the active thread is the one permitted to use the interp, no other thread is permitted to do so, so no other thread could take advantage of this locking scheme while operating correctly. > > It is commonplace to make calls into the Tcl library and pass the interp value from within the body of a Tcl_CmdProc. In fact, it’s difficult to imagine much of a useful command that did not at least call something like Tcl_SetObjResult(). It’s a mystery to me why your description asserts this cannot be done. > > I suspect strongly this is a situation where it is very important to follow the advice of “Show, Don’t Tell”. Descriptions of code are never as useful as just presenting the code that fails to act as you expect. Ends a lot of pointless guessing, https://github.com/native-api/cpython/tree/tcltk_race is a codebase with the current fix (based on 2.7 branch). https://github.com/native-api/cpython/blob/tcltk_race/Modules/_tkinter.c is the C part of Tkinter. It gets control from the Python part, https://github.com/native-api/cpython/blob/tcltk_race/Lib/lib-tk/Tkinter.py , which is what programs import. In _tkinter.c: * moduleMethods[] -- the module's public methods aka Python entry points. In the current case, only "create" is of interest. * Tkapp is a Python-class-implemented-in-C that encapsulates a Tcl interpreter   * TkappObject -- the body of a Tkapp instance   * Tkapp_New -- constructs a Tkapp instance, which includes a Tcl interpreter creation   * Tkapp_methods[] -- all public methods of Tkapp. Most of them are what I called "interpreter calls".     * Tkapp_Call -- this is by far the most used "interpreter call" (e.g. all Tk commands are done with it) * PythonCmd -- custom command implementation. It's reused for any event handler implemented in Python, just with different clientData that points to a specific Python payload. https://bugs.python.org/file47529/TkinterCrash2-2.py demonstrates race conditions in regular calls that are fixed by the fix. https://bugs.python.org/file47536/TkinterHanders.py demonstrates race conditions in calls with custom command which causes an abort(). > [*] Thread-enabled Tcl is the default everywhere for a long time now, and even longer on Windows, but if you’re still using Tcl 8.5.19 (why?) you might still drop into a thread-disabled mode by accident. Stop that. Use thread-enabled Tcl in multi-thread programs. > >> On Apr 15, 2018, at 1:04 PM, Ivan Pozdeev via Tcl-Core wrote: >> >> Could any of the local C interface experts consult me on >> https://stackoverflow.com/questions/49842937/call-other-tcl-commands-from-a-custom-command-tcl-cmdproc ? >> If there's a doc entry covering this topic, I couldn't find it. >> The specific version whose symptoms I described there is tcl-8.5.19.0, without threads, win64. The question proper is not specific to Windows or the version though. >> >> -- >> Regards, >> Ivan >> >> >> -- >> -- >> Regards, >> Ivan >> >> ------------------------------------------------------------------------------ >> Check out the vibrant tech community on one of the world's most >> engaging tech sites, Slashdot.org! http://sdm.link/slashdot >> _______________________________________________ >> Tcl-Core mailing list >> Tcl-Core@lists.sourceforge.net >> https://lists.sourceforge.net/lists/listinfo/tcl-core -- -- Regards, Ivan --------------88B5608F8C619935285AF187 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: 8bit Thank you all for your consideration.
In the SO question, I couldn't describe the whole situation due to the site's policy -- and because I didn't want to bother you with more information than absolutely necessary.

Looks like a full description did turn out to be necessary for you to make any sense of it.
I hope it will resolve all the confusion and misunderstanding that the replies showed.

In the description, I'll be giving short pointers to relevant code parts. At the end, there's a summary of them with URLs.

On 15.04.2018 21:09, Donald Porter wrote:

Your question seems confused. Are there, or are there not multiple threads of execution when you have trouble? In this message you say “without threads” but the referenced descriptions appear to disagree.
In brief, there are multiple threads of execution but Python does its best to make this irrelevant for Tcl (that's why I took the liberty to omit this fact in the SO post).

Python uses Tcl through a middleware module called "Tkinter" (_tkinter.c ; the Python parts are irrelevant here). It is designed to work with both threaded and non-threaded versions of Tcl. The most probable reason why both versions are (still) supported is compatibility and backward compatibility (e.g. the 2.7 branch claims full backward compatibility since 2010). The official Windows releases are built against 8.5.19.0 nonthreaded (Python 2.x) and 8.6.6.0 threaded (Python 3.x) but the code would accept anything 8.4.2.0+ that it's built against (I'm almost sure that only the above versions are tested by the core team though). For POSIX, it's probably built against whatever version a distribution provides.

In both threaded and non-threaded cases, the module strives to completely lift any thread limitations -- that is, allow any Tkinter calls from any Python threads.
For that, when it creates an interpreter (Tkapp_New), it checks whether it's threaded, and sets a flag.

All the public methods of Tkapp (Tkapp_methods[]) check that flag and fire one of the two parts of themselves.
A "threaded" part checks the current thread, and if it's different than the thread the target interpreter is associated with, it forwards the call into the appropriate thread (I didn't look into specifics as it's irrelevant to the bug, but it seems to work smoothly).

A "non-threaded" version (which is where the issue that I'm fixing happens) assumes that a non-threaded interpreter doesn't know or care about threads (like any other C library with no thread support).  It simply wraps any Tcl calls (to any interpreter) with a global lock named the "Tcl lock" (see a comment at _tkinter.c:148 and any usages of the macros described there). (More specifically, it declares to try to only do that for library calls that deal with "an interpreter's and/or global state". But after plenty of time with Tkinter, I've yet to see a single Tcl library call that doesn't.)

So, whatever OS threads the calls to a nonthreaded interpreter happen to run in, from the interpreter's point of view, it all looks like a single thread of execution (that may just happen to change its ID between calls; but if the above assumption is correct, this shouldn't matter). All that Tkinter needs to do is to ensure that calls are sequential and in correct order.

(As a side note, any calls into Python's machinery must also be wrapped with a lock, the (in)famous Global Interpreter Lock. This is why most macros that handle the Tcl lock also handle the Python lock.)


Now, this is where the bug lies: sometimes, the Tcl lock isn't held when it should be, leading to race conditions if there are other threads waiting to make a call (and in my test examples, I make sure that there always are).
There are two potential kinds of race conditions here: 1)A library call is simply made before another one returns; 2)A library call is made in sequence, but after the previous call, the interpreter and/or library is in an inappropriate state to accept it.
**I don't know -- and want to know -- if Tcl actually has type 2 situations** -- these would be additional restrictions on when I can release the Tcl lock.

I've already fixed type 1 race conditions in cases where Python simply makes a sequence of interdependent calls into Tcl and gets a result. I'll call these "Tcl interpreter calls" (Tkapp_Call et al.). A "call" constitutes of a `Tcl_Eval<xxx>` or similar and a few auxiliary library calls like arguments allocation/deallocation and `Tcl_Get<xxx>Result`. The fix was to hold the Tcl lock for the entire sequence.
With the fix, up to 40 threads can be calling `<canvas> create line` concurrently (TkinterCrash2-2.py), and it all goes without a hitch. So far so good.


Now, there's a case when during such an "interpreter call", Tcl calls back into Python, via a custom Tcl command implemented by Tkinter (PythonCmd) that runs a Python payload, then returns back into Tcl.
This is where I'm stuck.
The command's implementation wishes to "lend" the interpreter (by releasing the lock) to any other outstanding "calls" by other threads, only to reacquire it at the end to `Tcl_SetResult` and return. If the above "single thread illusion" is correct, it shouldn't matter which threads and in which order make the "calls", as long as the interpreter is in an appropriate state to accept new "calls" at the start of each one. If the Python payload wishes to make a "call", too, if will reacquire and release the lock for that, too, as usual.

This looks good on paper. Unexpectedly, the example that uses these custom commands (TkinterHandlers.py) abort()s shortly, from inside Tcl, with a message on stderr: "TclStackFree: incorrect freePtr. Call out of sequence?"

Due to success with the previous fix  -- which seems to have confirmed the "single thread illusion"'s soundness, -- and due to the message, I theorized that during a custom command's execution, the interpreter is somehow "not ready to accept new "calls"" as it's waiting for the custom command to return or something (i.e. there's a type 2 race condition, and I need to do something first to make the interpreter "ready", and at the end, undo this change).

So, the question arises: if this theory is correct, how to make the interpreter "ready"? And if it's actually "ready", and something else is causing the abort(), what is the immediate cause, and what should I look for in Tkinter<->Tcl interaction to point me towards the root cause? Maybe Tcl has some tracing facilities that would give some helpful info?

If the program contains multiple threads, use a thread-enabled build of the Tcl library [*], and write your code to honor Tcl’s apartment model. That is, each (Tcl_Interp *) value is passed to routines of the Tcl library only by the same thread that originally receives that (Tcl_Interp *) value  from a Tcl_CreateInterp() call. The description of the locking protocol suggests a violation of the apartment model. A locking protocol "allowing other calls to the same interpreter in the meantime” makes no sense. If the active thread is the one permitted to use the interp, no other thread is permitted to do so, so no other thread could take advantage of this locking scheme while operating correctly.

It is commonplace to make calls into the Tcl library and pass the interp value from within the body of a Tcl_CmdProc. In fact, it’s difficult to imagine much of a useful command that did not at least call something like Tcl_SetObjResult(). It’s a mystery to me why your description asserts this cannot be done.

I suspect strongly this is a situation where it is very important to follow the advice of “Show, Don’t Tell”. Descriptions of code are never as useful as just presenting the code that fails to act as you expect. Ends a lot of pointless guessing,
https://github.com/native-api/cpython/tree/tcltk_race is a codebase with the current fix (based on 2.7 branch).
https://github.com/native-api/cpython/blob/tcltk_race/Modules/_tkinter.c is the C part of Tkinter. It gets control from the Python part, https://github.com/native-api/cpython/blob/tcltk_race/Lib/lib-tk/Tkinter.py , which is what programs import.

In _tkinter.c:
* moduleMethods[] -- the module's public methods aka Python entry points. In the current case, only "create" is of interest.
* Tkapp is a Python-class-implemented-in-C that encapsulates a Tcl interpreter
  * TkappObject -- the body of a Tkapp instance
  * Tkapp_New -- constructs a Tkapp instance, which includes a Tcl interpreter creation
  * Tkapp_methods[] -- all public methods of Tkapp. Most of them are what I called "interpreter calls".
    * Tkapp_Call -- this is by far the most used "interpreter call" (e.g. all Tk commands are done with it)
* PythonCmd -- custom command implementation. It's reused for any event handler implemented in Python, just with different clientData that points to a specific Python payload.

https://bugs.python.org/file47529/TkinterCrash2-2.py demonstrates race conditions in regular calls that are fixed by the fix.
https://bugs.python.org/file47536/TkinterHanders.py demonstrates race conditions in calls with custom command which causes an abort().

[*] Thread-enabled Tcl is the default everywhere for a long time now, and even longer on Windows, but if you’re still using Tcl 8.5.19 (why?) you might still drop into a thread-disabled mode by accident. Stop that. Use thread-enabled Tcl in multi-thread programs.

On Apr 15, 2018, at 1:04 PM, Ivan Pozdeev via Tcl-Core <tcl-core@lists.sourceforge.net> wrote:

Could any of the local C interface experts consult me on
https://stackoverflow.com/questions/49842937/call-other-tcl-commands-from-a-custom-command-tcl-cmdproc ?
If there's a doc entry covering this topic, I couldn't find it.
The specific version whose symptoms I described there is tcl-8.5.19.0, without threads, win64. The question proper is not specific to Windows or the version though.

-- 
Regards,
Ivan


-- 
--
Regards,
Ivan

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Tcl-Core mailing list
Tcl-Core@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tcl-core

    

-- 
--
Regards,
Ivan
--------------88B5608F8C619935285AF187-- --------------8D3691C81316FDBC42577CF6--