/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.query.engine.impl; import java.util.concurrent.TimeUnit; import org.apache.lucene.util.Counter; import org.hibernate.search.engine.spi.TimingSource; import org.hibernate.search.exception.SearchException; import org.hibernate.search.query.engine.spi.TimeoutExceptionFactory; import org.hibernate.search.query.engine.spi.TimeoutManager; /** * @author Emmanuel Bernard */ public class TimeoutManagerImpl implements TimeoutManager { // timeout in nanoseconds private Long timeout; private long start; boolean timedOut = false; private final Object query; private Type type; private boolean partialResults; private final TimeoutExceptionFactory timeoutExceptionFactory; private final TimingSource timingSource; public TimeoutManagerImpl(Object query, TimeoutExceptionFactory timeoutExceptionFactory, TimingSource timingSource) { this.query = query; this.timeoutExceptionFactory = timeoutExceptionFactory; this.timingSource = timingSource; } /** we start counting from this method call (if needed) */ @Override public void start() { if ( timeout == null ) { return; } this.start = System.nanoTime(); this.partialResults = false; } @Override public Long getTimeoutLeftInMilliseconds() { return getTimeoutLeft( 1000000 ); } @Override public Long getTimeoutLeftInSeconds() { return getTimeoutLeft( 1000000000 ); } private Long getTimeoutLeft(long factor) { if ( timeout == null ) { return null; } else { final long currentTime = System.nanoTime(); if ( isTimedOut( currentTime ) ) { //0 means no limit so we return the lowest possible value return 0l; } long left = timeout - ( currentTime - start); long result; if ( left % factor == 0 ) { result = left / factor; } else { result = (left / factor) + 1; } if ( result <= 0 ) { //0 means no limit so we return the lowest possible value return 0l; } else { return result; } } } @Override public boolean isTimedOut() { if ( timeout == null ) { return false; } if ( timedOut ) { return true; } return isTimedOut( System.nanoTime() ); } private boolean isTimedOut(long currentTime) { if ( timeout == null ) { return false; } if ( timedOut ) { return true; } else { final long elapsedTime = currentTime - start; timedOut = elapsedTime > timeout; if ( this.type != Type.LIMIT && timedOut ) { throw timeoutExceptionFactory.createTimeoutException( "Full-text query took longer than expected (in microsecond): " + TimeUnit.NANOSECONDS.toMicros( elapsedTime ), String.valueOf( query ) ); } return timedOut; } } @Override public void stop() { this.timeout = null; this.type = Type.NONE; //don't reset, we need it for the query API even when the manager is stopped. //this.partialResults = false; } @Override public void setTimeout(long timeout, TimeUnit timeUnit) { this.timeout = timeUnit.toNanos( timeout ); //timeout of 0 means no more timeout if ( timeout == 0 ) { stop(); } } public void forceTimedOut() { this.timedOut = Boolean.TRUE; if ( type == Type.LIMIT ) { //we stop where we are return what we have this.partialResults = true; } } @Override public void raiseExceptionOnTimeout() { if ( this.type == Type.LIMIT ) { throw new SearchException( "Cannot define both setTimeout and limitFetchingTime on a full-text query. Please report your need to the Hibernate team" ); } this.type = Type.EXCEPTION; } @Override public void limitFetchingOnTimeout() { if ( this.type == Type.EXCEPTION ) { throw new SearchException( "Cannot define both setTimeout and limitFetchingTime on a full-text query. Please report your need to the Hibernate team" ); } this.type = Type.LIMIT; } @Override public void reactOnQueryTimeoutExceptionWhileExtracting(RuntimeException e) { if ( type == Type.LIMIT ) { //we stop where we are return what we have this.partialResults = true; } else { if ( e == null ) { e = timeoutExceptionFactory.createTimeoutException( "Timeout period exceeded", String.valueOf( query ) ); } throw e; } } @Override public boolean hasPartialResults() { return partialResults; } @Override public Type getType() { return type; } public Counter getLuceneTimeoutCounter() { this.timingSource.ensureInitialized(); return new LuceneCounterAdapter( timingSource ); } /** * Converts our generic TimingSource so that Lucene can use it as a Counter */ private static final class LuceneCounterAdapter extends org.apache.lucene.util.Counter { private final TimingSource timingSource; public LuceneCounterAdapter(TimingSource timingSource) { this.timingSource = timingSource; } @Override public long addAndGet(final long delta) { //parameter delta is ignored as we don't use the clock ticking strategy from Lucene's threads //as I don't want to deal with statically referenced threads. return timingSource.getMonotonicTimeEstimate(); } @Override public long get() { return timingSource.getMonotonicTimeEstimate(); } } }