Issue2321

classification
Title: Py.setSystemState() is a NOOP
Type: crash Severity: major
Components: Core Versions: Jython 2.7
Milestone: Jython 2.7.1
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: zyasoft Nosy List: JanVok, kevinmcmurtrie, oxmane, zyasoft
Priority: high Keywords: patch

Created on 2015-04-10.23:50:37 by kevinmcmurtrie, last changed 2016-01-06.15:59:29 by zyasoft.

Files
File name Uploaded Description Edit Remove
threadstate.diff zyasoft, 2015-12-23.00:30:07
Messages
msg9794 (view) Author: Kevin McMurtrie (kevinmcmurtrie) Date: 2015-04-10.23:50:37
Py.setSystemState(PySystemState newSystemState) places the new value into a ThreadState object that has only a weak reference.  It ceases to exist almost immediately, resulting in Py.setSystemState() doing nothing.  Almost every Python execution has a new default PySystemState.

The fancy two-layer cache in ThreadStateMapping is not necessary and it's causing this bug.
msg9795 (view) Author: Jim Baker (zyasoft) Date: 2015-04-11.03:09:26
Agreed, it's overzealous. The problem here is that it should be 

    private static final Map<Thread, ThreadState> cachedThreadState =
        new MapMaker().weakKeys().makeMap();

(so not weakValues())

We still want ThreadState to GC upon the corresponding Thread be GC'ed, so weakKeys() makes sense.
msg9797 (view) Author: Kevin McMurtrie (kevinmcmurtrie) Date: 2015-04-11.05:25:07
A weak key map of Thread to ThreadState is exactly what ThreadLocal does.  The intermediate Object[1] reference and MapMaker use makes no sense.  Only an instance of ThreadLocal<ThreadState> is needed here.
msg9800 (view) Author: Jim Baker (zyasoft) Date: 2015-04-11.12:24:20
Kevin, ThreadStateMapping.scopedThreadState uses an additional level of indirection to avoid having a ThreadLocal directly refer to a ThreadState, which is necessary to avoid a resource leak where the Thread object has a reference to the ClassLoader of the entire Jython runtime, thereby preventing the runtime from being unloaded until the thread exits. See http://blog.crazybob.org/2006/07/hard-core-java-threadlocal.html and #1327, as well as r7273 (https://hg.python.org/jython/rev/55f7eaddc954).
msg9804 (view) Author: Jim Baker (zyasoft) Date: 2015-04-11.14:09:59
Kevin, I have given some more thought to this problem. We have two sets of objects - Threads and ThreadState - which we are mapping with ThreadStateMapping.cachedThreadState. Threads may either run a single task, or be used in a thread pool to avoid the overhead of constructring new threads for a given task, with very different lifetimes. ThreadState objects are lightweight and simply track a mapping to PySystemState, along with some other dynamically scoped data, such as call_depth.

Because there are independent lifetimes on both sides, the mapping should be weak keys AND weak values (so I was wrong in msg9795). Otherwise a ThreadState could stop a Thread from being GC'ed, and vice versa. In particular, we don't want a long running Thread object in a thread poool to not be able to unload the Jython runtime, because it's strongly referring to a ThreadState, which in turn strongly refers to a PySystemState.

Interestingly, there's a small but significant performance improvement in benchmarks (my recollection is 10% or so on pystone) because even though the ThreadState can generally be recreated as necessary, just to avoid additional lookups.

However, this weak key/weak value mapping will raise an issue with code patterns like so, if there's no other reference to the PySystemState than a newly allocated ThreadState:

Py.setSystemState(new PySystemState());

In the Jython codebase, there's one example of the above fragment of code in restarting an interpreter. In this case it looks like the initialization will be done twice. However, this doesn't appear to do anything wrong, just slightly less efficient. In all other cases in the Jython codebase, such as by using a PythonInterpreter, a PySystemState always is being referred to with a strong reference, so there's no loss of this state.

We decided in 2.7 that we would make backwards breaking changes that we could not do in 2.5, as discussed in #1327, if it solves long standing memory leaks and other problems.
msg9805 (view) Author: Jim Baker (zyasoft) Date: 2015-04-11.14:13:54
Oops, didn't include some details - ThreadStates can generally be recreated of course if there's no dynamic scope (so call_depth, etc) because that's just a function of being in the thread stack. The fact that weakKeys/weakValues can still work here is that even a generational GC is not reclaiming ThreadState objects as fast a tight performance loop as seen in pystone.

We still might want to expand on the caching behavior in some way in 2.7.1
msg9867 (view) Author: Kevin McMurtrie (kevinmcmurtrie) Date: 2015-04-15.21:07:55
Code is breaking where the provided PySystemState must differ in some way from the default prototype - argv, path, stdout, stderr, stdin, etc.  Every call to Py.getSystemState() scattered throughout Jython potentially returns a new object until something calls ThreadState.enterRepr().  In debugging, many PySystemState instances may come and go before reaching that point.

Some refactoring is needed in Jython.  The problem is that ThreadState has become the real execution state but it's not very well exposed or managed.  PySystemState is well exposed but it's just a field in a temporary object.

My current workaround is to get my hands on the ThreadState and keep a hard reference:

final ThreadState ts= Py.getThreadState(newSystemState);
ts.systemState = newSystemState; 
 ... save ts somewhere so it doesn't evaporate ...

It's non-obvious and what it does to ThreadState.systemState isn't really legit.  It leaves a very high risk of future code calling Py.setSystemState(), because that looks right, and then failing for mysterious reasons.
msg9869 (view) Author: Jim Baker (zyasoft) Date: 2015-04-16.02:34:44
Kevin, I understand your concerns, and any input you might have into a better API would be highly appreciated. However, I think maintaining the hard reference explicitly can be a reasonable workaround for the moment, at least compared to the alternative that forced us to make these changes.
msg9962 (view) Author: Jim Baker (zyasoft) Date: 2015-04-25.20:24:49
Per discussion, we need a better API for setting PySystemState, and likely API deprecation. We should also better document expectations of how to use the API - much of our API is really not public, because of the limitations of Java - but this API is.
msg10028 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-07.12:01:59
Hello! This bug is biting my application which uses Py.setSystemState(). I was wondering what would be the best fix and I wonder if the following would be OK:

We change the code to be that of msg9795 (no weak values) but then we store the provided *PySystemState* to a WeakReference with a ReferenceQueue.

And then we use the associated ReferenceQueue to detect when the PySystemState is garbage collected so that we can remove the associated ThreadState from the map...

BTW, I was never really clear whether sharing the PySystemState between two threads was safe or not (this is where documenting the API would be helpful!).
msg10029 (view) Author: Jim Baker (zyasoft) Date: 2015-05-07.14:12:53
Jan, PySystemState can be shared by multiple threads and is designed to do so. Many applications only need to have one instance of a PySystemState, regardless of the number of threads they may be running.
msg10030 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-07.14:18:20
Jim, great this was my assumption... but I had not found a formal answer so far... What about the suggested solution? I am trying to hack an fix... would you like me to do it properly and submit a pull request? I was thinking of having ThreadState extend WeakReference<PySystemState> to find it again in the reference queue... anything wrong with that?
msg10031 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-07.14:55:20
Letting ThreadState extend WeakReference is not a viable option because  Py.setSystemState(...) needs to replace the reference held in the ThreadState... which is not possible in a WeakReference. Before I dig any further into the issue, I'll wait for your advice (which can be to just let *you* fix it). Please let me know.
msg10032 (view) Author: Jim Baker (zyasoft) Date: 2015-05-07.15:14:34
Jan, I think the answer is "you should fix it", not Jython. Basically all references to PySystemState should be owned by the container, whether that is org.python.util.jython, app containers like Jetty (via ModJy or Fireside), JSR223 (see PyScriptEngine), or any embedding solution.

However, there may be some common ways that this ownership should be managed, so that's where we can come up with a better API.
msg10033 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-07.15:42:57
Jim, I am afraid that Jython has to do something about it :) Please read carefully.

The issue is that the map is *not* a "weak-map" Thread -> PySystemState...

It is a "weak-map" Thread -> ThreadState

What is occurring is the following:
1. Py.setSystemState calls ThreadStateMapping.getThreadState 
2. which is creating a ThreadState instance... that 
3. *never gets* out of Py.setSystemState!!
4. As a local variable, it is garbage collected very early
5. The ThreadState is evicted from the "weak-map" since the value is GCed
6. Any time later, if I call Py.getSystemState, the "weak-map" does not find an entry for my thread
7. Py.getSystemState returns Py.defaultSystemState

So how do you want us to fix anything without a change in Jython?? I already keep a strong ref to the PySystemState... there's nothing I can do more outside of Jython.
msg10034 (view) Author: Jim Baker (zyasoft) Date: 2015-05-07.16:10:33
Jan, do you think you can create a patch that will fix this problem? We can then use this as a starting point for determining what the better API should be.
msg10045 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-11.08:48:30
Hi Jim, this is done in the pull-request 62: https://bitbucket.org/jython/jython/pull-request/62
With this, there is no change in the API. However, I might have missed something so don't hesitate to review with care! I have left a few comments/questions in the code...
Best regards
msg10047 (view) Author: Jim Baker (zyasoft) Date: 2015-05-11.14:13:07
Jan, thanks! I will review this PR today.
msg10052 (view) Author: Jim Baker (zyasoft) Date: 2015-05-12.00:58:46
Deferring review due to lack of time today, but definitely this week
msg10080 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-27.06:39:50
Hi Jim! Have you had any chance to do the review?
Please forgive my insisting, but I believe that the original bug title "Py.setSystemState() is a no-op" was more accurate... it is not just a documentation issue but a major regression for those who use that API function: it is a blocker for our transition to Jython 2.7.x
Cheers
msg10081 (view) Author: Jim Baker (zyasoft) Date: 2015-05-27.14:52:40
Jan, my apologies, I have been sick with a virus for the last 2 weeks, so this has impacted my productivity substantially. Presumably I will get better soon, and then I can do the detailed review that is required.
msg10082 (view) Author: Jan Vorwerk (JanVok) Date: 2015-05-27.15:24:37
Sorry to read that! I wish you a very fast recovery!
msg10176 (view) Author: Jan Vorwerk (JanVok) Date: 2015-08-13.14:32:27
Hello Jim! Hope you recovered from your sickness! Would you have a few minutes now? BTW, are there any plans for 2.7.1? Thanks in advance!
msg10432 (view) Author: Jan Vorwerk (JanVok) Date: 2015-11-03.17:07:18
I am changing the title because it looks that a doc issue has low priority and this is a serious regression wrt Jython 2.5.x
msg10549 (view) Author: Jim Baker (zyasoft) Date: 2015-12-22.06:40:47
Finally reviewed Jan's https://bitbucket.org/jython/jython/pull-requests/62/suggested-fix-for-2321/diff and with a couple of minor changes, this looks good. Looking forward to trying this out in beta 3!
msg10550 (view) Author: Jim Baker (zyasoft) Date: 2015-12-23.00:30:07
See this patch, based on JanVok's PR and modified by me per my review comments
msg10553 (view) Author: Jim Baker (zyasoft) Date: 2015-12-24.17:23:45
Fixed as of https://hg.python.org/jython/rev/dcff83c1c4ac
History
Date User Action Args
2016-01-06 15:59:29zyasoftsetstatus: pending -> closed
2015-12-24 17:23:45zyasoftsetstatus: open -> pending
assignee: zyasoft
resolution: accepted -> fixed
messages: + msg10553
2015-12-23 20:29:11zyasoftsetpriority: high
2015-12-23 00:30:09zyasoftsetfiles: + threadstate.diff
keywords: + patch
messages: + msg10550
2015-12-22 13:47:11oxmanesetnosy: + oxmane
2015-12-22 06:40:47zyasoftsetmessages: + msg10549
2015-11-03 17:07:19JanVoksetmessages: + msg10432
title: Need better API and docs for using PySystemState -> Py.setSystemState() is a NOOP
2015-08-13 14:32:28JanVoksetmessages: + msg10176
2015-05-27 15:24:38JanVoksetmessages: + msg10082
2015-05-27 14:52:40zyasoftsetmessages: + msg10081
2015-05-27 06:39:51JanVoksetmessages: + msg10080
2015-05-12 00:58:46zyasoftsetmessages: + msg10052
2015-05-11 14:13:07zyasoftsetmessages: + msg10047
2015-05-11 08:48:30JanVoksetmessages: + msg10045
2015-05-07 16:10:33zyasoftsetmessages: + msg10034
2015-05-07 15:42:57JanVoksetmessages: + msg10033
2015-05-07 15:14:35zyasoftsetmessages: + msg10032
2015-05-07 14:55:21JanVoksetmessages: + msg10031
2015-05-07 14:18:21JanVoksetmessages: + msg10030
2015-05-07 14:12:53zyasoftsetmessages: + msg10029
2015-05-07 12:01:59JanVoksetnosy: + JanVok
messages: + msg10028
2015-04-25 20:24:50zyasoftsetstatus: pending -> open
resolution: invalid -> accepted
title: Py.setSystemState() is a no-op -> Need better API and docs for using PySystemState
messages: + msg9962
milestone: Jython 2.7.1
2015-04-16 02:34:45zyasoftsetmessages: + msg9869
2015-04-15 21:07:56kevinmcmurtriesetmessages: + msg9867
2015-04-11 14:13:54zyasoftsetmessages: + msg9805
2015-04-11 14:09:59zyasoftsetstatus: open -> pending
resolution: invalid
messages: + msg9804
2015-04-11 12:24:20zyasoftsetmessages: + msg9800
2015-04-11 05:25:07kevinmcmurtriesetmessages: + msg9797
2015-04-11 03:09:27zyasoftsetnosy: + zyasoft
messages: + msg9795
2015-04-10 23:50:37kevinmcmurtriecreate