SourceForge.net Logo
Main Overview Wiki Issues Forum Build Fisheye
Issue Details (XML | Word | Printable)

Key: CMP-572
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Shay Banon
Reporter: Matan
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
Compass

ScrollableHibernateIndexEntitiesIndexer evicts objects from the hibernate session that are still needed (can cause a LazyInitializationException)

Created: 03/Mar/08 10:38 AM   Updated: 10/Oct/10 05:11 PM
Component/s: Compass::Core
Affects Version/s: 2.0.0 M2
Fix Version/s: 2.0.0 M3


 Description  « Hide
Ok, I found the bug. It's not in Hibernate as i thought in the earlier post. It's in ScrollableHibernateIndexEntitiesIndexer.

The main problem is the prev/item mechanism used in lines 119-129:

Object prev = null;
while (cursor.next()) {
Object item = cursor.get(0);
if (item != prev && prev != null) { buffer.put(prev); }
prev = item;
}
if (prev != null) { buffer.put(prev); }

When buffer.put(prev) is called and the buffer is full, it calls session.clear(). This is very bad for the object "prev" passed in since it has been disconnected from the session. When compass tries to index it on the next RowBuffer.flush(), it will fail if it has any lazy associations. So this is a logic bug.

My suggestion is to remove the prev/item mechanism and just put items into the buffer and flush the buffer when it actually becomes full, not when you try to overflow past the buffer size because then the session.clear() would affect the current item.



 All   Comments   Change History      Sort Order: Ascending order - Click to sort in descending order
Matan added a comment - 03/Mar/08 10:39 AM
Here is an updated class with my fix:

/*

  • Copyright 2004-2006 the original author or authors.
    *
  • Licensed under the Apache License, Version 2.0 (the "License");
  • you may not use this file except in compliance with the License.
  • You may obtain a copy of the License at
    *
  • http://www.apache.org/licenses/LICENSE-2.0
    *
  • Unless required by applicable law or agreed to in writing, software
  • distributed under the License is distributed on an "AS IS" BASIS,
  • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  • See the License for the specific language governing permissions and
  • limitations under the License.
    */

package org.compass.gps.device.hibernate.indexer;

import java.util.HashMap;
import java.util.Map;
import java.util.Collection;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.compass.core.CompassSession;
import org.compass.gps.device.hibernate.HibernateGpsDevice;
import org.compass.gps.device.hibernate.HibernateGpsDeviceException;
import org.compass.gps.device.hibernate.entities.EntityInformation;
import org.compass.gps.device.support.parallel.IndexEntity;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.metadata.ClassMetadata;

/**

  • A Hibernate indexer uses Hibernate <code>ScrollableResults</code> to index the database
  • instead of using <code>setFirstResult</code> and <code>setMaxResults</code>. Using scrollable
  • results yields better performance especially for large result set.
    *
  • <p>First tries to call {@link org.compass.gps.device.hibernate.HibernateQueryProvider#createCriteria(org.hibernate.Session, org.compass.gps.device.hibernate.entities.EntityInformation)}
  • in order to use Hibernate <code>Criteria</code> to construct the cursor. If no criteria is returned (<code>null</code>
  • is returned), Hibernate <code>Query</code> is used by calling {@link org.compass.gps.device.hibernate.HibernateQueryProvider#createQuery(org.hibernate.Session, org.compass.gps.device.hibernate.entities.EntityInformation)}.
    *
  • <p>When using Criteria, by default, orders the results by entity id. This can be turned off either globablly using
  • {@link #setPerformOrderById(boolean)}, or per entity using {@link #setPerformOrderById(String, boolean)}.
    *
    * @author kimchy
    */
    public class ScrollableHibernateIndexEntitiesIndexer implements HibernateIndexEntitiesIndexer {

    private static final Log log = LogFactory.getLog(ScrollableHibernateIndexEntitiesIndexer.class);

    private HibernateGpsDevice device;

    private boolean performOrderById = true;

    private Map<String, Boolean> performOrderByPerEntity = new HashMap<String, Boolean>();

    public void setHibernateGpsDevice(HibernateGpsDevice device) { this.device = device; }

    /**
    * Should this indxer order by the ids when <code>Criteria</code> is available.
    * Defaults to <code>true</code>.
    */
    public void setPerformOrderById(boolean performOrderById) { this.performOrderById = performOrderById; }

    /**
    * Should this indxer order by the ids when <code>Criteria</code> is available for
    * the given entity. Defaults to {@link #setPerformOrderById(boolean)}.
    */
    public void setPerformOrderById(String entity, boolean performOrderById) { performOrderByPerEntity.put(entity, performOrderById); }

public void performIndex(CompassSession session, IndexEntity[] entities) {
for (IndexEntity entity : entities) {
EntityInformation entityInformation = (EntityInformation) entity;
if (device.isFilteredForIndex(entityInformation.getName())) { continue; }
if (!device.isRunning()) { return; }
ScrollableResults cursor = null;
Session hibernateSession = device.getSessionFactory().openSession();
Transaction hibernateTransaction = null;
try {
hibernateTransaction = hibernateSession.beginTransaction();
if (log.isDebugEnabled()) { log.debug(device.buildMessage("Indexing entities [" + entityInformation.getName() + "] using query [" + entityInformation.getQueryProvider() + "]")); }

Criteria criteria = entityInformation.getQueryProvider().createCriteria(hibernateSession, entityInformation);
if (criteria != null) {
if (performOrderById) {
Boolean performOrder = performOrderByPerEntity.get(entityInformation.getName());
if (performOrder == null || performOrder) { ClassMetadata metadata = hibernateSession.getSessionFactory().getClassMetadata(entityInformation.getName()); criteria.addOrder(Order.asc(metadata.getIdentifierPropertyName())); }
}
criteria.setFetchSize(device.getFetchCount());
cursor = criteria.scroll(ScrollMode.FORWARD_ONLY);
} else { Query query = entityInformation.getQueryProvider().createQuery(hibernateSession, entityInformation); cursor = query.scroll(ScrollMode.FORWARD_ONLY); }

// store things in row buffer to allow using batch fetching in Hibernate
RowBuffer buffer = new RowBuffer(session, hibernateSession, device.getFetchCount());
while (cursor.next()) { buffer.put(cursor.get(0)); }
buffer.close();
cursor.close();

hibernateTransaction.commit();
} catch (Exception e) {
log.error(device.buildMessage("Failed to index the database"), e);
if (cursor != null) {
try { cursor.close(); } catch (Exception e1) { log.warn(device.buildMessage("Failed to close cursor on error, ignoring"), e1); }
}
if (hibernateTransaction != null) {
try { hibernateTransaction.rollback(); } catch (Exception e1) { log.warn("Failed to rollback Hibernate", e1); }
}
if (!(e instanceof HibernateGpsDeviceException)) { throw new HibernateGpsDeviceException(device.buildMessage("Failed to index the database"), e); }
throw (HibernateGpsDeviceException) e;
} finally { hibernateSession.close(); session.close(); }
}
}

private class RowBuffer {
private Collection<Object> buffer;
private int bufferSize = 0;
private CompassSession compassSession;
private Session hibernateSession;

RowBuffer(CompassSession compassSession, Session hibernateSession, int fetchCount) { this.compassSession = compassSession; this.hibernateSession = hibernateSession; this.bufferSize = fetchCount; this.buffer = new ArrayList<Object>(this.bufferSize); }

public void put(Object row) { buffer.add(row); if (buffer.size() >= bufferSize) flush(); }

public void close() { flush(); buffer = null; }

private void flush() {
for (Object item : buffer) { compassSession.create(item); }
// clear buffer and sessions to allow for GC
buffer.clear();
hibernateSession.clear();
compassSession.evictAll();
}
}

}


Shay Banon added a comment - 03/Mar/08 11:53 AM
The prev and item logic is required to minimize indexing the same object. See this issue for more details: CMP-477. I will try and see if can implement it with it.

Shay Banon added a comment - 03/Mar/08 12:09 PM
I have just committed a fix for this, can you give it a go? Can you build Compass from SVN, or should I kick in a nightly build so you can download it from there?

Matan added a comment - 03/Mar/08 02:27 PM
thanks. i can pull a version from SVN tomorrow and give it a go. i'll let you know how it goes afterwards.

Matan added a comment - 04/Mar/08 02:59 AM
Looks good so far. Haven't got any errors.

Shay Banon added a comment - 04/Mar/08 03:23 AM
Great. I will wait with this issue until you give the final approvement.

Matan added a comment - 04/Mar/08 09:28 AM
you can go ahead and wrap it up. looks ok for me.


Razan Abbass added a comment - 10/Oct/10 05:11 PM