/*
* 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 com.addthis.hydra.data.query.engine;
import java.io.File;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.addthis.basis.util.Parameter;
import com.addthis.hydra.data.tree.DataTree;
import com.addthis.hydra.data.tree.ReadTree;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Meter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class EngineLoader extends CacheLoader<String, QueryEngine> {
private static final Logger log = LoggerFactory.getLogger(EngineLoader.class);
/**
* thread pool for refreshing engines. the max size of the pool tunes how many engine refreshes are allowed to
* occur concurrently. by limiting this amount, we reduce its ability to interfere with opening new engines.
* since new engines are actually blocking queries, they are more important.
*/
private static final int MAX_REFRESH_THREADS = Parameter.intValue("queryEngineCache.maxRefreshThreads", 1);
/**
* metric to track the number of 'new' engines opened
*/
protected static final Meter newEnginesOpened = Metrics.newMeter(QueryEngineCache.class, "newEnginesOpened",
"newEnginesOpened", TimeUnit.MINUTES);
private final ExecutorService engineRefresherPool =
new ThreadPoolExecutor(1, MAX_REFRESH_THREADS, 5000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setDaemon(true)
.setNameFormat("engineRefresher-%d")
.build());
@Override
public QueryEngine load(String dir) throws Exception {
QueryEngine qe = newQueryEngineDirectory(dir);
newEnginesOpened.mark();
return qe;
}
@Override
public ListenableFuture<QueryEngine> reload(final String dir, final QueryEngine oldValue) throws Exception {
//test for new data
if (((QueryEngineDirectory) oldValue).isOlder(dir)) {
ListenableFutureTask<QueryEngine> task =
ListenableFutureTask.create(new RefreshEngineCall(dir, oldValue, this));
engineRefresherPool.submit(task);
return task;
} else {
SettableFuture<QueryEngine> task = SettableFuture.create();
task.set(oldValue);
return task;
}
}
/**
* Creates engines for the cacheLoader and is called by load and reload to be the backend of get/refresh.
* Takes an unresolved directory, resolves it, creates an engine with the resolved directory, and returns it.
* If for any reason, it fails to create an engine, it throws an exception and attempts to close the tree/bdb if needed
*
* @param dir - Directory to open engine for -- may be (and usually is) an unresolved path with symlinks
* @return QueryEngine for that directory
* @throws Exception - any problem while making the engine
*/
protected QueryEngine newQueryEngineDirectory(String dir) throws Exception {
String canonicalDirString = new File(dir).getCanonicalPath();
DataTree tree = new ReadTree(new File(canonicalDirString));
try {
return new QueryEngineDirectory(tree, canonicalDirString);
} catch (Exception e) {
tree.close();
throw e;
}
}
}