Long-lasting HTTP requests (suspend/response)

Some HTTP interactions may need to trigger a long running transaction on the server and wait for the result to generate a response. However, this can be problematic with an NIO-based server as there are generally a handful of threads servicing all requests. A long running transaction in this case would tie up one of the processing threads preventing it from servicing other requests. If enough of these long running transactions were initiated, it could lead to a denial of service.

To support these use cases without negatively impacting the server, Grizzly allows a response to be suspended until such time that the long running task is complete and the response is ready to be generated.

Let's cover the methods related to response suspend/resume. The following methods are available on the Response object itself:

    /**
     * Suspend the {@link Response}. Suspending a {@link Response} will
     * tell the underlying container to avoid recycling objects associated with
     * the current instance, and also to avoid committing response.
     */
    public void suspend() {
        ...
    }

    /**
     * Suspend the {@link Response}. Suspending a {@link Response} will
     * tell the underlying container to avoid recycling objects associated with
     * the current instance, and also to avoid committing response.
     *
     * @param timeout The maximum amount of time,
     * a {@link Response} can be suspended. When the timeout expires (because
     * nothing has been written or because the {@link Response#resume()}
     * or {@link Response#cancel()}), the {@link Response} will be automatically
     * resumed and committed. Usage of any methods of a {@link Response} that
     * times out will throw an {@link IllegalStateException}.
     * @param timeunit timeout units
     *
     */
    public void suspend(final long timeout, final TimeUnit timeunit) {
        ...
    }

    /**
     * Suspend the {@link Response}. Suspending a {@link Response} will
     * tell the underlying container to avoid recycling objects associated with
     * the current instance, and also to avoid committing response. When the
     * {@link Response#resume()} is invoked, the container will
     * make sure {@link CompletionHandler#completed(Object)}
     * is invoked with the original <tt>attachment</tt>. When the
     * {@link Response#cancel()} is invoked, the container will
     * make sure {@link org.glassfish.grizzly.CompletionHandler#cancelled()}
     * is invoked with the original <tt>attachment</tt>. If the timeout expires, the
     * {@link org.glassfish.grizzly.CompletionHandler#cancelled()} is invoked with 
     * the original <tt>attachment</tt> and the {@link Response} committed.
     *
     * @param timeout The maximum amount of time the {@link Response} can be suspended.
     * When the timeout expires (because nothing has been written or because the
     * {@link Response#resume()} or {@link Response#cancel()}), the {@link Response}
     * will be automatically resumed and committed. Usage of any methods of a
     * {@link Response} that times out will throw an {@link IllegalStateException}.
     * @param timeunit timeout units
     * @param completionHandler a {@link org.glassfish.grizzly.CompletionHandler}
     */
    public void suspend(final long timeout, 
                        final TimeUnit timeunit,
                        final CompletionHandler<Response> completionHandler) {
        ...
    }

    /**
     * Suspend the {@link Response}. Suspending a {@link Response} will
     * tell the underlying container to avoid recycling objects associated with
     * the current instance, and also to avoid committing response. When the
     * {@link Response#resume()} is invoked, the container will
     * make sure {@link CompletionHandler#completed(Object)}
     * is invoked with the original <tt>attachment</tt>. When the
     * {@link Response#cancel()} is invoked, the container will
     * make sure {@link org.glassfish.grizzly.CompletionHandler#cancelled()}
     * is invoked with the original <tt>attachment</tt>. If the timeout expires, the
     * {@link org.glassfish.grizzly.CompletionHandler#cancelled()} is invoked with the 
     * original <tt>attachment</tt> and the {@link Response} committed.
     *
     * @param timeout The maximum amount of time the {@link Response} can be suspended.
     * When the timeout expires (because nothing has been written or because the
     * {@link Response#resume()} or {@link Response#cancel()}), the {@link Response}
     * will be automatically resumed and committed. Usage of any methods of a
     * {@link Response} that times out will throw an {@link IllegalStateException}.
     * @param timeunit timeou
     * @param completionHandler a {@link org.glassfish.grizzly.CompletionHandler}
     * @param timeoutHandler {@link TimeoutHandler} to customize the suspended 
     *  <tt>Response</tt> timeout logic.
     */
    public void suspend(final long timeout, 
                        final TimeUnit timeunit,
                        final CompletionHandler<Response> completionHandler,
                        final TimeoutHandler timeoutHandler) {
        ...
    }

    /**
     * Complete the {@link Response} and finish/commit it. If a
     * {@link CompletionHandler} has been defined, its 
     * {@link CompletionHandler#completed(Object)} will first be invoked, 
     * then the {@link Response#finish()}.
     * Those operations commit the response.
     */
    public void resume() {
        ...
    }

    /**
     * Cancel the {@link Response} and finish/commit it. If a
     * {@link CompletionHandler} has been defined, its 
     * {@link CompletionHandler#cancelled()} will first be invoked, 
     * then the {@link Response#finish()}.
     * Those operations commit the response.
     */
    public void cancel() {
        ...
    }

    /**
     * Get the context of the suspended <tt>Response</tt>.
     *
     * @return the context of the suspended <tt>Response</tt>.
     */
    public SuspendContext getSuspendContext() {
        ...
    }
    
    /**
     * Return <tt>true<//tt> if that {@link Response#suspend()} has been
     * invoked and set to <tt>true</tt>
     * @return <tt>true<//tt> if that {@link Response#suspend()} has been
     * invoked and set to <tt>true</tt>
     */
    public boolean isSuspended() {
        ...
    }

The following diagram describes a typical suspend/resume scenario:

Figure 4.1. Suspend/Resume

Suspend/Resume

The "Suspended Response Queue" warrants some explaination. If a suspended Response has defined a timeout, it will be added to the "Suspended Response Queue". When the queue is processed, the current time will be evaluated against the timeout as defined within the Response's SuspendContext. If the timeout has been exceeded, the response will be committed and the Response object within the queue will be marked for removal. So in the case no timeout has been defined, the response will not be added to the queue, and the task may run indefiniately.