Skip to content

Commit bbf144f

Browse files
committed
feat: Allow to globally set the scheduling mode to 'manual'
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
1 parent 4ddebae commit bbf144f

18 files changed

+378
-98
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package de.skuzzle.inject.async;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
import static org.junit.Assert.fail;
6+
7+
import java.util.concurrent.CountDownLatch;
8+
9+
import org.junit.Test;
10+
11+
import com.google.inject.AbstractModule;
12+
import com.google.inject.Guice;
13+
import com.google.inject.Inject;
14+
15+
import de.skuzzle.inject.async.guice.GuiceAsync;
16+
import de.skuzzle.inject.async.guice.ScheduleFeature;
17+
import de.skuzzle.inject.async.schedule.ScheduleProperties;
18+
import de.skuzzle.inject.async.schedule.SchedulingService;
19+
import de.skuzzle.inject.async.schedule.annotation.Scheduled;
20+
import de.skuzzle.inject.async.schedule.annotation.SimpleTrigger;
21+
22+
public class ManuallyScheduledIT {
23+
24+
private volatile CountDownLatch manualLatch = new CountDownLatch(2);
25+
private volatile int counterManual;
26+
27+
@Inject
28+
private SchedulingService schedulingService;
29+
30+
@Scheduled
31+
@SimpleTrigger(100)
32+
public void testManual() {
33+
++counterManual;
34+
manualLatch.countDown();
35+
}
36+
37+
@Test
38+
public void testManuallyStart() throws Exception {
39+
Guice.createInjector(new AbstractModule() {
40+
41+
@Override
42+
protected void configure() {
43+
final ScheduleProperties disableAutoScheduling = ScheduleProperties.defaultProperties()
44+
.disableAutoScheduling();
45+
46+
GuiceAsync.enableFeaturesFor(binder(),
47+
ScheduleFeature.withProperties(disableAutoScheduling));
48+
}
49+
}).injectMembers(this);
50+
51+
schedulingService.startManualScheduling();
52+
manualLatch.await();
53+
assertTrue(counterManual > 0);
54+
}
55+
56+
@Test
57+
public void testDoNotManuallyStart() throws Exception {
58+
Guice.createInjector(new AbstractModule() {
59+
60+
@Override
61+
protected void configure() {
62+
final ScheduleProperties disableAutoScheduling = ScheduleProperties.defaultProperties()
63+
.disableAutoScheduling();
64+
65+
GuiceAsync.enableFeaturesFor(binder(),
66+
ScheduleFeature.withProperties(disableAutoScheduling));
67+
}
68+
}).injectMembers(this);
69+
70+
// this thing waits forever because scheduler is never started
71+
final Thread waitForTimeout = new Thread(() -> {
72+
try {
73+
manualLatch.await();
74+
// XXX: this would not fail the test because it occurs in wrong thread
75+
fail();
76+
} catch (final InterruptedException ignore) {
77+
}
78+
});
79+
80+
waitForTimeout.start();
81+
Thread.sleep(1000);
82+
waitForTimeout.interrupt();
83+
assertEquals(0, counterManual);
84+
}
85+
86+
}

src/it/java/de/skuzzle/inject/async/ScheduledIT.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.inject.Provides;
2121
import com.google.inject.name.Names;
2222

23+
import de.skuzzle.inject.async.guice.DefaultFeatures;
2324
import de.skuzzle.inject.async.guice.GuiceAsync;
2425
import de.skuzzle.inject.async.schedule.ExceptionHandler;
2526
import de.skuzzle.inject.async.schedule.ExecutionContext;
@@ -158,7 +159,7 @@ public void setup() {
158159

159160
@Override
160161
protected void configure() {
161-
GuiceAsync.enableFor(binder());
162+
GuiceAsync.enableFeaturesFor(binder(), DefaultFeatures.SCHEDULE);
162163

163164
bind(TestExceptionHandler.class).asEagerSingleton();
164165
bind(TypeWithScheduledMethods.class).asEagerSingleton();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package de.skuzzle.inject.async.guice;
2+
3+
import java.util.concurrent.ExecutorService;
4+
import java.util.concurrent.ScheduledExecutorService;
5+
import java.util.concurrent.TimeUnit;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import com.google.inject.Binder;
11+
import com.google.inject.Injector;
12+
13+
import de.skuzzle.inject.async.methods.AsyncModule;
14+
import de.skuzzle.inject.async.methods.annotation.Async;
15+
import de.skuzzle.inject.async.schedule.ScheduleModule;
16+
import de.skuzzle.inject.async.schedule.ScheduleProperties;
17+
import de.skuzzle.inject.async.schedule.annotation.Scheduled;
18+
19+
/**
20+
* Supported features that can be passed when initializing the async/scheduling subsystem.
21+
* Each feature is self contained and has no dependence to other features being present.
22+
*
23+
* @author Simon Taddiken
24+
* @since 2.0.0
25+
*/
26+
public enum DefaultFeatures implements Feature {
27+
/** This feature enables handling of the {@link Async} annotation. */
28+
ASYNC {
29+
@Override
30+
public void installModuleTo(Binder binder, GuiceAsync principal) {
31+
binder.install(new AsyncModule(principal));
32+
}
33+
34+
@Override
35+
public boolean cleanupExecutor(Injector injector, long timeout, TimeUnit timeUnit) {
36+
final ExecutorService executor = injector.getInstance(Keys.DEFAULT_EXECUTOR_KEY);
37+
if (!Shutdown.executor(executor, timeout, timeUnit)) {
38+
LOG.warn("There are still active tasks lingering in default executor after shutdown. Wait time: {} {}",
39+
timeout, timeUnit);
40+
return false;
41+
}
42+
return true;
43+
}
44+
},
45+
/**
46+
* This feature enables handling of the {@link Scheduled} annotation. You may also use
47+
* an instance of {@link ScheduleFeature} instead of this instance (but better do not
48+
* provide both).
49+
*/
50+
SCHEDULE {
51+
@Override
52+
public void installModuleTo(Binder binder, GuiceAsync principal) {
53+
binder.install(new ScheduleModule(
54+
principal,
55+
ScheduleProperties.defaultProperties()));
56+
}
57+
58+
@Override
59+
public boolean cleanupExecutor(Injector injector, long timeout, TimeUnit timeUnit) {
60+
final ScheduledExecutorService scheduler = injector.getInstance(Keys.DEFAULT_SCHEDULER_KEY);
61+
if (!Shutdown.executor(scheduler, timeout, timeUnit)) {
62+
LOG.warn("There are still active tasks lingering in default scheduler after shutdown. Wait time: {} {}",
63+
timeout, timeUnit);
64+
return false;
65+
}
66+
return true;
67+
}
68+
};
69+
70+
private static final Logger LOG = LoggerFactory.getLogger(DefaultFeatures.class);
71+
72+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
package de.skuzzle.inject.async.guice;
22

3-
import de.skuzzle.inject.async.methods.annotation.Async;
4-
import de.skuzzle.inject.async.schedule.annotation.Scheduled;
3+
import java.util.concurrent.TimeUnit;
4+
5+
import com.google.inject.Binder;
6+
import com.google.inject.Injector;
57

68
/**
7-
* Supported features that can be passed when initializing the async/scheduling subsystem.
8-
* Each feature is self contained and has no dependence to other features being present.
9+
* A stand alone feature that can be passed to {@link GuiceAsync}. Use
10+
* {@link DefaultFeatures} or the dedicated {@link ScheduleFeature} which allows
11+
* customization.
912
*
1013
* @author Simon Taddiken
14+
* @since 2.0.0
1115
*/
12-
public enum Feature {
13-
/** This feature enables handling of the {@link Async} annotation. */
14-
ASYNC,
15-
/** This feature enables handling of the {@link Scheduled} annotation. */
16-
SCHEDULE
16+
public interface Feature {
17+
18+
/**
19+
* Installs the modules relevant to this feature to the given {@link Binder}.
20+
*
21+
* @param binder The binder to install any required modules to.
22+
* @param principal The {@link GuiceAsync} instance guarding the modules from
23+
* unintended instantiation.
24+
*/
25+
void installModuleTo(Binder binder, GuiceAsync principal);
26+
27+
/**
28+
* Makes sure to shutdown this feature's executor when
29+
* {@link GuiceAsyncService#shutdown(long, TimeUnit)} is being called.
30+
*
31+
* @param injector The injector.
32+
* @param timeout The time to wait for an orderly shutdown.
33+
* @param timeUnit Unit for the timeout parameter.
34+
* @return Whether the executor orderly shutdown within given time.
35+
*/
36+
boolean cleanupExecutor(Injector injector, long timeout, TimeUnit timeUnit);
1737
}

src/main/java/de/skuzzle/inject/async/guice/GuiceAsync.java

+16-25
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,19 @@
22

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

5-
import java.util.Arrays;
6-
import java.util.EnumSet;
75
import java.util.Set;
86
import java.util.concurrent.ThreadFactory;
97

108
import javax.inject.Singleton;
119

10+
import com.google.common.collect.ImmutableSet;
1211
import com.google.common.util.concurrent.ThreadFactoryBuilder;
1312
import com.google.inject.AbstractModule;
1413
import com.google.inject.Binder;
1514
import com.google.inject.Module;
1615
import com.google.inject.Provides;
1716

18-
import de.skuzzle.inject.async.methods.AsyncModule;
1917
import de.skuzzle.inject.async.methods.annotation.Async;
20-
import de.skuzzle.inject.async.schedule.ScheduleModule;
2118
import de.skuzzle.inject.async.schedule.annotation.Scheduled;
2219

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

6461
/**
65-
* Enable support for the given {@link Feature features}. Allows to separately enable
66-
* support for async or scheduled.
62+
* Enable support for the given {@link DefaultFeatures features}. Allows to separately
63+
* enable support for async or scheduled.
6764
*
6865
* @param binder The binder to register with.
6966
* @param features The features to enable.
67+
* @since 2.0.0
68+
* @see DefaultFeatures
7069
*/
7170
public static void enableFeaturesFor(Binder binder, Feature... features) {
7271
checkArgument(binder != null, "binder must not be null");
@@ -81,52 +80,44 @@ public static void enableFeaturesFor(Binder binder, Feature... features) {
8180
* @since 0.2.0
8281
*/
8382
public static Module createModule() {
84-
return createModuleWithFeatures(Feature.ASYNC, Feature.SCHEDULE);
83+
return createModuleWithFeatures(DefaultFeatures.ASYNC, DefaultFeatures.SCHEDULE);
8584
}
8685

8786
/**
88-
* Creates a module taht can be used to enable the given features.
87+
* Creates a module that can be used to enable the given features.
8988
*
9089
* @param features The features to enable.
9190
* @return The module.
91+
* @since 2.0.0
9292
*/
9393
public static Module createModuleWithFeatures(Feature... features) {
9494
final GuiceAsync principal = new GuiceAsync();
95-
final EnumSet<Feature> featureSet = EnumSet.copyOf(Arrays.asList(features));
95+
final Set<Feature> featureSet = ImmutableSet.copyOf(features);
9696
return new GuiceAsyncModule(principal, featureSet);
9797
}
9898

9999
private static final class GuiceAsyncModule extends AbstractModule {
100100

101101
private final GuiceAsync principal;
102-
private final Set<Feature> features;
102+
private final Set<Feature> enabledFeatures;
103103

104104
public GuiceAsyncModule(GuiceAsync principal, Set<Feature> features) {
105105
checkArgument(!features.isEmpty(), "Set of features must not be empty");
106106
this.principal = principal;
107-
this.features = features;
108-
}
109-
110-
private boolean hasFeature(Feature feature) {
111-
return features.contains(feature);
107+
this.enabledFeatures = features;
112108
}
113109

114110
@Override
115111
protected void configure() {
116-
if (hasFeature(Feature.ASYNC)) {
117-
install(new AsyncModule(principal));
118-
}
119-
if (hasFeature(Feature.SCHEDULE)) {
120-
install(new ScheduleModule(principal));
121-
}
112+
enabledFeatures.forEach(feature -> feature.installModuleTo(binder(), principal));
122113
bind(GuiceAsyncService.class).to(GuiceAsyncServiceImpl.class).in(Singleton.class);
123114
}
124115

125116
@Provides
126117
@Singleton
127118
@DefaultBinding
128119
Set<Feature> provideFeatures() {
129-
return features;
120+
return enabledFeatures;
130121
}
131122

132123
@Provides

0 commit comments

Comments
 (0)