Skip to content

Instantly share code, notes, and snippets.

@gregw
Created May 31, 2018 09:39
Show Gist options
  • Save gregw/029f76e7de58b9689607171bec04732c to your computer and use it in GitHub Desktop.
Save gregw/029f76e7de58b9689607171bec04732c to your computer and use it in GitHub Desktop.
Filter to timeout requests
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