I feel like I just had a moment of clarity where I saw the whole problem and then lost it. But..
There have been scattered reports of ConcurrentModificationExceptions in various other threads. CharPaneRequest does:
Code:
public Object run()
{
if ( CharPaneRequest.isRunning )
{
synchronized ( this )
{
return null;
}
}
synchronized ( this )
{
CharPaneRequest.isRunning = true;
super.run();
CharPaneRequest.isRunning = false;
}
if ( this.responseCode == 200 )
{
CharPaneRequest.lastResponse = responseText;
}
return null;
}
In order to avoid ConcurrentModificationExceptions from CharPaneRequests. BUT it calls super.run() which runs a RelayRequest - which is not synchronized. No concurrency lock is generated for it. So if an exception happens while super.run() is being called, the lock upstream in CharPaneRequest is
never released. There is nothing stopping two RelayRequest threads from interfering and that could permanently lock down CharPaneRequest - and anything that subsequently calls it would wait on a hung thread that will never complete.
I'm increasingly convinced that your patch would solve this symptom, but there is an underlying cause that needs to be treated, otherwise more very strange bugs will arise. Thread interference is notoriously difficult to detect and solve, so we should be happy that we've even detected here.
A more robust solution needs to be implemented for
all Requests. Concurrency should be expected, and handled gracefully. The current status quo is that we can have interleaving threads generated from the same object (i.e. concurrent UseSkillRequests can happen from moods and clicking the "up" arrow) or from two threads that call a parent's method (i.e. RelayRequest's run(), which is likely the problem here).
So you're on the right track here, I think. But the proper solution is not to remove all concurrency locks, but add more of them, make them better, with failsafes. Try.. finally() blocks that release the lock if something goes wrong. Etc.