Skip to content

Commit

Permalink
feat: Allow to globally set the scheduling mode to 'manual'
Browse files Browse the repository at this point in the history
This commit provides the functionality for parameterized GuiceAsync
initialization.
When setting the respective flag, all @scheduled methods behave as if
they were annotated with @ManuallyScheduled

#7
  • Loading branch information
skuzzle committed Jan 22, 2020
1 parent 4ddebae commit bbf144f
Show file tree
Hide file tree
Showing 18 changed files with 378 additions and 98 deletions.
86 changes: 86 additions & 0 deletions src/it/java/de/skuzzle/inject/async/ManuallyScheduledIT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package de.skuzzle.inject.async;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.concurrent.CountDownLatch;

import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;

import de.skuzzle.inject.async.guice.GuiceAsync;
import de.skuzzle.inject.async.guice.ScheduleFeature;
import de.skuzzle.inject.async.schedule.ScheduleProperties;
import de.skuzzle.inject.async.schedule.SchedulingService;
import de.skuzzle.inject.async.schedule.annotation.Scheduled;
import de.skuzzle.inject.async.schedule.annotation.SimpleTrigger;

public class ManuallyScheduledIT {

private volatile CountDownLatch manualLatch = new CountDownLatch(2);
private volatile int counterManual;

@Inject
private SchedulingService schedulingService;

@Scheduled
@SimpleTrigger(100)
public void testManual() {
++counterManual;
manualLatch.countDown();
}

@Test
public void testManuallyStart() throws Exception {
Guice.createInjector(new AbstractModule() {

@Override
protected void configure() {
final ScheduleProperties disableAutoScheduling = ScheduleProperties.defaultProperties()
.disableAutoScheduling();

GuiceAsync.enableFeaturesFor(binder(),
ScheduleFeature.withProperties(disableAutoScheduling));
}
}).injectMembers(this);

schedulingService.startManualScheduling();
manualLatch.await();
assertTrue(counterManual > 0);
}

@Test
public void testDoNotManuallyStart() throws Exception {
Guice.createInjector(new AbstractModule() {

@Override
protected void configure() {
final ScheduleProperties disableAutoScheduling = ScheduleProperties.defaultProperties()
.disableAutoScheduling();

GuiceAsync.enableFeaturesFor(binder(),
ScheduleFeature.withProperties(disableAutoScheduling));
}
}).injectMembers(this);

// this thing waits forever because scheduler is never started
final Thread waitForTimeout = new Thread(() -> {
try {
manualLatch.await();
// XXX: this would not fail the test because it occurs in wrong thread
fail();
} catch (final InterruptedException ignore) {
}
});

waitForTimeout.start();
Thread.sleep(1000);
waitForTimeout.interrupt();
assertEquals(0, counterManual);
}

}
3 changes: 2 additions & 1 deletion src/it/java/de/skuzzle/inject/async/ScheduledIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.inject.Provides;
import com.google.inject.name.Names;

import de.skuzzle.inject.async.guice.DefaultFeatures;
import de.skuzzle.inject.async.guice.GuiceAsync;
import de.skuzzle.inject.async.schedule.ExceptionHandler;
import de.skuzzle.inject.async.schedule.ExecutionContext;
Expand Down Expand Up @@ -158,7 +159,7 @@ public void setup() {

@Override
protected void configure() {
GuiceAsync.enableFor(binder());
GuiceAsync.enableFeaturesFor(binder(), DefaultFeatures.SCHEDULE);

bind(TestExceptionHandler.class).asEagerSingleton();
bind(TypeWithScheduledMethods.class).asEagerSingleton();
Expand Down
72 changes: 72 additions & 0 deletions src/main/java/de/skuzzle/inject/async/guice/DefaultFeatures.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package de.skuzzle.inject.async.guice;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Binder;
import com.google.inject.Injector;

import de.skuzzle.inject.async.methods.AsyncModule;
import de.skuzzle.inject.async.methods.annotation.Async;
import de.skuzzle.inject.async.schedule.ScheduleModule;
import de.skuzzle.inject.async.schedule.ScheduleProperties;
import de.skuzzle.inject.async.schedule.annotation.Scheduled;

/**
* Supported features that can be passed when initializing the async/scheduling subsystem.
* Each feature is self contained and has no dependence to other features being present.
*
* @author Simon Taddiken
* @since 2.0.0
*/
public enum DefaultFeatures implements Feature {
/** This feature enables handling of the {@link Async} annotation. */
ASYNC {
@Override
public void installModuleTo(Binder binder, GuiceAsync principal) {
binder.install(new AsyncModule(principal));
}

@Override
public boolean cleanupExecutor(Injector injector, long timeout, TimeUnit timeUnit) {
final ExecutorService executor = injector.getInstance(Keys.DEFAULT_EXECUTOR_KEY);
if (!Shutdown.executor(executor, timeout, timeUnit)) {
LOG.warn("There are still active tasks lingering in default executor after shutdown. Wait time: {} {}",
timeout, timeUnit);
return false;
}
return true;
}
},
/**
* This feature enables handling of the {@link Scheduled} annotation. You may also use
* an instance of {@link ScheduleFeature} instead of this instance (but better do not
* provide both).
*/
SCHEDULE {
@Override
public void installModuleTo(Binder binder, GuiceAsync principal) {
binder.install(new ScheduleModule(
principal,
ScheduleProperties.defaultProperties()));
}

@Override
public boolean cleanupExecutor(Injector injector, long timeout, TimeUnit timeUnit) {
final ScheduledExecutorService scheduler = injector.getInstance(Keys.DEFAULT_SCHEDULER_KEY);
if (!Shutdown.executor(scheduler, timeout, timeUnit)) {
LOG.warn("There are still active tasks lingering in default scheduler after shutdown. Wait time: {} {}",
timeout, timeUnit);
return false;
}
return true;
}
};

private static final Logger LOG = LoggerFactory.getLogger(DefaultFeatures.class);

}
38 changes: 29 additions & 9 deletions src/main/java/de/skuzzle/inject/async/guice/Feature.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
package de.skuzzle.inject.async.guice;

import de.skuzzle.inject.async.methods.annotation.Async;
import de.skuzzle.inject.async.schedule.annotation.Scheduled;
import java.util.concurrent.TimeUnit;

import com.google.inject.Binder;
import com.google.inject.Injector;

/**
* Supported features that can be passed when initializing the async/scheduling subsystem.
* Each feature is self contained and has no dependence to other features being present.
* A stand alone feature that can be passed to {@link GuiceAsync}. Use
* {@link DefaultFeatures} or the dedicated {@link ScheduleFeature} which allows
* customization.
*
* @author Simon Taddiken
* @since 2.0.0
*/
public enum Feature {
/** This feature enables handling of the {@link Async} annotation. */
ASYNC,
/** This feature enables handling of the {@link Scheduled} annotation. */
SCHEDULE
public interface Feature {

/**
* Installs the modules relevant to this feature to the given {@link Binder}.
*
* @param binder The binder to install any required modules to.
* @param principal The {@link GuiceAsync} instance guarding the modules from
* unintended instantiation.
*/
void installModuleTo(Binder binder, GuiceAsync principal);

/**
* Makes sure to shutdown this feature's executor when
* {@link GuiceAsyncService#shutdown(long, TimeUnit)} is being called.
*
* @param injector The injector.
* @param timeout The time to wait for an orderly shutdown.
* @param timeUnit Unit for the timeout parameter.
* @return Whether the executor orderly shutdown within given time.
*/
boolean cleanupExecutor(Injector injector, long timeout, TimeUnit timeUnit);
}
41 changes: 16 additions & 25 deletions src/main/java/de/skuzzle/inject/async/guice/GuiceAsync.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,19 @@

import static com.google.common.base.Preconditions.checkArgument;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.ThreadFactory;

import javax.inject.Singleton;

import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Provides;

import de.skuzzle.inject.async.methods.AsyncModule;
import de.skuzzle.inject.async.methods.annotation.Async;
import de.skuzzle.inject.async.schedule.ScheduleModule;
import de.skuzzle.inject.async.schedule.annotation.Scheduled;

/**
Expand All @@ -35,8 +32,8 @@
* </pre>
* <p>
* You may choose to only enable scheduling OR async methods in case you do not need both.
* See {@link #enableFeaturesFor(Binder, Feature...)} and
* {@link #createModuleWithFeatures(Feature...)}.
* See {@link #enableFeaturesFor(Binder, DefaultFeatures...)} and
* {@link #createModuleWithFeatures(DefaultFeatures...)}.
* <p>
* Please see the JavaDoc of the {@link Async} and {@link Scheduled} annotation for
* further usage information.
Expand All @@ -58,15 +55,17 @@ private GuiceAsync() {
* @param binder The binder to register with.
*/
public static void enableFor(Binder binder) {
enableFeaturesFor(binder, Feature.ASYNC, Feature.SCHEDULE);
enableFeaturesFor(binder, DefaultFeatures.ASYNC, DefaultFeatures.SCHEDULE);
}

/**
* Enable support for the given {@link Feature features}. Allows to separately enable
* support for async or scheduled.
* Enable support for the given {@link DefaultFeatures features}. Allows to separately
* enable support for async or scheduled.
*
* @param binder The binder to register with.
* @param features The features to enable.
* @since 2.0.0
* @see DefaultFeatures
*/
public static void enableFeaturesFor(Binder binder, Feature... features) {
checkArgument(binder != null, "binder must not be null");
Expand All @@ -81,52 +80,44 @@ public static void enableFeaturesFor(Binder binder, Feature... features) {
* @since 0.2.0
*/
public static Module createModule() {
return createModuleWithFeatures(Feature.ASYNC, Feature.SCHEDULE);
return createModuleWithFeatures(DefaultFeatures.ASYNC, DefaultFeatures.SCHEDULE);
}

/**
* Creates a module taht can be used to enable the given features.
* Creates a module that can be used to enable the given features.
*
* @param features The features to enable.
* @return The module.
* @since 2.0.0
*/
public static Module createModuleWithFeatures(Feature... features) {
final GuiceAsync principal = new GuiceAsync();
final EnumSet<Feature> featureSet = EnumSet.copyOf(Arrays.asList(features));
final Set<Feature> featureSet = ImmutableSet.copyOf(features);
return new GuiceAsyncModule(principal, featureSet);
}

private static final class GuiceAsyncModule extends AbstractModule {

private final GuiceAsync principal;
private final Set<Feature> features;
private final Set<Feature> enabledFeatures;

public GuiceAsyncModule(GuiceAsync principal, Set<Feature> features) {
checkArgument(!features.isEmpty(), "Set of features must not be empty");
this.principal = principal;
this.features = features;
}

private boolean hasFeature(Feature feature) {
return features.contains(feature);
this.enabledFeatures = features;
}

@Override
protected void configure() {
if (hasFeature(Feature.ASYNC)) {
install(new AsyncModule(principal));
}
if (hasFeature(Feature.SCHEDULE)) {
install(new ScheduleModule(principal));
}
enabledFeatures.forEach(feature -> feature.installModuleTo(binder(), principal));
bind(GuiceAsyncService.class).to(GuiceAsyncServiceImpl.class).in(Singleton.class);
}

@Provides
@Singleton
@DefaultBinding
Set<Feature> provideFeatures() {
return features;
return enabledFeatures;
}

@Provides
Expand Down
Loading

0 comments on commit bbf144f

Please sign in to comment.