views:

408

answers:

3

I help maintain and build on a fairly large Swing GUI, with a lot of complex interaction. Often I find myself fixing bugs that are the result of things getting into odd states due to some race condition somewhere else in the code.

As the code base gets large, I've found it's gotten less consistent about specifying via documentation which methods have threading restrictions: most commonly, methods that must be run on the Swing EDT. Similarly, it would be useful to know and provide static awareness into which (of our custom) listeners are notified on the EDT by specification.

So it came to me that this should be something that could be easily enforced using annotations. Lo and behold, there exists at least one static analysis tool, CheckThread, that uses annotations to accomplish this. It seems to allow you to declare a method to be confined to a specific thread (most commonly the EDT), and will flag methods that try to call that method without also declaring themselves as confined to that thread.

So on the surface this just seems like a low-pain, huge-gain addition to the source and build cycle. My questions are:

  • Are there any success stories for people using CheckThread or similar libraries to enforce threading constraints? Any stories of failure? Why did it succeed/fail?
  • Is this good in theory? Are there theoretical downsides?
  • Is this good in practice? Is it worth it? What kind of value has it delivered?
  • If it works in practice, what are good tools to support this? I've just found CheckThread but admit I'm not entirely sure what I'm searching for to find other tools that do the same thing.

I know whether it's right for us depends on our scenario. But I've never heard of people using something like this in practice, and to be honest it doesn't seem to have taken hold much from some general browsing. So I'm wondering why.

+3  A: 

We haven't tried any static analysis tools, but we've used AspectJ to write a simple aspect that detects at runtime when any code in java.awt or javax.swing is invoked outside the EDT. It has found several places in our code that were missing a SwingUtilities.invokeLater(). We run with this aspect enabled throughout our QA cycle, then turn it off shortly before release.

Matt McHenry
Runtime analysis seems like a good thing as well, if paired with thorough testing. I'll admit that when I've had things in-dev in the past, I'd hack together a `checkEDT()` method that I'd remove occurrences of before committing. I suppose the reality is that race conditions don't change the question of whether or not the code was running on the correct thread. So maybe this is sufficient, and static analysis doesn't provide much better results? (Except for documentation and enforcement)
Mark Peters
That is a great idea to the check using aspectJ. Is it possible to share the code?
Jayan
@Jayan: No, sorry, I'm not allowed to share the code.
Matt McHenry
+2  A: 

As requested, this doesn’t pertain specifically to Java or the EDT, but I’ve seen good results with Coverity’s concurrency static analysis checkers for C/C++. They did have a higher false positive rate than less complicated checkers, but the code owners seemed willing to put up with that, given how hard threading bugs can be to find via testing. The details are confidential, I’m afraid, but Dawson Engler’s public papers (e.g., “Bugs as Deviant Behavior”) are very good on the general approach of “The following «N» instances of your code do «X» before doing «Y»,; this instance doesn’t.”

Flash Sheridan
I'm enjoying reading through that paper, and have taken a look at what Coverity offers in terms of concurrency. Engler's research is interesting in that it strives to identify bugs without prior knowledge of the system (system "rules", etc). I don't know how I feel about that since part of the allure of a solution that uses annotations (thus embedding the "rules" in the code) was that it would force documentation while simultaneously preventing bugs. This philosophy does however present an easy way to find bugs without having to first create those explicit rules. It's cheap to implement.
Mark Peters
+5  A: 

This answer is more focused on the theory aspect of your question.

Fundamentally you are making an assertion: "This methods runs only under certain threads". This assertion isn't really different than any other assertion you might make ("The method accepts only integers less than 17 for parameter X"). Issues are

  • Where do such assertions come from?
  • Can static analyzers check them?
  • Where do you get such a static analyzer?

Mostly such assertions have to come from the software designers, as they are the only people that know the intentions. The traditional term for this is "Design by Contract", although most DBC schemes are only over the current program state (C's assert macro) and they should really be over the programs' past and future states ("temporal assertions"), e.,g., "This routine will allocate a block of storage, and eventually some piece of code will deallocate it". One can build tools that try to determine hueristically what the assertions are (e.g., Engler's assertion induction work; others have done work in this area). That's useful, but the false positives are an issue. As practical matter, asking the designers to code such assertions doesn't seem particularly onerous, and is really good long term documentation. Whether you code such assertions with a specific "Contract" language construct, or with an if statement ("if Debug && Not(assertion) Then Fail();") or hide them in an annotation is really just a matter of convenience. Its nice when the language allows to code such assertions directly.

Checking of such assertions statically is difficult. If you stick with current-state only, the static analyzer pretty much has to do full data flow analysis of your entire application, because the information needed to satisfy the assertion likely comes from data created by another part of the application. (In your case, the "inside EDT" signal has to come from analyzing the whole call graph of the application to see if there is any call-path that leads to the method from a thread which is NOT the EDT thread). If you use temporal properties, the static check pretty much needs some kind of state-space verification logic in addition; these are presently still pretty much research tools. Even with all this machinery, static analyzers generally have to be "conservative" in their anlayses; if they can't demonstrate that something is false, they pretty much have to assume it is true, because of the halting problem.

Where do you get such analyzers? Given all the machinery needed, they're hard to build and so you should expect them to be rare. If somebody has built one, great. If not... as a general rule, you don't want do this yourself from scratch. The best long-term hope is to have generic program analysis machinery available on which to build such analyzers, to amortize the cost of building all the infrastructure. (I build program analyzer tool foundations; see our DMS Software Reengineering Toolkit).

One way to make it "easier" to build such static analyzers is to restrict the cases they handle to narrow scope, e.g., CheckThread. I'd expect CheckThread to do exactly what it presently does, and it would be unlikely to get a lot stronger.

The reason that "assert" macros and other such dynamic "current state" checks are popular is that they can actually be implemented by a simple runtime test. That's pretty practical. The problem here is that you may never exercise a path that leads to a failed conditions. So, for dynamic analysis, absence of detected failure is not really evidence of correctness. Still feels good.

Bottom line: static analyzers and dynamic analyzers each have their strength.

Ira Baxter
This is a pretty cool explanation of how to go about implementing a static analysis tool. Luckily, it seems like they already exist. Can you comment on what you mean by "restrict the cases they handle" when talking about CheckThread?
Mark Peters
As for runtime analysis tools not exercising every path...that was my thinking as well. Plus, instrumenting this for a runtime check is comparatively difficult unless you use bytecode injection. If you want to ensure that every Swing method is called from the EDT, you have to add a runtime check to every method that **calls** any Swing method. With static analysis (specifically CheckThread) you can configure the tool to automatically work backwords from those methods. It even comes with that preconfigured since Swing is constant over all projects.
Mark Peters
@Mark Peters: I don't know specifically the limits of CheckThread. My comments about restricting what tools do is based on seeing many tools built over the years by others as well as by myself; when you limit your scope, you maximize your chance of succeeding! Usually, when you have to build the infrastructure from scratch, you are *forced* to limit your scope, because the infrastructure can't give you enough support.
Ira Baxter
@Mark Peters: Regarding runtime check implementation: yes, you have to instrument a lot of paths. (You might use byte code instrumentation; you might more generallly do source code instrumentation [DMS will do this] because most programming languages don't have byte codes.) And I'm not sure you have to instrument every Swing caller; all you have to do is instrument the front door of all Swing routines to check that they have been called in the context of the EDT thread.
Ira Baxter