/* * 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.query; import java.util.concurrent.Semaphore; import com.addthis.basis.util.Parameter; import com.addthis.bundle.channel.DataChannelOutput; import com.addthis.hydra.data.query.Query; import com.addthis.hydra.data.query.QueryException; import com.addthis.hydra.data.query.engine.QueryEngine; import com.addthis.hydra.data.query.source.QueryHandle; import com.addthis.hydra.data.query.source.QuerySource; import com.yammer.metrics.Metrics; import com.yammer.metrics.core.Gauge; import com.yammer.metrics.core.Histogram; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.channel.DefaultChannelProgressivePromise; import io.netty.util.concurrent.ImmediateEventExecutor; /** */ public abstract class QueryEngineSource implements QuerySource { private static final int maxConcurrency = Parameter.intValue("query.engine.source.maxConcurrency", 4); private final Logger log = LoggerFactory.getLogger(QueryEngineSource.class); private final Semaphore engineGate = new Semaphore(maxConcurrency); private final Gauge<Integer> engineGatePermitMetric = Metrics.newGauge(QueryEngineSource.class, "engineGatePermitMetric", new Gauge<Integer>() { @Override public Integer value() { return engineGate.availablePermits(); } }); private final Histogram engineGateHistogram = Metrics.newHistogram(QueryEngineSource.class, "engineGateHistogram"); @Override public QueryHandle query(final Query query, final DataChannelOutput consumer) throws QueryException { return new Handle(query, consumer); } public abstract QueryEngine getEngineLease(); /** */ private class Handle extends Thread implements QueryHandle { private Query query; private QueryEngine engine; private DataChannelOutput consumer; Handle(Query query, DataChannelOutput consumer) { setName("EngineSource " + query.uuid()); this.query = query; this.consumer = consumer; start(); } @Override public void run() { engine = null; try { engineGate.acquire(1); engineGateHistogram.update(engineGate.availablePermits()); engine = getEngineLease(); engine.search(query, consumer, new DefaultChannelProgressivePromise(null, ImmediateEventExecutor.INSTANCE)); consumer.sendComplete(); } catch (QueryException e) { log.warn("query exception " + query.uuid() + " " + e + " " + consumer); consumer.sourceError(e); } catch (Exception e) { log.warn("query error " + query.uuid() + " " + e + " " + consumer, e); consumer.sourceError(new QueryException(e)); } finally { engineGate.release(); engineGateHistogram.update(engineGate.availablePermits()); if (engine != null) { try { engine.release(); } catch (Throwable t) { log.warn("[dispatch] error during db release of " + engine + " : " + t, t); } } } } @Override public void cancel(String message) { log.warn(query.uuid() + " cancel called on handle " + consumer + " message: " + message); if (engine != null) { interrupt(); } } } @Override public void noop() { } @Override public boolean isClosed() { return false; } }