/* * Hibernate, Relational Persistence for Idiomatic Java * * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.hibernate.search.query.engine.impl; import java.util.concurrent.TimeUnit; import org.apache.lucene.search.Query; import org.apache.lucene.util.Counter; import org.hibernate.search.SearchException; import org.hibernate.search.engine.spi.TimingSource; 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 Query luceneQuery; private Type type; private boolean partialResults; private final TimeoutExceptionFactory timeoutExceptionFactory; private final TimingSource timingSource; public TimeoutManagerImpl(Query query, TimeoutExceptionFactory timeoutExceptionFactory, TimingSource timingSource) { this.luceneQuery = query; this.timeoutExceptionFactory = timeoutExceptionFactory; this.timingSource = timingSource; } /** we start counting from this method call (if needed) */ public void start() { if ( timeout == null ) return; this.start = System.nanoTime(); this.partialResults = false; } public Long getTimeoutLeftInMilliseconds() { return getTimeoutLeft( 1000000 ); } 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; } } } 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 ), luceneQuery ); } return timedOut; } } 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; } 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; } } 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; } 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; } 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", luceneQuery ); } throw e; } } public boolean hasPartialResults() { return partialResults; } 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 final 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 final long get() { return timingSource.getMonotonicTimeEstimate(); } } }