Skip to content

Commit 725d248

Browse files
committed
#207: Made the thread management more flexible by accepting a custom ExecutorService (ThreadPoolExecutor), with a default keepAliveTime of 1ms, allowing core threads to die idly as well
1 parent 17d2da1 commit 725d248

File tree

8 files changed

+176
-63
lines changed

8 files changed

+176
-63
lines changed

modules/core-module/src/main/java/org/simplejavamail/api/mailer/MailerGenericBuilder.java

+39-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import java.util.List;
1313
import java.util.Map;
1414
import java.util.Properties;
15+
import java.util.concurrent.ExecutorService;
16+
import java.util.concurrent.LinkedBlockingQueue;
1517

1618
/**
1719
* Builder superclass which contains API to take care of all generic Mailer properties unrelated to the SMTP server
@@ -57,9 +59,9 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
5759
boolean DEFAULT_JAVAXMAIL_DEBUG = false;
5860

5961
/**
60-
* Sets flag that send or server connection test should run in the background returning immediately.
62+
* Changes the default for sending emails and testing server connections to asynchronous (batch mode).
6163
* <p>
62-
* In case of asynchronous mode, make sure you configure logging to file, as otherwise you won't know if there was an error.
64+
* In case of asynchronous mode, make sure you configure logging to file or inspect the returned {@link AsyncResponse}.
6365
*/
6466
T async();
6567

@@ -164,6 +166,27 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
164166
*/
165167
T withEmailAddressCriteria(@Nonnull EnumSet<EmailAddressCriteria> emailAddressCriteria);
166168

169+
/**
170+
* <strong>For advanced use cases.</strong>
171+
* <p>
172+
* Allows you to fully customize and manage the thread pool, threads and concurrency characteristics when
173+
* sending in batch mode.
174+
* <p>
175+
* By default the {@code NonJvmBlockingThreadPoolExecutor} is used:
176+
* <ul>
177+
* <li>with core and max threads fixed to the given pool size</li>
178+
* <li>with keepAliveTime as specified (if greater than zero, core thread will also time out and die off)</li>
179+
* <li>A {@link LinkedBlockingQueue}</li>
180+
* <li>The {@code NamedThreadFactory}, which creates named non-daemon threads</li>
181+
* </ul>
182+
* <p>
183+
* <strong>Note:</strong> What makes it NonJvm is that the default keepAliveTime is set to the lowest non-zero value (so 1), so that
184+
* any threads will die off as soon as possible, as not to block the JVM from shutting down.
185+
*
186+
* @param executorService A custom executor service (ThreadPoolExecutor), replacing the {@code NonJvmBlockingThreadPoolExecutor}.
187+
*/
188+
T withExecutorService(@Nonnull ExecutorService executorService);
189+
167190
/**
168191
* Sets both core thread pool size and max thread pool size to the given size.
169192
*
@@ -182,7 +205,7 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
182205
* When set to zero, this keepAliveTime is applied only to extra threads, not core threads. This is the classic executor
183206
* behavior, but this blocks the JVM from exiting.
184207
* <p>
185-
* Defaults to {@value #DEFAULT_POOL_KEEP_ALIVE_TIME}.
208+
* Defaults to {@value #DEFAULT_POOL_KEEP_ALIVE_TIME}ms.
186209
*
187210
* @param threadPoolKeepAliveTime Value in milliseconds. See main description for details.
188211
*
@@ -268,12 +291,17 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
268291
*/
269292
T resetEmailAddressCriteria();
270293

294+
/**
295+
* Resets the executor services to be used back to the default.
296+
*
297+
* @see #withExecutorService(ExecutorService)
298+
*/
299+
T resetExecutorService();
300+
271301
/**
272302
* Resets both thread pool max and core size to their defaults.
273303
*
274304
* @see #withThreadPoolSize(Integer)
275-
* @see #resetThreadpoolCoreSize()
276-
* @see #resetThreadpoolMaxSize()
277305
*/
278306
T resetThreadpoolSize();
279307

@@ -373,6 +401,12 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
373401
@Nullable
374402
EnumSet<EmailAddressCriteria> getEmailAddressCriteria();
375403

404+
/**
405+
* @see #withExecutorService(ExecutorService)
406+
*/
407+
@Nullable
408+
ExecutorService getExecutorService();
409+
376410
/**
377411
* @see #withThreadPoolSize(Integer)
378412
*/

modules/core-module/src/main/java/org/simplejavamail/api/mailer/config/OperationalConfig.java

+10
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
import org.simplejavamail.api.mailer.MailerGenericBuilder;
55
import org.simplejavamail.api.mailer.MailerRegularBuilder;
66

7+
import javax.annotation.Nonnull;
78
import java.util.List;
89
import java.util.Properties;
10+
import java.util.concurrent.ExecutorService;
911

1012
/**
1113
* Contains all the configuration that affect how a {@link Mailer} operates. This includes connection settings such as
@@ -47,6 +49,7 @@ public interface OperationalConfig {
4749
/**
4850
* @see MailerRegularBuilder#trustingSSLHosts(String...)
4951
*/
52+
@Nonnull
5053
List<String> getSslHostsToTrust();
5154

5255
/**
@@ -57,5 +60,12 @@ public interface OperationalConfig {
5760
/**
5861
* @see MailerRegularBuilder#withProperties(Properties)
5962
*/
63+
@Nonnull
6064
Properties getProperties();
65+
66+
/**
67+
* @see MailerRegularBuilder#withExecutorService(ExecutorService)
68+
*/
69+
@Nonnull
70+
ExecutorService getExecutorService();
6171
}

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerGenericBuilderImpl.java

+36-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.List;
1616
import java.util.Map;
1717
import java.util.Properties;
18+
import java.util.concurrent.ExecutorService;
1819

1920
import static org.simplejavamail.config.ConfigLoader.Property.PROXY_HOST;
2021
import static org.simplejavamail.config.ConfigLoader.Property.PROXY_PASSWORD;
@@ -80,6 +81,12 @@ public abstract class MailerGenericBuilderImpl<T extends MailerGenericBuilderImp
8081
@Nonnull
8182
private EnumSet<EmailAddressCriteria> emailAddressCriteria;
8283

84+
/**
85+
* @see MailerGenericBuilder#withExecutorService(ExecutorService)
86+
*/
87+
@Nullable
88+
private ExecutorService executorService;
89+
8390
/**
8491
* @see MailerGenericBuilder#withThreadPoolSize(Integer)
8592
*/
@@ -181,7 +188,8 @@ OperationalConfig buildOperationalConfig() {
181188
isTransportModeLoggingOnly(),
182189
isDebugLogging(),
183190
getSslHostsToTrust(),
184-
isTrustAllSSLHost());
191+
isTrustAllSSLHost(),
192+
getExecutorService());
185193
}
186194

187195
/**
@@ -285,6 +293,15 @@ public T withEmailAddressCriteria(@Nonnull final EnumSet<EmailAddressCriteria> e
285293
return (T) this;
286294
}
287295

296+
/**
297+
* @see MailerGenericBuilder#withExecutorService(ExecutorService)
298+
*/
299+
@Override
300+
public T withExecutorService(@Nonnull final ExecutorService executorService) {
301+
this.executorService = executorService;
302+
return (T) this;
303+
}
304+
288305
/**
289306
* @see MailerGenericBuilder#withThreadPoolSize(Integer)
290307
*/
@@ -381,6 +398,15 @@ public T resetEmailAddressCriteria() {
381398
return withEmailAddressCriteria(EmailAddressCriteria.RFC_COMPLIANT);
382399
}
383400

401+
/**
402+
* @see MailerGenericBuilder#resetExecutorService()
403+
*/
404+
@Override
405+
public T resetExecutorService() {
406+
this.executorService = null;
407+
return (T) this;
408+
}
409+
384410
/**
385411
* @see MailerGenericBuilder#resetThreadpoolSize()
386412
*/
@@ -518,6 +544,15 @@ public EnumSet<EmailAddressCriteria> getEmailAddressCriteria() {
518544
return emailAddressCriteria;
519545
}
520546

547+
/**
548+
* @see MailerGenericBuilder#getExecutorService()
549+
*/
550+
@Override
551+
@Nullable
552+
public ExecutorService getExecutorService() {
553+
return executorService;
554+
}
555+
521556
/**
522557
* @see MailerGenericBuilder#getThreadPoolSize()
523558
*/

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/OperationalConfigImpl.java

+27-4
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22

33
import org.simplejavamail.api.mailer.MailerRegularBuilder;
44
import org.simplejavamail.api.mailer.config.OperationalConfig;
5+
import org.simplejavamail.mailer.internal.mailsender.concurrent.NonJvmBlockingThreadPoolExecutor;
56

7+
import javax.annotation.Nonnull;
8+
import javax.annotation.Nullable;
69
import java.util.Collections;
710
import java.util.List;
811
import java.util.Properties;
12+
import java.util.concurrent.ExecutorService;
913

1014
/**
1115
* @see OperationalConfig
1216
*/
17+
// FIXME Lombok
1318
public class OperationalConfigImpl implements OperationalConfig {
1419
/**
1520
* @see MailerRegularBuilder#withSessionTimeout(Integer)
@@ -48,20 +53,27 @@ public class OperationalConfigImpl implements OperationalConfig {
4853
/**
4954
* @see MailerRegularBuilder#trustingSSLHosts(String...)
5055
*/
56+
@Nonnull
5157
private final List<String> sslHostsToTrust;
52-
58+
5359
/**
5460
* @see MailerRegularBuilder#trustingAllHosts(boolean)
5561
*/
5662
private final boolean trustAllSSLHost;
63+
64+
/**
65+
* @see MailerRegularBuilder#withExecutorService(ExecutorService)
66+
*/
67+
@Nonnull
68+
private final ExecutorService executorService;
5769

5870
/**
5971
* @deprecated For internal use only.
6072
*/
6173
@Deprecated
6274
@SuppressWarnings("DeprecatedIsStillUsed")
63-
public OperationalConfigImpl(boolean async, Properties properties, int sessionTimeout, int threadPoolSize, int threadPoolKeepAliveTime, boolean transportModeLoggingOnly, boolean debugLogging,
64-
List<String> sslHostsToTrust, boolean trustAllSSLHost) {
75+
public OperationalConfigImpl(final boolean async, Properties properties, int sessionTimeout, int threadPoolSize, int threadPoolKeepAliveTime, boolean transportModeLoggingOnly,
76+
boolean debugLogging, @Nonnull List<String> sslHostsToTrust, boolean trustAllSSLHost, @Nullable final ExecutorService executorService) {
6577
this.async = async;
6678
this.properties = properties;
6779
this.sessionTimeout = sessionTimeout;
@@ -71,8 +83,11 @@ public OperationalConfigImpl(boolean async, Properties properties, int sessionTi
7183
this.debugLogging = debugLogging;
7284
this.sslHostsToTrust = Collections.unmodifiableList(sslHostsToTrust);
7385
this.trustAllSSLHost = trustAllSSLHost;
86+
this.executorService = executorService != null
87+
? executorService
88+
: new NonJvmBlockingThreadPoolExecutor(getThreadPoolSize(), getThreadPoolKeepAliveTime());
7489
}
75-
90+
7691
/**
7792
* @see OperationalConfig#isAsync()
7893
*/
@@ -124,6 +139,7 @@ public boolean isDebugLogging() {
124139
/**
125140
* @see OperationalConfig#getSslHostsToTrust()
126141
*/
142+
@Nonnull
127143
@Override
128144
public List<String> getSslHostsToTrust() {
129145
return sslHostsToTrust;
@@ -140,8 +156,15 @@ public boolean isTrustAllSSLHost() {
140156
/**
141157
* @see OperationalConfig#getProperties()
142158
*/
159+
@Nonnull
143160
@Override
144161
public Properties getProperties() {
145162
return properties;
146163
}
164+
165+
@Nonnull
166+
@Override
167+
public ExecutorService getExecutorService() {
168+
return executorService;
169+
}
147170
}

modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/mailsender/MailSenderImpl.java

+8-17
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.io.UnsupportedEncodingException;
2525
import java.util.List;
2626
import java.util.Properties;
27-
import java.util.concurrent.ExecutorService;
2827
import java.util.concurrent.Phaser;
2928

3029
import static java.lang.String.format;
@@ -49,6 +48,7 @@ public class MailSenderImpl implements MailSender {
4948
/**
5049
* Used to actually send the email. This session can come from being passed in the default constructor, or made by <code>Mailer</code> directly.
5150
*/
51+
@Nonnull
5252
private final Session session;
5353

5454
/**
@@ -65,6 +65,7 @@ public class MailSenderImpl implements MailSender {
6565
/**
6666
* @see OperationalConfig
6767
*/
68+
@Nonnull
6869
private final OperationalConfig operationalConfig;
6970

7071
/**
@@ -74,14 +75,6 @@ public class MailSenderImpl implements MailSender {
7475
@Nullable
7576
private final AnonymousSocks5Server proxyServer;
7677

77-
/**
78-
* Allows us to manage how many thread we run at the same time using a thread pool.
79-
* <p>
80-
* Can't be initialized in the field, because we need to reinitialize it whenever the threadpool was closed after a batch of emails and this
81-
* MailSender instance is again engaged.
82-
*/
83-
private ExecutorService executor;
84-
8578
/**
8679
* Used to keep track of running SMTP requests, so that we know when to close down the proxy bridging server (if used).
8780
* <p>
@@ -106,7 +99,7 @@ private void initSession(@Nonnull final Session session, @Nonnull OperationalCon
10699
session.setDebug(operationalConfig.isDebugLogging());
107100
session.getProperties().putAll(operationalConfig.getProperties());
108101

109-
configureSessionWithTimeout(session, operationalConfig.getSessionTimeout());
102+
configureSessionWithTimeout(session, operationalConfig.getSessionTimeout(), transportStrategy);
110103

111104
if (transportStrategy != null) {
112105
if (operationalConfig.isTrustAllSSLHost()) {
@@ -186,18 +179,14 @@ the proxy bridge server (or connection pool in async mode) while a non-async ema
186179
sendMailClosure.run();
187180
return null;
188181
} else {
189-
// start up thread pool if necessary
190-
if (executor == null) {
191-
executor = new NonJvmBlockingThreadPoolExecutor(operationalConfig, "Simple Java Mail async mail sender");
192-
}
193-
return AsyncOperationHelper.executeAsync(executor, "sendMail process", sendMailClosure);
182+
return AsyncOperationHelper.executeAsync(operationalConfig.getExecutorService(), "sendMail process", sendMailClosure);
194183
}
195184
}
196185

197186
/**
198187
* Configures the {@link Session} with the same timeout for socket connection timeout, read and write timeout.
199188
*/
200-
private void configureSessionWithTimeout(final Session session, final int sessionTimeout) {
189+
private void configureSessionWithTimeout(@Nonnull final Session session, final int sessionTimeout, @Nullable final TransportStrategy transportStrategy) {
201190
if (transportStrategy != null) {
202191
// socket timeouts handling
203192
final Properties sessionProperties = session.getProperties();
@@ -384,7 +373,7 @@ public void run() {
384373

385374
boolean proxyBridgeStartedForTestingConnection = false;
386375

387-
configureSessionWithTimeout(session, operationalConfig.getSessionTimeout());
376+
configureSessionWithTimeout(session, operationalConfig.getSessionTimeout(), transportStrategy);
388377

389378
logSession(session, async, "connection test");
390379

@@ -419,6 +408,7 @@ private boolean needsAuthenticatedProxy() {
419408
* @see MailSender#getSession()
420409
*/
421410
@Override
411+
@Nonnull
422412
public Session getSession() {
423413
return session;
424414
}
@@ -427,6 +417,7 @@ public Session getSession() {
427417
* @see MailSender#getOperationalConfig()
428418
*/
429419
@Override
420+
@Nonnull
430421
public OperationalConfig getOperationalConfig() {
431422
return operationalConfig;
432423
}

0 commit comments

Comments
 (0)