BlackBoard: Concurrent Modification Exception

(sorry, I should have replied to the BlackBoard thread instead of starting a new one)

I have two windows. In the first window, I click on a button. The second window (different browser, different session, hence different Application) has a registered listener. In the first window, I have two buttons, that tells the other window to show/hide a pop-up. The listener in the second window does this with a synchronized block, and the ICEPush servlet.

After a few show/hide clicks, I always manage to create the following condition (the number of clicks required varies)

As you notice from the log, the user interface click is an event that ultimate leads to the blackboard event firing.


java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
	at java.util.HashMap$KeyIterator.next(HashMap.java:828)
	at com.github.wolfie.blackboard.Blackboard.fire(Unknown Source)
	at org.concordiainternational.competition.ui.SessionData.fireBlackBoardEvent(SessionData.java:1112)
	at org.concordiainternational.competition.ui.SessionData.clearPublicAddressDisplay(SessionData.java:1080)
	at org.concordiainternational.competition.publicAddress.PublicAddressForm.clearDisplay(PublicAddressForm.java:114)
	at org.concordiainternational.competition.publicAddress.PublicAddressForm$2.buttonClick(PublicAddressForm.java:74)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:487)
	at com.vaadin.event.EventRouter.fireEvent(EventRouter.java:161)
	at com.vaadin.ui.AbstractComponent.fireEvent(AbstractComponent.java:1154)
	at com.vaadin.ui.Button.fireClick(Button.java:371)

Oh man. That seems like a weird race condition bug, or something… I’ve never seen that. Are you using one static instance of Blackboard, or what’s the scope? Probably unrelated, but what JVM are you using? it seems you’re not using 1.5, at least.

Apparently the Vaadin Eclipse plugin’s export function doesn’t compile the thing with debug info, so merely looking at the stack trace it’s hard to know what exactly is going on. But you could try and open the .jar file, take all the source files and put them directly in your project, so you’d get a more descriptive stacktrace.

I will copy the source to my project and have a look. There was a very similar bug in the Vaadin EventRouter where adding/removing listeners during event processing would fail in this way. The fix was to copy the values over which the iteration is made just before the loop, such that if the real list is changed during the loop, the C.M. Exception is not thrown.

My application is a push application where the blackboard is shared between several Vaadin Application instances, so it is indeed a situation prone to race conditions. We shall find out.

It was exactly what I suspected. One line fix. Patch attached.

[edit. I attached the patch as an attachment, but it did not go through]

So here is the patch

diff -r 8e113d1fef3d src/main/java/com/github/wolfie/blackboard/Blackboard.java
— a/src/main/java/com/github/wolfie/blackboard/Blackboard.java Thu Oct 07 07:37:28 2010 -0400
+++ b/src/main/java/com/github/wolfie/blackboard/Blackboard.java Thu Oct 07 07:39:39 2010 -0400
@@ -313,7 +313,11 @@
return;
}

  • for (final Listener listener : listenersForClass) {
  • // copy listenersForClass, so that if the invoked method adds or removes listeners
  • // the list being iterated on is not affected. the newly added/removed listeners will
  • // be taken into account when the next event fires.
  • HashSet iterationListeners = new HashSet(listenersForClass);
  • for (final Listener listener : iterationListeners) {
    try {
    Log.log(" triggering " + listener);

Thanks for the patch! I’ll put it into the next release of Blackboard (with all due credits, naturally).

Hope it all works better now.