We are having problems with a mysterious NoSuchElementException within the Hibernate library which only seems to occur when the system is under heavy load and even then only occasionally (once every 5 hours or so).
We are using:
Hibernate version: 3.0.5
Database: PostgreSQL 8.0.3
Spring framework version: 1.2.3
Please see the stacktrace below. I have also included some snippets of the source code involved to clarify why we deem it so mysterious.
The stacktrace and the code snippet of SequencedHashMap.OrderedIterator below indicate that the iterator has already reached its end and that there is no next element available. However the code snippet of IdentityMap.entryArray() shows that prior to the invocation of method next() it has already invoked method hasNext() to verify the presence of a next element.
It seems that the only reasonable explanation for this behaviour is a concurrent modification of the collection by another thread such that afterwards no elements were left. Our application code does not make use of multi-threading! Tests have shown that only the standard system-group threads are present.
Could it be that somehow the Finalizer thread is causing a concurrent modification? E.g. when cleaning up Hibernate sessions that weren’t closed explicitly.
In any case it is clear that the design of method concurrentEntries() is flawed. It is not threadsafe while invoking method entryArray() although it is seemingly meant to support concurrent modification.
java.util.NoSuchElementException
at org.apache.commons.collections.SequencedHashMap$OrderedIterator.next(Unknown Source)
at org.hibernate.util.IdentityMap.entryArray(IdentityMap.java:195)
at org.hibernate.util.IdentityMap.concurrentEntries(IdentityMap.java:59)
at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:104)
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:59)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:730)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:324)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:86)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:490)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:495)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:468)
at org.springframework.transaction.interceptor.TransactionAspectSupport.doCommitTransactionAfterReturning(TransactionAspectSupport.java:258)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:144)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:174)
at $Proxy3.invokeWithinNewTx(Unknown Source)
..........
IdentityMap (v 1.5 2005/05/14 17:51:16):
Code:
/**
* Return the map entries (as instances of <tt>Map.Entry</tt> in a collection that
* is safe from concurrent modification). ie. we may safely add new instances to
* the underlying <tt>Map</tt> during iteration of the <tt>entries()</tt>.
*
* @param map
* @return Collection
*/
public static Map.Entry[] concurrentEntries(Map map) {
return ( (IdentityMap) map ).entryArray();
}
public Map.Entry[] entryArray() {
if (dirty) {
entryArray = new Map.Entry[ map.size() ];
Iterator iter = map.entrySet().iterator();
int i=0;
while ( iter.hasNext() ) {
Map.Entry me = (Map.Entry) iter.next();
entryArray[i++] = new IdentityMapEntry( ( IdentityKey) me.getKey() ).key, me.getValue() );
}
dirty = false;
}
return entryArray;
}
SequencedHashMap.OrderedIterator (Revision: 1.28 $ $Date: 2004/02/18 01:15:42):
Code:
/**
* Returns whether there is any additional elements in the iterator to be
* returned.
*
* @return <code>true</code> if there are more elements left to be
* returned from the iterator; <code>false</code> otherwise.
*/
public boolean hasNext() {
return pos.next != sentinel;
}
/**
* Returns the next element from the iterator.
*
* @return the next element from the iterator.
*
* @throws NoSuchElementException if there are no more elements in the
* iterator.
*
* @throws ConcurrentModificationException if a modification occurs in
* the underlying map.
*/
public Object next() {
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
if (pos.next == sentinel) {
throw new NoSuchElementException();
}
...
}