Created
May 31, 2018 09:39
-
-
Save gregw/029f76e7de58b9689607171bec04732c to your computer and use it in GitHub Desktop.
Filter to timeout requests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.example; | |
import java.io.IOException; | |
import java.util.concurrent.TimeUnit; | |
import java.util.concurrent.atomic.AtomicReference; | |
import javax.servlet.Filter; | |
import javax.servlet.FilterChain; | |
import javax.servlet.FilterConfig; | |
import javax.servlet.ServletException; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.ServletResponse; | |
import org.eclipse.jetty.io.CyclicTimeout; | |
import org.eclipse.jetty.util.log.Log; | |
import org.eclipse.jetty.util.log.Logger; | |
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; | |
import org.eclipse.jetty.util.thread.Scheduler; | |
public class TimeoutFilter implements Filter | |
{ | |
private final static Logger LOG = Log.getLogger(TimeoutFilter.class); | |
private final Scheduler _scheduler = new ScheduledExecutorScheduler("TimeoutFilter",false); | |
private final ThreadLocal<RequestTimer> _timers= new ThreadLocal<RequestTimer>() | |
{ | |
@Override | |
protected RequestTimer initialValue() | |
{ | |
return new RequestTimer(_scheduler); | |
} | |
}; | |
private int _requestTimeoutSeconds; | |
private int _interruptTimeoutSeconds; | |
@Override | |
public void init(FilterConfig filterConfig) throws ServletException | |
{ | |
try | |
{ | |
String s = filterConfig.getInitParameter("requestTimeoutSeconds"); | |
_requestTimeoutSeconds = s==null?120:Integer.parseInt(s); | |
s = filterConfig.getInitParameter("interruptTimeoutSeconds"); | |
_interruptTimeoutSeconds = s==null?30:Integer.parseInt(s); | |
_scheduler.start(); | |
} | |
catch (Exception e) | |
{ | |
throw new ServletException(e); | |
} | |
} | |
protected void interrupt(Thread thread) | |
{ | |
thread.interrupt(); | |
} | |
@SuppressWarnings("deprecation") | |
protected void stop(Thread thread) | |
{ | |
thread.stop(); | |
} | |
@Override | |
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException | |
{ | |
final RequestTimer timer = _timers.get(); | |
try | |
{ | |
timer.start(); | |
chain.doFilter(request,response); | |
} | |
finally | |
{ | |
timer.stop(); | |
} | |
} | |
@Override | |
public void destroy() | |
{ | |
try | |
{ | |
_scheduler.stop(); | |
} | |
catch (Exception e) | |
{ | |
LOG.warn(e); | |
} | |
} | |
enum State {IDLE, REQUEST, INTERRUPTED}; | |
private static class ThreadState | |
{ | |
private final Thread _thread; | |
private final State _state; | |
private ThreadState(Thread thread, State state) | |
{ | |
_thread = thread; | |
_state = state; | |
} | |
} | |
private final ThreadState IDLE = new ThreadState(null,State.IDLE); | |
private class RequestTimer extends CyclicTimeout | |
{ | |
private final AtomicReference<ThreadState> _thread = new AtomicReference<>(IDLE); | |
private RequestTimer(Scheduler scheduler) | |
{ | |
super(scheduler); | |
} | |
private void start() | |
{ | |
if (!_thread.compareAndSet(IDLE,new ThreadState(Thread.currentThread(),State.REQUEST))) | |
throw new IllegalStateException(); | |
schedule(_requestTimeoutSeconds,TimeUnit.SECONDS); | |
} | |
private void stop() | |
{ | |
while(true) | |
{ | |
ThreadState thread = _thread.get(); | |
switch(thread._state) | |
{ | |
case REQUEST: | |
case INTERRUPTED: | |
if (!_thread.compareAndSet(thread,IDLE)) | |
continue; | |
cancel(); | |
break; | |
default: | |
break; | |
} | |
break; | |
} | |
} | |
@Override | |
public void onTimeoutExpired() | |
{ | |
while(true) | |
{ | |
ThreadState thread = _thread.get(); | |
switch(thread._state) | |
{ | |
case REQUEST: | |
if (!_thread.compareAndSet(thread,new ThreadState(thread._thread,State.INTERRUPTED))) | |
continue; | |
TimeoutFilter.this.interrupt(thread._thread); | |
schedule(_interruptTimeoutSeconds,TimeUnit.SECONDS); | |
break; | |
case INTERRUPTED: | |
if (!_thread.compareAndSet(thread,null)) | |
continue; | |
TimeoutFilter.this.stop(thread._thread); | |
break; | |
default: | |
break; | |
} | |
break; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment