Hi,
Found a potentially serious bug in the broadcast method that can lead to some
confusing behavior on event delivery.
Situation:
When an event is delivered to a JessListener via Rete.bradcastEvent(int, Object)
it naturally guards against other threads trying to access the listener list
(Vector) using a synchronized block on m_listeners. Although this guards against
other threads it does not and cannot stop the owning thread from adding a listener.
This would lead to deadlock. Essentially a JessListener can add a JessListener to
the same Rete engine dispatching the event within the eventHappened(JessEvent)
call. When this happens the newly added listener[s] is added to the end of the
Vector down stream of the current listener whose event handler is currently being
invoked. The same event will eventually reach the newly registered JessListener.
Convention:
By convention an event that fires takes a snapshot of the listeners at the point
of firing. Listeners added while firing should never see this event since they
acquired listener status after the fact.
Dilema:
In certain situations one listener may want to delagate reactions to events to
other listeners after the first firing. Under the present situation the latter
listener receives the first event as well as subsequent events.
Solution:
Clone the m_listeners Vector and operate on the vector. The synchronized block
can then just surround the clone() call.
Changes to Rete::broadcastEvent(int, Object):
final void broadcastEvent(int type, Object data) throws JessException
{
// only broadcast active events
if ((type & m_eventMask) == 0)
return;
// We lock this for two reasons. One, it's cheaper than going in and out of
// the vector methods over and over. Two, it prevents any other thread from
// messing up our count and triggering an ArrayIndexOutOfBounds exception. Note
// that we must call size() each time since a handler may remove a listener.
Vector cloned = null ;
synchronized (m_listeners) {
cloned = (Vector) m_listeners.clone() ;
}
if(cloned.size() == 0)
return ;
for(int i = 0; i < cloned.size(); i++) {
try {
m_theEvent.reset(type, data);
((JessListener) cloned.elementAt(i)).eventHappened(m_theEvent);
} catch(JessException je) {
throw je;
} catch(Exception e) {
throw new JessException("Rete.broadcastEvent",
"Event handler threw an exception",
e);
}
}
}
Hope this helps,
-Alex
Rete.java