/*
* Copyright 2010 NCHOVY
*
* 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 org.krakenapps.rpc.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.krakenapps.rpc.RpcBlockingTable;
import org.krakenapps.rpc.RpcMessage;
import org.krakenapps.rpc.RpcWaitingCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Each connection has RPC blocking table, because id scope of RPC message is
* bound by RPC connection. New message id is generated by connection, and
* blocking table use message id as waiting identifier.
*
* @author xeraph
*
*/
public class RpcBlockingTableImpl implements RpcBlockingTable {
private final Logger logger = LoggerFactory.getLogger(RpcBlockingTableImpl.class.getName());
private Map<Integer, RpcWaitingCall> lockMap;
private RpcMessage interruptSignal = RpcMessage.newException(-1, 0, 0, null);
public RpcBlockingTableImpl() {
lockMap = new ConcurrentHashMap<Integer, RpcWaitingCall>();
}
@Override
public Collection<RpcWaitingCall> getWaitingCalls() {
return Collections.unmodifiableCollection(lockMap.values());
}
@Override
public void cancel(int id) {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: interrupt waiting call {}", id);
RpcWaitingCallImpl item = (RpcWaitingCallImpl) lockMap.get(id);
if (item == null)
return;
synchronized (item) {
item.done(interruptSignal);
item.notifyAll();
}
}
/**
* Wakes up waiting threads
*
* @param id
* the message id
* @param response
* the response message
*/
public void signal(int id, RpcMessage response) {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: signal call response {}", id);
RpcWaitingCallImpl item = (RpcWaitingCallImpl) lockMap.get(id);
if (item == null) {
logger.warn("kraken-rpc: no waiting item {}, maybe timeout", id);
return;
}
synchronized (item) {
item.done(response);
item.notifyAll();
}
}
@Override
public RpcWaitingCall set(int id) {
RpcWaitingCallImpl item = new RpcWaitingCallImpl(id);
lockMap.put(id, item);
return item;
}
/**
* Blocks and waits response until response returns. It may not terminate
* because it waits infinitely without any timeout. In that case, you should
* interrupt waiting thread manually using rpc shell command.
*
* @param id
* the rpc message id
* @return the rpc response of call
* @throws InterruptedException
*/
public RpcMessage await(RpcWaitingCall item) throws InterruptedException {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: waiting call response id {}", item.getId());
try {
synchronized (item) {
while (item.getResult() == null)
item.wait();
}
if (item.getResult() == interruptSignal)
throw new InterruptedException("call cancelled");
} finally {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: removing blocking lock id {}", item.getId());
lockMap.remove(item.getId());
}
return item.getResult();
}
public RpcMessage await(RpcWaitingCall item, long timeout) throws InterruptedException {
long before = new Date().getTime();
try {
synchronized (item) {
while (item.getResult() == null) {
item.wait(timeout);
if (new Date().getTime() - before >= timeout) {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: blocking timeout of id {}", item.getId());
break;
}
}
}
if (item.getResult() == interruptSignal)
throw new InterruptedException("call cancelled");
} finally {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: blocking finished for id {}", item.getId());
lockMap.remove(item.getId());
}
return item.getResult();
}
private static class RpcWaitingCallImpl implements RpcWaitingCall {
private int id;
private Date since;
private RpcMessage result = null;
private CountDownLatch done;
public RpcWaitingCallImpl(int id) {
this.id = id;
this.since = new Date();
this.done = new CountDownLatch(1);
}
@Override
public int getId() {
return id;
}
@Override
public Date getSince() {
return since;
}
@Override
public void done(RpcMessage result) {
this.result = result;
done.countDown();
}
public RpcMessage getResult() {
return result;
}
@Override
public void await(int timeout) throws InterruptedException {
done.await(timeout, TimeUnit.MILLISECONDS);
}
@Override
public String toString() {
return String.format("id=%s, since=%s", id, since.toString());
}
}
}