/* * 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()); } } }