Skip to content
This repository has been archived by the owner on May 28, 2018. It is now read-only.

Add suspend and resume support for the simple container #2071

Open
glassfishrobot opened this issue Mar 18, 2013 · 6 comments
Open

Add suspend and resume support for the simple container #2071

glassfishrobot opened this issue Mar 18, 2013 · 6 comments

Comments

@glassfishrobot
Copy link

I have attached a patch to get suspend and resume working properly with SimpleContainer. I have also added a new test for this.

Affected Versions

[2.0-m13, 2.0]

@glassfishrobot
Copy link
Author

Reported by gallagher_niall

@glassfishrobot
Copy link
Author

gallagher_niall said:
diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
index 15bdef6..c194a4a 100644
— a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainer.java
@@ -47,15 +47,23 @@ import java.security.Principal;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

-import javax.ws.rs.core.SecurityContext;

import javax.inject.Inject;
import javax.inject.Provider;
+import javax.ws.rs.core.SecurityContext;

+import org.glassfish.hk2.api.PerLookup;
+import org.glassfish.hk2.api.ServiceLocator;
+import org.glassfish.hk2.api.TypeLiteral;
+import org.glassfish.hk2.utilities.Binder;
+import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.inject.ReferencingFactory;
import org.glassfish.jersey.internal.util.ExtendedLogger;
@@ -70,18 +78,13 @@ import org.glassfish.jersey.server.internal.ConfigHelper;
import org.glassfish.jersey.server.spi.Container;
import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
import org.glassfish.jersey.server.spi.ContainerResponseWriter;
+import org.glassfish.jersey.server.spi.ContainerResponseWriter.TimeoutHandler;
import org.glassfish.jersey.server.spi.RequestScopedInitializer;

-import org.glassfish.hk2.api.PerLookup;
-import org.glassfish.hk2.api.ServiceLocator;
-import org.glassfish.hk2.api.TypeLiteral;
-import org.glassfish.hk2.utilities.Binder;
-import org.glassfish.hk2.utilities.binding.AbstractBinder;

import org.simpleframework.http.Address;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.Status;
+import org.simpleframework.util.thread.Daemon;

/**

  • Simple Jersey HTTP Container.
    @@ -132,14 +135,20 @@ public final class SimpleContainer implements org.simpleframework.http.core.Cont

private volatile ApplicationHandler appHandler;
private volatile ContainerLifecycleListener containerListener;

  • private final DelayQueue suspendQueue;
  • private final SuspendMonitor suspendMonitor;

private final static class Writer implements ContainerResponseWriter {

  • private final AtomicReference reference;
  • private final DelayQueue queue;
    private final Response response;
    private final Request request;
  • Writer(Request request, Response response) {
    • Writer(Request request, Response response, DelayQueue queue) { + this.reference = new AtomicReference(); this.response = response; this.request = request; + this.queue = queue; }

@OverRide
@@ -169,12 +178,33 @@ public final class SimpleContainer implements org.simpleframework.http.core.Cont

@OverRide
public boolean suspend(long timeOut, TimeUnit timeUnit, TimeoutHandler timeoutHandler) {

  • throw new UnsupportedOperationException("Method suspend is not supported by the container.");
    • SuspendTimer suspendTimer = reference.get();

    • if(suspendTimer == null)Unknown macro: {+ suspendTimer = new SuspendTimer(this, response, timeOut, timeUnit, timeoutHandler);+ reference.set(suspendTimer);+ + if(timeOut > 0) { + queue.offer(suspendTimer); + }+ }

    • return true;
      }

@OverRide
public void setSuspendTimeout(long timeOut, TimeUnit timeUnit) throws IllegalStateException {

  • throw new UnsupportedOperationException("Method suspend is not supported by the container.");

    • SuspendTimer suspendTimer = reference.get();

    • if(suspendTimer == null) { + throw new IllegalStateException("Response has not been suspended yet"); + }

    • if(queue.remove(suspendTimer))

    Unknown macro: {+ suspendTimer.setDelay(timeOut, timeUnit);+ + if(timeOut > 0) { + queue.offer(suspendTimer); + }+ }

    }

@OverRide
@@ -220,11 +250,124 @@ public final class SimpleContainer implements org.simpleframework.http.core.Cont
throw new ContainerException(error);
}
}

  • }
  • private final static class SuspendTimer implements Delayed {
  • private ContainerResponseWriter responseWriter;
  • private TimeoutHandler timeoutHandler;
  • private Response response;
  • private volatile long time;
  • public SuspendTimer(ContainerResponseWriter responseWriter, Response response, long delay, TimeUnit timeUnit, TimeoutHandler timeoutHandler)

{ + this.time = getTime() + timeUnit.toNanos(delay); + this.responseWriter = responseWriter; + this.timeoutHandler = timeoutHandler; + this.response = response; + }

  • public Response getResponse()

{ + return response; + }

  • public ContainerResponseWriter getContainerResponseWriter()

{ + return responseWriter; + }

  • public TimeoutHandler getTimeoutHandler()

{ + return timeoutHandler; + }

  • private long getTime()

{ + return System.nanoTime(); + }

  • public long getDelay(TimeUnit unit)

{ + return unit.convert(time - getTime(), TimeUnit.NANOSECONDS); + }

  • public void setDelay(long delay, TimeUnit unit)

{ + this.time = getTime() + unit.toNanos(delay); + }

  • public int compareTo(Delayed other) {
  • SuspendTimer entry = (SuspendTimer) other;
  • if(other == this)

{ + return 0; + }

  • return compareTo(entry);
  • }
  • private int compareTo(SuspendTimer entry) {
  • long diff = time - entry.time;
  • if(diff < 0)

{ + return -1; + }

else if(diff > 0)

{ + return 1; + }

  • return 0;
  • }
  • }
  • public static class SuspendMonitor extends Daemon {
  • private final DelayQueue delayQueue;
  • private final AtomicBoolean active;
  • public SuspendMonitor(DelayQueue delayQueue)

{ + this.active = new AtomicBoolean(true); + this.delayQueue = delayQueue; + }

  • public void stop()

{ + active.set(false); + }

  • @OverRide
  • public void run() {
  • while(active.get()) {
  • try {
  • SuspendTimer suspendTimer = delayQueue.poll(5L, TimeUnit.SECONDS);
  • if(suspendTimer != null) {
  • TimeoutHandler timeoutHandler = suspendTimer.getTimeoutHandler();
  • ContainerResponseWriter responseWriter = suspendTimer.getContainerResponseWriter();
  • Response response = suspendTimer.getResponse();
  • if(!response.isCommitted())

{ + SuspendTimeoutNotifier timeout = new SuspendTimeoutNotifier(responseWriter, timeoutHandler); + timeout.start(); + }

  • }
  • }catch(Throwable e)

{ + logger.log(Level.SEVERE, "Could not notify of timeout.", e); + Thread.yield(); + }

  • }
  • }
  • }
  • private static class SuspendTimeoutNotifier extends Daemon {
  • private ContainerResponseWriter responseWriter;
  • private TimeoutHandler timeoutHandler;
  • public SuspendTimeoutNotifier(ContainerResponseWriter responseWriter, TimeoutHandler timeoutHandler)

{ + this.responseWriter = responseWriter; + this.timeoutHandler = timeoutHandler; + }

{ + timeoutHandler.onTimeout(responseWriter); + }

}

public void handle(final Request request, final Response response)

{ - final Writer responseWriter = new Writer(request, response); + final Writer responseWriter = new Writer(request, response, suspendQueue); final URI baseUri = getBaseUri(request); final URI requestUri = baseUri.resolve(request.getTarget()); @@ -324,12 +467,17 @@ public final class SimpleContainer implements org.simpleframework.http.core.Cont containerListener.onReload(this); containerListener = ConfigHelper.getContainerLifecycleListener(appHandler); }

  • void onServerStop()

{ + this.suspendMonitor.stop(); + }

/**

  • Inform this container that the server was started. This method must be implicitly called after
  • the server containing this container is started.
    */
    void onServerStart() { + this.suspendMonitor.start(); this.containerListener.onStartup(this); }

@@ -340,6 +488,8 @@ public final class SimpleContainer implements org.simpleframework.http.core.Cont

  • @param application Jersey application to be deployed on Grizzly container.
    */
    SimpleContainer(final ApplicationHandler application) {
    • this.suspendQueue = new DelayQueue();
    • this.suspendMonitor = new SuspendMonitor(suspendQueue);
      this.appHandler = application;
      this.containerListener = ConfigHelper.getContainerLifecycleListener(application);

diff --git a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
index d0b00ed..a5b68a8 100644
— a/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
+++ b/containers/simple-http/src/main/java/org/glassfish/jersey/simple/SimpleContainerFactory.java
@@ -39,25 +39,24 @@
*/
package org.glassfish.jersey.simple;

+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+
+import javax.net.ssl.SSLContext;
+
import org.glassfish.jersey.internal.ProcessingException;
import org.glassfish.jersey.server.ApplicationHandler;
import org.glassfish.jersey.server.ContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
-import org.glassfish.jersey.server.internal.ConfigHelper;
-import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
import org.simpleframework.http.core.Container;
import org.simpleframework.http.core.ContainerServer;
import org.simpleframework.transport.Server;
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;

-import javax.net.ssl.SSLContext;
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
-import java.net.URI;

/**

  • Factory for creating and starting Simple server containers. This returns

  • a handle to the started server as {@link Closeable}

    instances, which allows
    @@ -111,7 +110,7 @@ public final class SimpleContainerFactory {
    *

  • @param address the URI to create the http server. The URI scheme must be

  • equal to "https". The URI user information and host

    • are ignored If the URI port is not present then port 143 will be
      • are ignored If the URI port is not present then port 443 will be
  • used. The URI path, query and fragment components are ignored.

  • @param context this is the SSL context used for SSL connections

  • @param config the resource configuration.
    @@ -157,7 +156,7 @@ public final class SimpleContainerFactory {
    *

  • @param address the URI to create the http server. The URI scheme must be

  • equal to "https". The URI user information and host

    • are ignored If the URI port is not present then port 143 will be
      • are ignored If the URI port is not present then port 443 will be
  • used. The URI path, query and fragment components are ignored.

  • @param context this is the SSL context used for SSL connections

  • @param container the container that handles all HTTP requests
    @@ -180,7 +179,7 @@ public final class SimpleContainerFactory {
    if (!scheme.equalsIgnoreCase("https")) { throw new IllegalArgumentException("The URI scheme should be 'https' when using SSL"); }

  • defaultPort = 143; // default HTTPS port

    • defaultPort = 443; // default HTTPS port
      }
      int port = address.getPort();

@@ -198,6 +197,26 @@ public final class SimpleContainerFactory {
} catch (IOException ex)

{ throw new ProcessingException("IOException thrown when trying to create simple server", ex); }

  • return connection;

    • return new ClosableContainer(container, connection);

    • }

    • private static final class ClosableContainer implements Closeable {

    • private final SimpleContainer container;

    • private final Connection connection;

    • public ClosableContainer(SimpleContainer container, Connection connection) { + this.connection = connection; + this.container = container; + }

    • @OverRide

    • public void close() throws IOException

    Unknown macro: {+ try { + connection.close(); + } finally { + container.onServerStop(); + }+ }

    }
    }

@glassfishrobot
Copy link
Author

gallagher_niall said:
/*

  • DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
    *
  • Copyright (c) 2011-2013 Oracle and/or its affiliates. All rights reserved.
    *
  • The contents of this file are subject to the terms of either the GNU
  • General Public License Version 2 only ("GPL") or the Common Development
  • and Distribution License("CDDL") (collectively, the "License"). You
  • may not use this file except in compliance with the License. You can
  • obtain a copy of the License at
  • http://glassfish.java.net/public/CDDL+GPL_1_1.html
  • or packager/legal/LICENSE.txt. See the License for the specific
  • language governing permissions and limitations under the License.
    *
  • When distributing the software, include this License Header Notice in each
  • file and include the License file at packager/legal/LICENSE.txt.
    *
  • GPL Classpath Exception:
  • Oracle designates this particular file as subject to the "Classpath"
  • exception as provided by Oracle in the GPL Version 2 section of the License
  • file that accompanied this code.
    *
  • Modifications:
  • If applicable, add the following below the License Header, with the fields
  • enclosed by brackets [] replaced by your own identifying information:
  • "Portions Copyright [year] [name of copyright owner]"
    *
  • Contributor(s):
  • If you wish your version of this file to be governed by only the CDDL or
  • only the GPL Version 2, indicate your decision by adding "[Contributor]
  • elects to include this software in this distribution under the [CDDL or GPL
  • Version 2] license." If you don't indicate a single choice of license, a
  • recipient has the option to distribute your version of this file under
  • either the CDDL, the GPL Version 2 or to extend the choice of license to
  • its licensees as provided above. However, if you add GPL Version 2 code
  • and therefore, elected the GPL Version 2 license, then the option applies
  • only if the new code is made subject to such option by the copyright
  • holder.
    */
    package org.glassfish.jersey.test.simple;

import static org.junit.Assert.assertEquals;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.Context;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.simple.SimpleContainer;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;

/**

  • Test class for {@link SimpleContainer}

    .
    *

  • @author Arul Dhesiaseelan ([email protected])

  • @author Miroslav Fuksa (miroslav.fuksa at oracle.com)
    */
    public class SimpleContainerSuspendTest extends JerseyTest {

/**

  • Creates new instance.
    */
    public SimpleContainerSuspendTest() { super(new SimpleTestContainerFactory()); }

@OverRide
protected ResourceConfig configure()

{ return new ResourceConfig(Resource.class); }

/**

  • Test resource class.
    */
    @path("one")
    public static class Resource {

private final Request request;
private final Response response;

public Resource(@context Request request,
@context Response response)

{ this.request = request; this.response = response; }

/**

  • Test resource method.
    *

  • @return Test simple string response.
    */
    @get
    public void call(@suspended final AsyncResponse response) {
    new Thread(new Runnable() {
    @OverRide
    public void run()Unknown macro: { try { Thread.sleep(5000); response.resume("get"); } catch(Exception e) { e.printStackTrace(); } }

    }).start();
    }
    }

@test
/**

  • Test {@link Simple HttpServer}

    container.
    */
    public void testSimpleContainerTarget()

    { int status = target().path("one").request().get().getStatus(); assertEquals("Response status unexpected.", 200, status); }

    }

@glassfishrobot
Copy link
Author

@mpotociar said:
To accept the patch we need a signed OCA. Please see here: http://jersey.java.net/contribute/

@glassfishrobot
Copy link
Author

gallagher_niall said:
I have already signed and submitted this a few years back. I am the original author of the SimpleContainer framework.

@glassfishrobot
Copy link
Author

This issue was imported from java.net JIRA JERSEY-1799

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants