/**
* Copyright 2014 The CmRaft Project
*
* 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 com.chicm.cmraft.util;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.chicm.cmraft.rpc.RpcTimeoutException;
import com.google.common.base.Preconditions;
/**
* A hash table supports blocking method like a blocking queue, except that
* user thread blocks only on specified keys. If a user thread trying to get
* a value with specified key which does not exist in the hash table, user thread
* is blocked until the key is put into the table.
*
* @author chicm
*
* @param <K>
* @param <V>
*/
public class BlockingHashMap <K,V> {
static final Log LOG = LogFactory.getLog(BlockingHashMap.class);
private ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
private ConcurrentHashMap<K, KeyLock> locks = new ConcurrentHashMap<>();
private void signalKeyArrive(K key) {
final KeyLock lock = locks.get(key);
if(lock == null)
return;
lock.lock();
try {
LOG.debug("singal key:[" + key + "] arrive");
lock.signal();
} finally {
lock.unlock();
}
}
public V put(K key, V value) {
V ret = map.put(key, value);
signalKeyArrive(key);
return ret;
}
private V remove(K key) {
locks.remove(key);
return map.remove(key);
}
public V take(K key) {
try {
return take(key, 0);
} catch (RpcTimeoutException e) {
LOG.error("Timeout", e);
return null;
}
}
/**
* Take and remove a value from the hash table, if specified key not in
* the hash table, it blocks until timeout
* @param key : key of the value
* @param timeout: timeout in milliseconds
* @return null if it does not get the value at specified timeout and retries
*/
public V take(K key, int timeout) throws RpcTimeoutException {
Preconditions.checkNotNull(key);
V ret = null;
KeyLock lock = locks.get(key);
if(lock == null) {
lock = new KeyLock();
locks.put(key, lock);
}
try {
lock.lock();
ret = map.get(key);
if(ret != null) {
remove(key);
return ret;
}
if(timeout==0) {
lock.await();
} else {
lock.await(timeout);
}
ret = map.get(key);
if(ret == null) {
throw new RpcTimeoutException("Rpc Timeout");
}
} catch (InterruptedException ex) {
LOG.error("InterruptedException", ex);
return ret;
}
finally {
lock.unlock();
}
remove(key);
return ret;
}
public int size() {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
public Set<K> keySet() {
return map.keySet();
}
public Collection<V> values() {
return map.values();
}
class KeyLock {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public ReentrantLock getLock() {
return lock;
}
public Condition getCondition () {
return condition;
}
public void lock() {
lock.lock();
}
public void unlock() {
lock.unlock();
}
public void signal() {
condition.signal();
}
public void await() throws InterruptedException {
condition.await();
}
public void await(int timeout) throws InterruptedException {
condition.await(timeout, TimeUnit.MILLISECONDS);
}
}
}