/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.drill.exec.store.hbase; import static com.google.common.base.Preconditions.checkNotNull; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.drill.common.exceptions.UserException; import org.apache.drill.exec.store.hbase.HBaseStoragePlugin.HBaseConnectionKey; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import com.google.common.util.concurrent.UncheckedExecutionException; /** * <p>A singleton class which manages the lifecycle of HBase connections.</p> * <p>One connection per storage plugin instance is maintained.</p> */ public final class HBaseConnectionManager extends CacheLoader<HBaseConnectionKey, Connection> implements RemovalListener<HBaseConnectionKey, Connection> { private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HBaseConnectionManager.class); public static final HBaseConnectionManager INSTANCE = new HBaseConnectionManager(); private final LoadingCache<HBaseConnectionKey, Connection> connectionCache; private HBaseConnectionManager() { this.connectionCache = CacheBuilder.newBuilder() .expireAfterAccess(1, TimeUnit.HOURS) // Connections will be closed after 1 hour of inactivity .removalListener(this) .build(this); } private boolean isValid(Connection conn) { return conn != null && !conn.isAborted() && !conn.isClosed(); } @Override public Connection load(HBaseConnectionKey key) throws Exception { Connection connection = ConnectionFactory.createConnection(key.getHBaseConf()); logger.info("HBase connection '{}' created.", connection); return connection; } @Override public void onRemoval(RemovalNotification<HBaseConnectionKey, Connection> notification) { try { Connection conn = notification.getValue(); if (isValid(conn)) { conn.close(); } logger.info("HBase connection '{}' closed.", conn); } catch (Throwable t) { logger.warn("Error while closing HBase connection.", t); } } public Connection getConnection(HBaseConnectionKey key) { checkNotNull(key); try { Connection conn = connectionCache.get(key); if (!isValid(conn)) { key.lock(); // invalidate the connection with a per storage plugin lock try { conn = connectionCache.get(key); if (!isValid(conn)) { connectionCache.invalidate(key); conn = connectionCache.get(key); } } finally { key.unlock(); } } return conn; } catch (ExecutionException | UncheckedExecutionException e) { throw UserException.dataReadError(e.getCause()).build(logger); } } public void closeConnection(HBaseConnectionKey key) { checkNotNull(key); connectionCache.invalidate(key); } }