传统的解决方案,这里不再赘述。但是传统课堂上的解决方案,可能并不能满足要求。因为,可能我们在使用的是第三方框架,
我们的业务逻辑是写在三方框架的回调函数中的,代码由三方库调用。顺带一提,这也是spring框架开创的模式,不是由用户调用第三方框架,而是由第三方框架来调用用户代码。
例如如下代码片段:
void onIOEvent(final int readyOps) throws IOException {
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
if (socketChannel.isConnectionPending()) {
socketChannel.finishConnect();
}
//if cas return true,that means connect timeout task has not been triggered,timeoutTask can be cancled.
if (timeOutState.compareAndSet(TimeOutState.NOTSET, TimeOutState.CANCLE)) {
getWheelTimeOut().cancel();//cancle connect timeout task
setWheelTimeOut(null);
final InternalDataChannel dataChannel = dataChannelFactory.create(
key,
socketChannel,
sessionRequest.remoteEndpoint,
sessionRequest.attachment);
//use HashedWheelTimer to trigger read timeout task
SingleCoreIOReactor.timeWheel.newTimeout(getSocketTimeoutTask(dataChannel), dataChannel.getSocketTimeout(), TimeUnit.MILLISECONDS);
key.attach(dataChannel);
sessionRequest.completed(dataChannel);
dataChannel.handleIOEvent(SelectionKey.OP_CONNECT);
}
}
}
private TimerTask getSocketTimeoutTask(InternalDataChannel dataChannel){
return new TimerTask() {
@Override
public void run(WheelTimeout timeout) throws Exception {
final long currentTime = System.currentTimeMillis();
if(dataChannel != null && !dataChannel.isClosed() && dataChannel.checkTimeout(currentTime)){
final long delayTime = getLastReadTime() + dataChannel.getTimeout() - currentTime;
SingleCoreIOReactor.timeWheel.newTimeout(getSocketTimeoutTask(dataChannel),delayTime,TimeUnit.MILLISECONDS);
}
}
};
}
第一个语句中,使用时间轮算法启动一个定时任务。该定时任务被第三方框架调用,回调函数需要写在TimerTask子类的run方法
中。
我们的业务逻辑是,在run方法中,还需要进行业务逻辑的判断,如果发现满足条件,需要在回调函数中继续调用时间轮框架,传入TimerTask子类,进行延时逻辑处理。此处则必须使用递归处理了。
为了满足以上条件,我们写了一个getSocketTimeouTask方法,该方法返回一个TimerTask类。在run方法中,我们递归调用自身,达到run方法中继续调用延时框架进行延时处理的目的。
但是使用递归方式有一个严重的问题,栈溢出问题。该代码是进行socket读取超时判定,run方法中判定socket是否读超时(readtimeout),如果没有,则将下次的延时任务放入第三方框架,等待下次延时进行判断。如果socket一直未超时,将导致该递归方法不断递归下去,触发栈溢出。
netty3 增加了一个类,使用增加类的方式,可以将这个方法的递归问题回避掉。
思路如下:
public class ReadTimeoutTask implements TimerTask {
private InternalDataChannel dataChannel;
@Override
public void run(WheelTimeout timeout) throws Exception {
// TODO Auto-generated method stub
final long currentTime = System.currentTimeMillis();
if(dataChannel != null && !dataChannel.isClosed() && dataChannel.checkTimeout(currentTime)){
final long delayTime = dataChannel.getLastReadTime() + dataChannel.getTimeout() - currentTime;
SingleCoreIOReactor.timeWheel.newTimeout(this,delayTime,TimeUnit.MILLISECONDS);
}
}
public ReadTimeoutTask(InternalDataChannel dataChannel) {
this.dataChannel = dataChannel;
}
}
在该类的run方法回调时将自身传入第三方框架中。每次可以正常调用完毕run方法。新的延时任务是另外启动的,将this
传入,而不是像前一个方法,将自身方法递归传入。规避了递归问题,是一个非常牛逼的思路。
此时只需要将
SingleCoreIOReactor.timeWheel.newTimeout(getSocketTimeoutTask(dataChannel), dataChannel.getSocketTimeout(), TimeUnit.MILLISECONDS);
修改为:
SingleCoreIOReactor.timeWheel.newTimeout(new ReadTimeoutTask(dataChannel), dataChannel.getSocketTimeout(), TimeUnit.MILLISECONDS);