Skip to content

Commit 4dbaaac

Browse files
authored
Fix #915 by adding ExecutorServiceProvider to enable developers to use their own ExecutorService (#916)
* Fix #915 by adding ExecutorServiceProvider to enable developers to use their own ExecutorService * Fix broken tests * Improve the metrics database code * Fix tests
1 parent 6fcff1c commit 4dbaaac

File tree

35 files changed

+557
-58
lines changed

35 files changed

+557
-58
lines changed

bolt-servlet/src/test/java/test_with_remote_apis/sample_json_generation/JsonDataRecordingListener.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package test_with_remote_apis.sample_json_generation;
22

33
import com.slack.api.util.http.listener.HttpResponseListener;
4-
import com.slack.api.util.thread.ExecutorServiceFactory;
4+
import com.slack.api.util.thread.DaemonThreadExecutorServiceProvider;
55
import lombok.extern.slf4j.Slf4j;
66

77
import java.io.IOException;
@@ -15,7 +15,8 @@ public class JsonDataRecordingListener extends HttpResponseListener {
1515

1616
private final CopyOnWriteArrayList<String> remaining = new CopyOnWriteArrayList<>();
1717
private final String threadGroupName = "slack-unit-test-JsonDataRecordingListener";
18-
private final ExecutorService executorService = ExecutorServiceFactory.createDaemonThreadPoolExecutor(threadGroupName, 5);
18+
private final ExecutorService executorService = DaemonThreadExecutorServiceProvider.getInstance()
19+
.createThreadPoolExecutor(threadGroupName, 5);
1920

2021
public boolean isAllDone() {
2122
if (remaining.size() > 0) {

bolt-socket-mode/src/test/java/util/socket_mode/MockSocketMode.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package util.socket_mode;
22

3-
import com.slack.api.util.thread.ExecutorServiceFactory;
3+
import com.slack.api.util.thread.DaemonThreadExecutorServiceProvider;
44
import lombok.extern.slf4j.Slf4j;
55
import org.eclipse.jetty.websocket.api.Session;
66
import org.eclipse.jetty.websocket.api.StatusCode;
@@ -22,7 +22,7 @@ public class MockSocketMode extends WebSocketAdapter {
2222
private CountDownLatch closureLatch = new CountDownLatch(1);
2323

2424
private CopyOnWriteArrayList<Session> activeSessions = new CopyOnWriteArrayList<>();
25-
private ScheduledExecutorService service = ExecutorServiceFactory.createDaemonThreadScheduledExecutor(
25+
private ScheduledExecutorService service = DaemonThreadExecutorServiceProvider.getInstance().createThreadScheduledExecutor(
2626
MockSocketMode.class.getCanonicalName());
2727

2828
public MockSocketMode() {

bolt/src/main/java/com/slack/api/bolt/App.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.google.gson.Gson;
44
import com.slack.api.Slack;
5+
import com.slack.api.SlackConfig;
56
import com.slack.api.app_backend.SlackSignature;
67
import com.slack.api.app_backend.events.EventHandler;
78
import com.slack.api.app_backend.events.EventsDispatcher;
@@ -32,7 +33,6 @@
3233
import com.slack.api.model.event.MessageEvent;
3334
import com.slack.api.model.event.TokensRevokedEvent;
3435
import com.slack.api.util.json.GsonFactory;
35-
import com.slack.api.util.thread.ExecutorServiceFactory;
3636
import lombok.AllArgsConstructor;
3737
import lombok.Builder;
3838
import lombok.extern.slf4j.Slf4j;
@@ -440,15 +440,29 @@ public App(AppConfig appConfig) {
440440
}
441441

442442
public App(AppConfig appConfig, List<Middleware> middlewareList) {
443-
this(appConfig, appConfig.getSlack() != null ? appConfig.getSlack() : Slack.getInstance(), middlewareList);
443+
this(appConfig, appConfig.getSlack() != null
444+
? appConfig.getSlack()
445+
// Intentionally instantiating a new SlackConfig instance
446+
// to avoid using the immutable singleton one
447+
: Slack.getInstance(new SlackConfig()),
448+
middlewareList
449+
);
444450
this.status = Status.Stopped;
445451
}
446452

447453
public App(AppConfig appConfig, Slack slack, List<Middleware> middlewareList) {
448454
this.appConfig = appConfig;
449-
this.executorService = ExecutorServiceFactory.createDaemonThreadPoolExecutor(
455+
this.appConfig.setSlack(slack);
456+
SlackConfig clientConfig = this.appConfig.getSlack().getConfig();
457+
if (!clientConfig.getExecutorServiceProvider().equals(this.appConfig.getExecutorServiceProvider())) {
458+
clientConfig.setExecutorServiceProvider(this.appConfig.getExecutorServiceProvider());
459+
clientConfig.synchronizeExecutorServiceProviders();
460+
}
461+
462+
this.executorService = this.appConfig.getExecutorServiceProvider().createThreadPoolExecutor(
450463
"bolt-app-threads",
451-
this.appConfig.getThreadPoolSize());
464+
this.appConfig.getThreadPoolSize()
465+
);
452466
this.middlewareList = middlewareList;
453467

454468
this.oAuthStateService = new ClientOnlyOAuthStateService();

bolt/src/main/java/com/slack/api/bolt/AppConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import com.slack.api.bolt.service.builtin.oauth.view.default_impl.OAuthDefaultRedirectUriPageRenderer;
1212
import com.slack.api.token_rotation.TokenRotator;
1313
import com.slack.api.util.http.SlackHttpClient;
14+
import com.slack.api.util.thread.DaemonThreadExecutorServiceProvider;
15+
import com.slack.api.util.thread.ExecutorServiceProvider;
1416
import lombok.AllArgsConstructor;
1517
import lombok.Builder;
1618
import lombok.Data;
@@ -67,8 +69,12 @@ private EnvVariableName() {
6769
public static final String SLACK_APP_OAUTH_COMPLETION_URL = "SLACK_APP_OAUTH_COMPLETION_URL";
6870
}
6971

72+
// We don't use SlackConfig.DEFAULT here to enable developers to customize the properties later on
7073
@Builder.Default
71-
private transient Slack slack = Slack.getInstance(SlackConfig.DEFAULT, buildSlackHttpClient());
74+
private transient Slack slack = Slack.getInstance(new SlackConfig(), buildSlackHttpClient());
75+
76+
@Builder.Default
77+
private transient ExecutorServiceProvider executorServiceProvider = DaemonThreadExecutorServiceProvider.getInstance();
7278

7379
private static SlackHttpClient buildSlackHttpClient() {
7480
Map<String, String> userAgentCustomInfo = new HashMap<>();

bolt/src/main/java/com/slack/api/bolt/middleware/builtin/MultiTeamsAuthorization.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public MultiTeamsAuthorization(AppConfig config, InstallationService installatio
9898
private ScheduledExecutorService buildTokenToAuthTestCacheCleaner(Runnable task) {
9999
String threadGroupName = MultiTeamsAuthorization.class.getSimpleName();
100100
ScheduledExecutorService tokenToAuthTestCacheCleaner =
101-
ExecutorServiceFactory.createDaemonThreadScheduledExecutor(threadGroupName);
101+
this.config.getExecutorServiceProvider().createThreadScheduledExecutor(threadGroupName);
102102
tokenToAuthTestCacheCleaner.scheduleAtFixedRate(task, 120_000, 30_000, TimeUnit.MILLISECONDS);
103103
log.debug("The tokenToAuthTestCacheCleaner (daemon thread) started");
104104
return tokenToAuthTestCacheCleaner;

bolt/src/main/java/com/slack/api/bolt/middleware/builtin/WorkflowStep.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import com.slack.api.bolt.request.builtin.WorkflowStepExecuteRequest;
1111
import com.slack.api.bolt.request.builtin.WorkflowStepSaveRequest;
1212
import com.slack.api.bolt.response.Response;
13-
import com.slack.api.util.thread.ExecutorServiceFactory;
13+
import com.slack.api.util.thread.DaemonThreadExecutorServiceProvider;
1414
import lombok.*;
1515
import lombok.extern.slf4j.Slf4j;
1616

@@ -30,8 +30,13 @@ public class WorkflowStep implements Middleware, AutoCloseable {
3030
private WorkflowStepEditHandler edit;
3131
private WorkflowStepSaveHandler save;
3232
private WorkflowStepExecuteHandler execute;
33+
3334
@Builder.Default
3435
private boolean executeAutoAcknowledgement = true;
36+
37+
// If a developer would like to use their own ExecutorService here,
38+
// the recommended way would be to pass the one using the builder method:
39+
// `WorkflowStep.builder().executorService(executorService).build()`
3540
@Builder.Default
3641
private ExecutorService executorService = buildDefaultExecutorService();
3742

@@ -94,7 +99,7 @@ public Response apply(Request req, Response resp, MiddlewareChain chain) throws
9499
// Auto acknowledgement
95100
return new Response();
96101
} else {
97-
// In the execute handler, workflows.stepCompleted/stepFailed
102+
// In the `execute` handler, workflows.stepCompleted/stepFailed
98103
// needs to be called asynchronously
99104
return execute.apply(request, request.getContext());
100105
}
@@ -125,7 +130,9 @@ && getCallbackIdPattern().matcher(requestCallbackId).matches())
125130
protected static ExecutorService buildDefaultExecutorService() {
126131
String threadGroupName = WorkflowStep.class.getSimpleName();
127132
int poolSize = 3;
128-
ExecutorService service = ExecutorServiceFactory.createDaemonThreadPoolExecutor(
133+
// If you want to use own ExecutorService, pass it using the builder method instead:
134+
// `WorkflowStep.builder().executorService(executorService).build()`
135+
ExecutorService service = DaemonThreadExecutorServiceProvider.getInstance().createThreadPoolExecutor(
129136
threadGroupName,
130137
poolSize
131138
);

bolt/src/test/java/test_locally/AppTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
import com.slack.api.bolt.service.builtin.oauth.OAuthV2SuccessHandler;
1616
import com.slack.api.bolt.service.builtin.oauth.default_impl.OAuthDefaultExceptionHandler;
1717
import com.slack.api.bolt.service.builtin.oauth.default_impl.OAuthDefaultStateErrorHandler;
18+
import com.slack.api.util.thread.DaemonThreadExecutorServiceFactory;
19+
import com.slack.api.util.thread.ExecutorServiceProvider;
1820
import lombok.extern.slf4j.Slf4j;
1921
import org.junit.After;
2022
import org.junit.Before;
2123
import org.junit.Test;
2224
import util.AuthTestMockServer;
2325

26+
import java.util.concurrent.ExecutorService;
27+
import java.util.concurrent.ScheduledExecutorService;
2428
import java.util.concurrent.atomic.AtomicBoolean;
2529

2630
import static org.hamcrest.CoreMatchers.is;
@@ -358,4 +362,29 @@ public void clientConfig() {
358362
is("http://localhost:8080/new"));
359363
}
360364

365+
@Test
366+
public void customExecutorService() {
367+
final AtomicBoolean called = new AtomicBoolean(false);
368+
ExecutorServiceProvider executorServiceProvider = new ExecutorServiceProvider() {
369+
@Override
370+
public ExecutorService createThreadPoolExecutor(String threadGroupName, int poolSize) {
371+
called.set(true);
372+
return DaemonThreadExecutorServiceFactory.createDaemonThreadPoolExecutor(
373+
threadGroupName, poolSize);
374+
}
375+
376+
@Override
377+
public ScheduledExecutorService createThreadScheduledExecutor(String threadGroupName) {
378+
return DaemonThreadExecutorServiceFactory.createDaemonThreadScheduledExecutor(threadGroupName);
379+
}
380+
};
381+
App app = new App(AppConfig.builder()
382+
.signingSecret("secret")
383+
.singleTeamBotToken("token")
384+
.executorServiceProvider(executorServiceProvider)
385+
.build());
386+
assertThat(app.config().getExecutorServiceProvider(), is(executorServiceProvider));
387+
assertThat(called.get(), is(true));
388+
}
389+
361390
}

bolt/src/test/java/test_locally/app/WorkflowStepTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.slack.api.bolt.request.builtin.WorkflowStepEditRequest;
1111
import com.slack.api.bolt.request.builtin.WorkflowStepSaveRequest;
1212
import com.slack.api.bolt.response.Response;
13+
import com.slack.api.util.thread.DaemonThreadExecutorServiceProvider;
1314
import lombok.extern.slf4j.Slf4j;
1415
import org.junit.After;
1516
import org.junit.Before;
@@ -18,7 +19,11 @@
1819

1920
import java.net.URLEncoder;
2021
import java.util.*;
22+
import java.util.concurrent.ExecutorService;
2123

24+
import static org.hamcrest.CoreMatchers.is;
25+
import static org.hamcrest.CoreMatchers.notNullValue;
26+
import static org.hamcrest.MatcherAssert.assertThat;
2227
import static org.junit.Assert.assertEquals;
2328

2429
@Slf4j
@@ -317,4 +322,13 @@ void setRequestHeaders(String requestBody, Map<String, List<String>> rawHeaders,
317322
rawHeaders.put(SlackSignature.HeaderNames.X_SLACK_SIGNATURE, Arrays.asList(generator.generate(timestamp, requestBody)));
318323
}
319324

325+
@Test
326+
public void buildWithCustomExecutorService() {
327+
ExecutorService executorService = DaemonThreadExecutorServiceProvider.getInstance()
328+
.createThreadPoolExecutor("foo", 3);
329+
WorkflowStep step = WorkflowStep.builder().executorService(executorService).build();
330+
assertThat(step, is(notNullValue()));
331+
assertThat(step.getExecutorService(), is(executorService));
332+
}
333+
320334
}

slack-api-client/src/main/java/com/slack/api/Slack.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ private Slack(SlackConfig config) {
7777

7878
private Slack(SlackConfig config, SlackHttpClient httpClient) {
7979
this.config = config;
80+
if (!this.config.equals(SlackConfig.DEFAULT)) {
81+
this.config.synchronizeExecutorServiceProviders();
82+
}
8083
this.httpClient = httpClient;
8184
this.httpClient.setConfig(this.config);
8285
}

slack-api-client/src/main/java/com/slack/api/SlackConfig.java

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import com.slack.api.util.http.listener.DetailedLoggingListener;
1212
import com.slack.api.util.http.listener.HttpResponseListener;
1313
import com.slack.api.util.http.listener.ResponsePrettyPrintingListener;
14+
import com.slack.api.util.thread.DaemonThreadExecutorServiceProvider;
15+
import com.slack.api.util.thread.ExecutorServiceProvider;
16+
import lombok.Builder;
1417
import lombok.Data;
1518

1619
import java.util.ArrayList;
@@ -121,6 +124,11 @@ public void setHttpClientWriteTimeoutMillis(Integer httpClientWriteTimeoutMillis
121124
public void setHttpClientReadTimeoutMillis(Integer httpClientReadTimeoutMillis) {
122125
throwException();
123126
}
127+
128+
@Override
129+
public void setExecutorServiceProvider(ExecutorServiceProvider executorServiceProvider) {
130+
throwException();
131+
}
124132
};
125133

126134
public SlackConfig() {
@@ -213,9 +221,30 @@ private static String initProxyUrl() {
213221

214222
private String legacyStatusEndpointUrlPrefix = LegacyStatusClient.ENDPOINT_URL_PREFIX;
215223

216-
private MethodsConfig methodsConfig = MethodsConfig.DEFAULT_SINGLETON;
224+
@Builder.Default
225+
private ExecutorServiceProvider executorServiceProvider = DaemonThreadExecutorServiceProvider.getInstance();
226+
227+
private MethodsConfig methodsConfig = new MethodsConfig();
217228

218-
private AuditConfig auditConfig = AuditConfig.DEFAULT_SINGLETON;
229+
private AuditConfig auditConfig = new AuditConfig();
219230

220-
private SCIMConfig sCIMConfig = SCIMConfig.DEFAULT_SINGLETON;
231+
private SCIMConfig sCIMConfig = new SCIMConfig();
232+
233+
public void synchronizeExecutorServiceProviders() {
234+
if (!methodsConfig.equals(MethodsConfig.DEFAULT_SINGLETON)
235+
&& !methodsConfig.getExecutorServiceProvider().equals(executorServiceProvider)) {
236+
methodsConfig.setExecutorServiceProvider(executorServiceProvider);
237+
methodsConfig.getMetricsDatastore().setExecutorServiceProvider(executorServiceProvider);
238+
}
239+
if (!auditConfig.equals(AuditConfig.DEFAULT_SINGLETON)
240+
&& !auditConfig.getExecutorServiceProvider().equals(executorServiceProvider)) {
241+
auditConfig.setExecutorServiceProvider(executorServiceProvider);
242+
auditConfig.getMetricsDatastore().setExecutorServiceProvider(executorServiceProvider);
243+
}
244+
if (!sCIMConfig.equals(SCIMConfig.DEFAULT_SINGLETON)
245+
&& !sCIMConfig.getExecutorServiceProvider().equals(executorServiceProvider)) {
246+
sCIMConfig.setExecutorServiceProvider(executorServiceProvider);
247+
sCIMConfig.getMetricsDatastore().setExecutorServiceProvider(executorServiceProvider);
248+
}
249+
}
221250
}

0 commit comments

Comments
 (0)