From ff3405c967b9fd6701fa39c6bb57d0308dd6e1e6 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Fri, 14 Jun 2013 14:45:04 +0300 Subject: [PATCH] JCLOUDS-127: Added a method to check if the context is open - Added isOpen in Context interface - Refactoring Closer class: - method close can only call once - method close is threadsafe - added method getState --- core/src/main/java/org/jclouds/Context.java | 4 + .../org/jclouds/internal/ContextImpl.java | 9 +- .../java/org/jclouds/lifecycle/Closer.java | 28 +++++- .../lifecycle/config/LifeCycleModuleTest.java | 93 ++++++++++++++++++- 4 files changed, 129 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/jclouds/Context.java b/core/src/main/java/org/jclouds/Context.java index 27a5817bae..9e3dd276c2 100644 --- a/core/src/main/java/org/jclouds/Context.java +++ b/core/src/main/java/org/jclouds/Context.java @@ -67,4 +67,8 @@ public interface Context extends Location, Closeable { @Override void close(); + /** + * @return true if context open + */ + boolean isOpen(); } diff --git a/core/src/main/java/org/jclouds/internal/ContextImpl.java b/core/src/main/java/org/jclouds/internal/ContextImpl.java index a72472edfc..db399eb5fa 100644 --- a/core/src/main/java/org/jclouds/internal/ContextImpl.java +++ b/core/src/main/java/org/jclouds/internal/ContextImpl.java @@ -21,6 +21,8 @@ import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.io.Closeables.closeQuietly; +import static org.jclouds.lifecycle.Closer.State.AVAILABLE; + import java.net.URI; import java.util.Map; import java.util.Set; @@ -72,7 +74,12 @@ public class ContextImpl implements Context { closeQuietly(closer); } - /** + @Override + public boolean isOpen() { + return closer.getState() == AVAILABLE; + } + + /** * {@inheritDoc} */ @Override diff --git a/core/src/main/java/org/jclouds/lifecycle/Closer.java b/core/src/main/java/org/jclouds/lifecycle/Closer.java index 4598ecadac..6827e70b10 100644 --- a/core/src/main/java/org/jclouds/lifecycle/Closer.java +++ b/core/src/main/java/org/jclouds/lifecycle/Closer.java @@ -20,11 +20,14 @@ import java.io.Closeable; import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import javax.inject.Singleton; import com.google.common.collect.Lists; +import static org.jclouds.lifecycle.Closer.State.*; + /** * This will close objects in the reverse order that they were added. * @@ -35,14 +38,33 @@ public class Closer implements Closeable { // guice is single threaded. no need to lock this List methodsToClose = Lists. newArrayList(); + public enum State { + AVAILABLE, + PROCESSING, + DONE + } + + private final AtomicReference state; + + public Closer() { + this.state = new AtomicReference(AVAILABLE); + } + public void addToClose(Closeable toClose) { methodsToClose.add(toClose); } public void close() throws IOException { - Collections.reverse(methodsToClose); - for (Closeable toClose : methodsToClose) { - toClose.close(); + if (state.compareAndSet(AVAILABLE, PROCESSING)) { + Collections.reverse(methodsToClose); + for (Closeable toClose : methodsToClose) { + toClose.close(); + } + state.set(DONE); } } + + public State getState() { + return state.get(); + } } diff --git a/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java b/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java index 547db62599..7f8d8bae95 100644 --- a/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java +++ b/core/src/test/java/org/jclouds/lifecycle/config/LifeCycleModuleTest.java @@ -20,10 +20,13 @@ import static com.google.inject.name.Names.named; import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS; import static org.jclouds.Constants.PROPERTY_USER_THREADS; +import java.io.Closeable; import java.io.IOException; +import java.util.concurrent.CountDownLatch; import javax.annotation.PostConstruct; +import static org.easymock.EasyMock.*; import org.jclouds.concurrent.config.ExecutorServiceModule; import org.jclouds.lifecycle.Closer; import org.testng.annotations.Test; @@ -66,6 +69,7 @@ public class LifeCycleModuleTest { void testBindsCloser() { Injector i = createInjector(); assert i.getInstance(Closer.class) != null; + assert i.getInstance(Closer.class).getState() == Closer.State.AVAILABLE; } @Test @@ -75,8 +79,10 @@ public class LifeCycleModuleTest { named(PROPERTY_USER_THREADS))); assert !executor.isShutdown(); Closer closer = i.getInstance(Closer.class); + assert closer.getState() == Closer.State.AVAILABLE; closer.close(); assert executor.isShutdown(); + assert closer.getState() == Closer.State.DONE; } @Test @@ -89,9 +95,11 @@ public class LifeCycleModuleTest { named(PROPERTY_IO_WORKER_THREADS))); assert !ioExecutor.isShutdown(); Closer closer = i.getInstance(Closer.class); + assert closer.getState() == Closer.State.AVAILABLE; closer.close(); assert userExecutor.isShutdown(); assert ioExecutor.isShutdown(); + assert closer.getState() == Closer.State.DONE; } static class PostConstructable { @@ -112,7 +120,90 @@ public class LifeCycleModuleTest { }); PostConstructable postConstructable = i.getInstance(PostConstructable.class); assert postConstructable.isStarted; - } + @Test + void testCloserClosingState() throws InterruptedException { + Injector i = createInjector(); + final Closer closer = i.getInstance(Closer.class); + + final CountDownLatch closeDone = new CountDownLatch(1); + final CountDownLatch closeStart = new CountDownLatch(1); + + closer.addToClose(new Closeable() { + @Override + public void close() throws IOException { + try { + closeStart.countDown(); + assert closer.getState() == Closer.State.PROCESSING; + closeDone.await(); + } catch (InterruptedException e) { + assert false : e.getMessage(); + } + } + }); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + closer.close(); + } catch (IOException e) { + assert false : e.getMessage(); + } + } + }); + + thread.start(); + + closeStart.await(); + + assert closer.getState() == Closer.State.PROCESSING; + + closeDone.countDown(); + + thread.join(); + + assert closer.getState() == Closer.State.DONE; + } + + @Test + void testCloserCallOneClose() throws IOException, InterruptedException { + Injector i = createInjector(); + final Closer closer = i.getInstance(Closer.class); + + Closeable closeable = createStrictMock(Closeable.class); + + closeable.close(); + + expectLastCall(); + + replay(closeable); + + closer.addToClose(closeable); + + Runnable closeContext = new Runnable() { + @Override + public void run() { + try { + closer.close(); + } catch (IOException e) { + assert false : e.getMessage(); + } + } + }; + + Thread thread1 = new Thread(closeContext); + Thread thread2 = new Thread(closeContext); + + thread1.start(); + thread2.start(); + + thread1.join(); + thread2.join(); + + verify(closeable); + + assert closer.getState() == Closer.State.DONE; + } }