Description
When multiple channels share an AutorecoveringConnection and queues are coming and going from multiple theads, about 1 time out of 10000 , we are seeing a NullPointerException or ConcurrentModificationException to be thrown:
java.lang.NullPointerException
at com.rabbitmq.client.impl.recovery.AutorecoveringConnection.removeBindingsWithDestination(AutorecoveringConnection.java:768)
at com.rabbitmq.client.impl.recovery.AutorecoveringConnection.deleteRecordedQueue(AutorecoveringConnection.java:690)
at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.deleteRecordedQueue(AutorecoveringChannel.java:562)
at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.queueDelete(AutorecoveringChannel.java:303)
at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.queueDelete(AutorecoveringChannel.java:299)
The code at this location looks innocuous enough:
Set<RecordedBinding> removeBindingsWithDestination(String s) {
Set<RecordedBinding> result = new HashSet<RecordedBinding>();
for (Iterator<RecordedBinding> it = this.recordedBindings.iterator(); it.hasNext(); ) {
RecordedBinding b = it.next();
if(b.getDestination().equals(s)) { // [[ NPE here ]] //
it.remove();
result.add(b);
}
}
return result;
}
However, removeBindingsWithDestination
is not synchronized, so it's possible for multiple threads to be in this method changing the recordedBindings ArrayList at the same time as bindings come and go from multiple threads, even with channels not being shared across threads (but the Connection is shared).
It looks the other accesses to recordedBindings are synchronized (though caution: maybeDeleteRecordedAutoDeleteExchange
is synchronizing differently than the others) so removeBindingsWithDestination
may have just fallen through the cracks.
It may be a sufficient fix just to make removeBindingsWithDestination
synchronized.
(Another tangential thing I noticed is that most of the recorded maintenance is done from synchronized methods, but maybeDeleteRecordedAutoDeleteQueue
during a basicCancel
instead synchronizes on recordedQueues
and consumers
, and that this method just removes the recordedQueues
entry, not the recordedBindings
associated with the queue as well.)