Skip to content

Commit 3de29fe

Browse files
author
Mike Davis
authored
Incorporate batch processing into OptimizelyFactory. (#322)
* Make batch processor default implementation within the OptimizelyFactory. * Lower the treshhold for default batch processing. * Close event dispatcher when closing event processor. * Change default interval to 30 seconds.
1 parent 775cffc commit 3de29fe

File tree

8 files changed

+223
-92
lines changed

8 files changed

+223
-92
lines changed

core-api/src/main/java/com/optimizely/ab/Optimizely.java

+2-13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import java.util.List;
4242
import java.util.Map;
4343

44+
import static com.optimizely.ab.internal.SafetyUtils.tryClose;
45+
4446
/**
4547
* Top-level container class for Optimizely functionality.
4648
* Thread-safe, so can be created as a singleton and safely passed around.
@@ -116,20 +118,7 @@ public boolean isValid() {
116118
return getProjectConfig() != null;
117119
}
118120

119-
/**
120-
* Helper method which checks if Object is an instance of AutoCloseable and calls close() on it.
121-
*/
122-
private void tryClose(Object obj) {
123-
if (!(obj instanceof AutoCloseable)) {
124-
return;
125-
}
126121

127-
try {
128-
((AutoCloseable) obj).close();
129-
} catch (Exception e) {
130-
logger.warn("Unexpected exception on trying to close {}.", obj);
131-
}
132-
}
133122

134123
/**
135124
* Checks if eventHandler {@link EventHandler} and projectConfigManager {@link ProjectConfigManager}

core-api/src/main/java/com/optimizely/ab/event/BatchEventProcessor.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.LinkedList;
2828
import java.util.concurrent.*;
2929

30+
import static com.optimizely.ab.internal.SafetyUtils.tryClose;
31+
3032
/**
3133
* BatchEventProcessor is a batched implementation of the {@link EventProcessor}
3234
*
@@ -44,9 +46,9 @@ public class BatchEventProcessor implements EventProcessor, AutoCloseable {
4446
public static final String CONFIG_BATCH_INTERVAL = "event.processor.batch.interval";
4547
public static final String CONFIG_CLOSE_TIMEOUT = "event.processor.close.timeout";
4648

47-
public static final int DEFAULT_QUEUE_CAPACITY = 1000;
48-
public static final int DEFAULT_BATCH_SIZE = 50;
49-
public static final long DEFAULT_BATCH_INTERVAL = TimeUnit.MINUTES.toMillis(1);
49+
public static final int DEFAULT_QUEUE_CAPACITY = 1000;
50+
public static final int DEFAULT_BATCH_SIZE = 10;
51+
public static final long DEFAULT_BATCH_INTERVAL = TimeUnit.SECONDS.toMillis(30);
5052
public static final long DEFAULT_TIMEOUT_INTERVAL = TimeUnit.SECONDS.toMillis(5);
5153

5254
private static final Object SHUTDOWN_SIGNAL = new Object();
@@ -108,6 +110,7 @@ public void close() throws Exception {
108110
logger.error("Timeout exceeded attempting to close for {} ms", timeoutMillis);
109111
} finally {
110112
isStarted = false;
113+
tryClose(eventHandler);
111114
}
112115
}
113116

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
*
3+
* Copyright 2019, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.internal;
18+
19+
import org.slf4j.Logger;
20+
import org.slf4j.LoggerFactory;
21+
22+
/**
23+
* Collection of utils used to prevent the Optimizely SDK from throwing or crashing the hosting application.
24+
*/
25+
public class SafetyUtils {
26+
27+
private static final Logger logger = LoggerFactory.getLogger(SafetyUtils.class);
28+
29+
/**
30+
* Helper method which checks if Object is an instance of AutoCloseable and calls close() on it.
31+
*/
32+
public static void tryClose(Object obj) {
33+
if (!(obj instanceof AutoCloseable)) {
34+
return;
35+
}
36+
37+
try {
38+
((AutoCloseable) obj).close();
39+
} catch (Exception e) {
40+
logger.warn("Unexpected exception on trying to close {}.", obj);
41+
}
42+
}
43+
}

core-api/src/test/java/com/optimizely/ab/OptimizelyTest.java

+12-66
Original file line numberDiff line numberDiff line change
@@ -168,87 +168,33 @@ public void setUp() throws Exception {
168168

169169
@Test
170170
public void testClose() throws Exception {
171-
// Check for AutoCloseable
172-
EventHandler mockAutoCloseableEventHandler = mock(
171+
EventHandler mockEventHandler = mock(
173172
EventHandler.class,
174173
withSettings().extraInterfaces(AutoCloseable.class)
175174
);
176-
ProjectConfigManager mockAutoCloseableProjectConfigManager = mock(
177-
ProjectConfigManager.class,
178-
withSettings().extraInterfaces(AutoCloseable.class)
179-
);
180-
181-
Optimizely optimizely = Optimizely.builder()
182-
.withEventHandler(mockAutoCloseableEventHandler)
183-
.withConfigManager(mockAutoCloseableProjectConfigManager)
184-
.build();
185-
186-
optimizely.close();
187175

188-
verify((AutoCloseable) mockAutoCloseableEventHandler).close();
189-
verify((AutoCloseable) mockAutoCloseableProjectConfigManager).close();
190-
191-
// Check for Closeable
192-
EventHandler mockCloseableEventHandler = mock(
193-
EventHandler.class,
194-
withSettings().extraInterfaces(Closeable.class)
195-
);
196-
ProjectConfigManager mockCloseableProjectConfigManager = mock(
176+
ProjectConfigManager mockProjectConfigManager = mock(
197177
ProjectConfigManager.class,
198-
withSettings().extraInterfaces(Closeable.class)
199-
);
200-
201-
optimizely = Optimizely.builder()
202-
.withEventHandler(mockCloseableEventHandler)
203-
.withConfigManager(mockCloseableProjectConfigManager)
204-
.build();
205-
206-
optimizely.close();
207-
208-
verify((Closeable) mockCloseableEventHandler).close();
209-
verify((Closeable) mockCloseableProjectConfigManager).close();
210-
}
211-
212-
@Test
213-
public void testCloseConfigManagerThrowsException() throws Exception {
214-
EventHandler mockAutoCloseableEventHandler = mock(
215-
EventHandler.class,
216178
withSettings().extraInterfaces(AutoCloseable.class)
217179
);
218-
ProjectConfigManager mockAutoCloseableProjectConfigManager = mock(
219-
ProjectConfigManager.class,
180+
181+
EventProcessor mockEventProcessor = mock(
182+
EventProcessor.class,
220183
withSettings().extraInterfaces(AutoCloseable.class)
221184
);
222185

223-
Optimizely optimizely = optimizelyBuilder
224-
.withEventHandler(mockAutoCloseableEventHandler)
225-
.withConfigManager(mockAutoCloseableProjectConfigManager)
186+
Optimizely optimizely = Optimizely.builder()
187+
.withEventHandler(mockEventHandler)
188+
.withEventProcessor(mockEventProcessor)
189+
.withConfigManager(mockProjectConfigManager)
226190
.build();
227191

228-
doThrow(new IOException()).when((AutoCloseable) mockAutoCloseableProjectConfigManager).close();
229-
logbackVerifier.expectMessage(Level.WARN, "Unexpected exception on trying to close " + mockAutoCloseableProjectConfigManager + ".");
230192
optimizely.close();
231-
}
232-
233-
@Test
234-
public void testCloseEventHandlerThrowsException() throws Exception {
235-
EventHandler mockAutoCloseableEventHandler = mock(
236-
EventHandler.class,
237-
withSettings().extraInterfaces(AutoCloseable.class)
238-
);
239-
ProjectConfigManager mockAutoCloseableProjectConfigManager = mock(
240-
ProjectConfigManager.class,
241-
withSettings().extraInterfaces(AutoCloseable.class)
242-
);
243193

244-
Optimizely optimizely = optimizelyBuilder
245-
.withEventHandler(mockAutoCloseableEventHandler)
246-
.withConfigManager(mockAutoCloseableProjectConfigManager)
247-
.build();
194+
verify((AutoCloseable) mockEventHandler).close();
195+
verify((AutoCloseable) mockProjectConfigManager).close();
196+
verify((AutoCloseable) mockEventProcessor).close();
248197

249-
doThrow(new IOException()).when((AutoCloseable) mockAutoCloseableEventHandler).close();
250-
logbackVerifier.expectMessage(Level.WARN, "Unexpected exception on trying to close " + mockAutoCloseableEventHandler + ".");
251-
optimizely.close();
252198
}
253199

254200
//======== activate tests ========//

core-api/src/test/java/com/optimizely/ab/event/BatchEventProcessorTest.java

+13-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@
3232
import java.util.concurrent.*;
3333

3434
import static org.junit.Assert.*;
35-
import static org.mockito.Mockito.mock;
36-
import static org.mockito.Mockito.when;
35+
import static org.mockito.Mockito.*;
3736

3837
@RunWith(MockitoJUnitRunner.class)
3938
public class BatchEventProcessorTest {
@@ -255,6 +254,18 @@ public void testCloseTimeout() throws Exception {
255254
countDownLatch.countDown();
256255
}
257256

257+
@Test
258+
public void testCloseEventHandler() throws Exception {
259+
EventHandler mockEventHandler = mock(
260+
EventHandler.class,
261+
withSettings().extraInterfaces(AutoCloseable.class)
262+
);
263+
264+
setEventProcessor(mockEventHandler);
265+
eventProcessor.close();
266+
verify((AutoCloseable) mockEventHandler).close();
267+
}
268+
258269
private void setEventProcessor(EventHandler eventHandler) {
259270
eventProcessor = BatchEventProcessor.builder()
260271
.withEventQueue(eventQueue)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
*
3+
* Copyright 2019, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package com.optimizely.ab.internal;
18+
19+
import org.junit.Test;
20+
21+
import java.io.Closeable;
22+
import java.io.IOException;
23+
24+
import static org.junit.Assert.*;
25+
import static org.mockito.Mockito.doThrow;
26+
import static org.mockito.Mockito.mock;
27+
import static org.mockito.Mockito.verify;
28+
29+
public class SafetyUtilsTest {
30+
31+
@Test
32+
public void tryCloseAutoCloseable() throws Exception {
33+
AutoCloseable autocloseable = mock(AutoCloseable.class);
34+
SafetyUtils.tryClose(autocloseable);
35+
36+
verify(autocloseable).close();
37+
}
38+
39+
@Test
40+
public void tryCloseCloseable() throws Exception {
41+
Closeable closeable = mock(Closeable.class);
42+
SafetyUtils.tryClose(closeable);
43+
44+
verify(closeable).close();
45+
}
46+
47+
@Test
48+
public void tryCloseNullDoesNotThrow() throws Exception {
49+
SafetyUtils.tryClose(null);
50+
}
51+
52+
@Test
53+
public void tryCloseExceptionDoesNotThrow() throws Exception {
54+
AutoCloseable autocloseable = mock(AutoCloseable.class);
55+
doThrow(new RuntimeException()).when(autocloseable).close();
56+
SafetyUtils.tryClose(autocloseable);
57+
}
58+
}

core-httpclient-impl/src/main/java/com/optimizely/ab/OptimizelyFactory.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.optimizely.ab.config.HttpProjectConfigManager;
2020
import com.optimizely.ab.config.ProjectConfigManager;
2121
import com.optimizely.ab.event.AsyncEventHandler;
22+
import com.optimizely.ab.event.BatchEventProcessor;
2223
import com.optimizely.ab.event.EventHandler;
2324
import com.optimizely.ab.internal.PropertyUtils;
2425
import com.optimizely.ab.notification.NotificationCenter;
@@ -35,6 +36,8 @@
3536
*
3637
* OptimizelyClients also provides setter methods to override the system properties at runtime.
3738
* <ul>
39+
* <li>{@link OptimizelyFactory#setMaxEventBatchSize}</li>
40+
* <li>{@link OptimizelyFactory#setMaxEventBatchInterval}</li>
3841
* <li>{@link OptimizelyFactory#setEventQueueParams}</li>
3942
* <li>{@link OptimizelyFactory#setBlockingTimeout}</li>
4043
* <li>{@link OptimizelyFactory#setPollingInterval}</li>
@@ -46,7 +49,33 @@ public final class OptimizelyFactory {
4649
private static final Logger logger = LoggerFactory.getLogger(OptimizelyFactory.class);
4750

4851
/**
49-
* Convenience method for setting the required queueing parameters.
52+
* Convenience method for setting the maximum number of events contained within a batch.
53+
* {@link AsyncEventHandler}
54+
*/
55+
public static void setMaxEventBatchSize(int batchSize) {
56+
if (batchSize <= 0) {
57+
logger.warn("Batch size cannot be <= 0. Reverting to default configuration.");
58+
return;
59+
}
60+
61+
PropertyUtils.set(BatchEventProcessor.CONFIG_BATCH_SIZE, Integer.toString(batchSize));
62+
}
63+
64+
/**
65+
* Convenience method for setting the maximum time interval in milliseconds between event dispatches.
66+
* {@link AsyncEventHandler}
67+
*/
68+
public static void setMaxEventBatchInterval(long batchInterval) {
69+
if (batchInterval <= 0) {
70+
logger.warn("Batch interval cannot be <= 0. Reverting to default configuration.");
71+
return;
72+
}
73+
74+
PropertyUtils.set(BatchEventProcessor.CONFIG_BATCH_INTERVAL, Long.toString(batchInterval));
75+
}
76+
77+
/**
78+
* Convenience method for setting the required queueing parameters for event dispatching.
5079
* {@link AsyncEventHandler}
5180
*/
5281
public static void setEventQueueParams(int queueCapacity, int numberWorkers) {
@@ -194,8 +223,13 @@ public static Optimizely newDefaultInstance(ProjectConfigManager configManager,
194223
notificationCenter = new NotificationCenter();
195224
}
196225

197-
return Optimizely.builder()
226+
BatchEventProcessor eventProcessor = BatchEventProcessor.builder()
198227
.withEventHandler(eventHandler)
228+
.withNotificationCenter(notificationCenter)
229+
.build();
230+
231+
return Optimizely.builder()
232+
.withEventProcessor(eventProcessor)
199233
.withConfigManager(configManager)
200234
.withNotificationCenter(notificationCenter)
201235
.build();

0 commit comments

Comments
 (0)