/**
* (C) 2007-2010 Taobao Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
package com.taobao.tair.comm;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.common.IoFuture;
import org.apache.mina.common.IoFutureListener;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.WriteFuture;
import com.taobao.tair.etc.TairClientException;
import com.taobao.tair.packet.BasePacket;
public class TairClient {
private static final Log LOGGER = LogFactory.getLog(TairClient.class);
private static final boolean isDebugEnabled = LOGGER.isDebugEnabled();
private static ConcurrentHashMap<Integer, ResponseCallbackTask> callbackTasks=
new ConcurrentHashMap<Integer, ResponseCallbackTask>();
private static long minTimeout=100L;
private static ConcurrentHashMap<Integer, ArrayBlockingQueue<Object>> responses=
new ConcurrentHashMap<Integer, ArrayBlockingQueue<Object>>();
private final IoSession session;
private String key;
private TairClientFactory clientFactory;
private static Thread callBackTaskScan = new Thread(new CallbackTasksScan());
static{
callBackTaskScan.setName("Thread-" + CallbackTasksScan.class.getName());
callBackTaskScan.setDaemon(true);
callBackTaskScan.start();
}
protected TairClient(TairClientFactory factory, IoSession session,String key) {
this.session = session;
this.key=key;
this.clientFactory = factory;
}
public Object invoke(final BasePacket packet, final long timeout)
throws TairClientException {
if (isDebugEnabled) {
LOGGER.debug("send request [" + packet.getChid() + "],time is:"
+ System.currentTimeMillis());
}
ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(1);
responses.put(packet.getChid(), queue);
ByteBuffer bb = packet.getByteBuffer();
bb.flip();
byte[] data = new byte[bb.remaining()];
bb.get(data);
WriteFuture writeFuture = session.write(data);
writeFuture.addListener(new IoFutureListener() {
public void operationComplete(IoFuture future) {
WriteFuture wfuture = (WriteFuture) future;
if (wfuture.isWritten()) {
return;
}
String error = "send message to tair server error ["
+ packet.getChid() + "], tair server: "
+ session.getRemoteAddress()
+ ", maybe because this connection closed :"
+ !session.isConnected();
LOGGER.warn(error);
TairResponse response = new TairResponse();
response.setRequestId(packet.getChid());
response.setResponse(new TairClientException(error));
try {
putResponse(packet.getChid(), response.getResponse());
} catch (TairClientException e) {
// IGNORE,should not happen
}
// close this session
if(session.isConnected())
session.close();
else
clientFactory.removeClient(key);
}
});
Object response = null;
try {
response = queue.poll(timeout, TimeUnit.MILLISECONDS);
if (response == null) {
throw new TairClientException(
"tair client invoke timeout,timeout is: " + timeout
+ ",requestId is: " + packet.getChid() + "request type:" + packet.getClass().getName());
} else if (response instanceof TairClientException) {
throw (TairClientException) response;
}
} catch (InterruptedException e) {
throw new TairClientException("tair client invoke error", e);
} finally {
responses.remove(packet.getChid());
// For GC
queue = null;
}
if (isDebugEnabled) {
LOGGER.debug("return response [" + packet.getChid() + "],time is:"
+ System.currentTimeMillis());
LOGGER.debug("current responses size: " + responses.size());
}
// do decode here
if (response instanceof BasePacket) {
((BasePacket)response).decode();
}
return response;
}
public void invokeAsync(final BasePacket packet, final long timeout,ResponseListener listener){
if(isDebugEnabled){
LOGGER.debug("send request ["+packet.getChid()+"] async,time is:"+System.currentTimeMillis());
}
if(minTimeout>timeout){
minTimeout=timeout;
}
final ResponseCallbackTask callbackTask=new ResponseCallbackTask(packet.getChid(),listener, this.session, timeout);
callbackTasks.put(packet.getChid(), callbackTask);
ByteBuffer bb = packet.getByteBuffer();
bb.flip();
byte[] data = new byte[bb.remaining()];
bb.get(data);
WriteFuture writeFuture=session.write(data);
writeFuture.addListener(new IoFutureListener(){
public void operationComplete(IoFuture future) {
WriteFuture wfuture=(WriteFuture)future;
if(wfuture.isWritten()){
return;
}
String error = "send message to tair server error [" + packet.getChid() + "], tair server: " + session.getRemoteAddress()+", maybe because this connection closed :"+ !session.isConnected();
LOGGER.warn(error);
callbackTask.setResponse(new TairClientException(error));
// close this session
if(session.isConnected())
session.close();
else
clientFactory.removeClient(key);
}
});
}
protected void putResponse(Integer requestId, Object response)
throws TairClientException {
if (responses.containsKey(requestId)) {
try {
ArrayBlockingQueue<Object> queue = responses.get(requestId);
if (queue != null) {
queue.put(response);
if (isDebugEnabled) {
LOGGER.debug("put response [" + requestId
+ "],time is:" + System.currentTimeMillis());
}
} else if (isDebugEnabled) {
LOGGER.debug("give up the response,maybe because timeout,requestId is:"
+ requestId);
}
} catch (InterruptedException e) {
throw new TairClientException("put response error", e);
}
} else {
if (isDebugEnabled)
LOGGER
.debug("give up the response,maybe because timeout,requestId is:"
+ requestId);
}
}
protected boolean isCallbackTask(Integer requestId){
return callbackTasks.containsKey(requestId);
}
protected void putCallbackResponse(Integer requestId,Object response) throws TairClientException{
ResponseCallbackTask task=callbackTasks.get(requestId);
if(task==null)
return;
task.setResponse(response);
}
static class CallbackTasksScan implements Runnable{
static final long DEFAULT_SLEEPTIME=10L;
boolean isRunning=true;
final TairClientException timeoutException=new TairClientException("receive response timeout");
public void run() {
while(isRunning){
List<Integer> removeIds=new ArrayList<Integer>();
for (Entry<Integer, ResponseCallbackTask> entry: callbackTasks.entrySet()) {
long currentTime=System.currentTimeMillis();
ResponseCallbackTask task=entry.getValue();
if((task.getIsDone().get())){
removeIds.add(task.getRequestId());
}
else if(task.getTimeout() < currentTime){
removeIds.add(task.getRequestId());
task.setResponse(timeoutException);
}
}
for (Integer removeId : removeIds) {
callbackTasks.remove(removeId);
}
long sleepTime=DEFAULT_SLEEPTIME;
if(callbackTasks.size()==0){
sleepTime=minTimeout;
}
try {
Thread.sleep(sleepTime);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public String toString() {
if (this.session != null)
return this.session.toString();
return "null session client";
}
public void close() {
if (session != null) {
session.close();
}
if (responses != null) {
responses.clear();
}
}
}