/*
* Copyright (c) 2002-2009 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.index.impl;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.index.IndexService;
import org.neo4j.index.Isolation;
/**
* Use in {@link IndexService} implementations for asynchronous
* {@link Isolation} jobs.
*/
public class IndexServiceQueue extends Thread
{
private final GenericIndexService indexService;
private final ConcurrentLinkedQueue<QueueElement> queue =
new ConcurrentLinkedQueue<QueueElement>();
private final ArrayList<QueueElement> nonCommittedElements =
new ArrayList<QueueElement>();
// maximum number of index operation in transaction before commit
private static final int MAX_TX_OPERATION_COUNT = 100;
// max time in ms between each commit
private static final long MAX_WAIT_TIME = 600;
// max retries for a index operations
private static final int MAX_ERROR_COUNT = 3;
// max number of index operations before async also blocks
private static final int MAX_PENDING_OPERATIONS = 1000;
private boolean run = true;
private int txOperationCount = 0;
private long lastCommit;
private long currentTimestamp;
private Transaction tx;
private boolean done = false;
IndexServiceQueue( GenericIndexService service )
{
super( "IndexServiceQueue" );
this.indexService = service;
}
void queueIndex( Isolation level, Node node, String key, Object value )
{
if ( level == Isolation.ASYNC_OTHER_TX )
{
QueueElement qe = new QueueElement( Operation.ADD, node, key,
value );
queue.add( qe );
synchronized ( this )
{
this.notify();
}
if ( nonCommittedElements.size() >= MAX_PENDING_OPERATIONS )
{
waitForQueueElementNotify( qe );
}
}
else if ( level == Isolation.SYNC_OTHER_TX )
{
QueueElement qe = new QueueElement( Operation.ADD, node, key,
value );
queue.add( qe );
synchronized ( this )
{
this.notify();
}
waitForQueueElementNotify( qe );
}
else
{
throw new IllegalArgumentException( "Wrong isolation " + level );
}
}
void queueRemove( Isolation level, Node node, String key, Object value )
{
if ( level == Isolation.ASYNC_OTHER_TX )
{
QueueElement qe = new QueueElement( Operation.REMOVE, node, key,
value );
queue.add( qe );
synchronized ( this )
{
this.notify();
}
if ( nonCommittedElements.size() >= MAX_PENDING_OPERATIONS )
{
waitForQueueElementNotify( qe );
}
}
else if ( level == Isolation.SYNC_OTHER_TX )
{
QueueElement qe = new QueueElement( Operation.REMOVE, node, key,
value );
queue.add( qe );
synchronized ( this )
{
this.notify();
}
waitForQueueElementNotify( qe );
}
else
{
throw new IllegalArgumentException( "Wrong isolation " + level );
}
}
private void waitForQueueElementNotify( QueueElement qe )
{
do
{
try
{
synchronized ( qe )
{
qe.wait( 100 );
}
}
catch ( InterruptedException e )
{
Thread.interrupted();
}
} while ( !qe.indexed() );
}
private enum Operation
{
ADD,
REMOVE,
}
private static class QueueElement
{
final Operation operation;
final Node node;
final String key;
final Object value;
private volatile boolean indexed = false;
private int errorCount = 0;
QueueElement( Operation operation, Node node, String key, Object value )
{
this.operation = operation;
this.node = node;
this.key = key;
this.value = value;
}
boolean indexed()
{
return indexed;
}
void setIndexed()
{
indexed = true;
}
void tickError()
{
errorCount++;
}
int getErrorCount()
{
return errorCount;
}
}
@Override
public void run()
{
tx = indexService.beginTx();
lastCommit = System.currentTimeMillis();
try
{
while ( run || !queue.isEmpty() )
{
QueueElement qe = queue.poll();
try
{
if ( qe != null )
{
performIndexOperation( qe );
}
else
{
synchronized ( this )
{
this.wait( 100 );
}
currentTimestamp = System.currentTimeMillis();
if ( currentTimestamp - lastCommit > MAX_WAIT_TIME )
{
tx.success();
tx.finish();
tx = indexService.beginTx();
}
}
}
catch ( InterruptedException e )
{
Thread.interrupted();
}
}
tx.success();
}
finally
{
done = true;
tx.finish();
}
synchronized ( indexService )
{
indexService.notify();
}
}
private void performIndexOperation( QueueElement qe )
{
if ( qe.operation == Operation.ADD )
{
indexService.indexThisTx( qe.node, qe.key, qe.value );
}
else if ( qe.operation == Operation.REMOVE )
{
indexService.removeIndexThisTx( qe.node, qe.key, qe.value );
}
nonCommittedElements.add( qe );
txOperationCount++;
checkForCommit();
}
private void checkForCommit()
{
if ( txOperationCount >= MAX_TX_OPERATION_COUNT ||
( currentTimestamp - lastCommit ) >= MAX_WAIT_TIME )
{
tx.success();
try
{
lastCommit = System.currentTimeMillis();
tx.finish();
for ( QueueElement doneElement : nonCommittedElements )
{
doneElement.setIndexed();
synchronized ( doneElement )
{
doneElement.notify();
}
}
nonCommittedElements.clear();
}
catch ( Throwable t )
{
handleError( t );
}
tx = indexService.beginTx();
}
}
private void handleError( Throwable t )
{
System.out.println( "Problem with current index batch[" + t +
"] retrying..." );
for ( QueueElement qe : nonCommittedElements )
{
qe.tickError();
if ( qe.getErrorCount() >= MAX_ERROR_COUNT )
{
reportError( qe );
}
queue.add( qe );
}
nonCommittedElements.clear();
}
private void reportError( QueueElement qe )
{
System.out.println( "Unable to perform indexing operation: " +
qe.operation + " " + qe.node + " " + qe.key + "," + qe.value );
}
void stopRunning()
{
run = false;
synchronized ( indexService )
{
while ( !done )
{
try
{
indexService.wait( 500 );
}
catch ( InterruptedException e )
{
Thread.interrupted();
}
}
}
}
}