Skip to content

Commit 1ecabc6

Browse files
authored
Fixes dangling resources when using async (#323)
* Fixes dangling resources when using async (#322) * Reverted incompatible pick from v2
1 parent bb3eb02 commit 1ecabc6

27 files changed

+338
-262
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ switcher.silent -> Enable contigency given the time for the client to retry - e.
7878
switcher.truststore.path -> Path to the truststore file
7979
switcher.truststore.password -> Truststore password
8080
switcher.timeout -> Time in ms given to the API to respond - 3000 default value
81-
switcher.poolsize -> Number of threads used to execute the API - 10 default value
81+
switcher.poolsize -> Number of threads used to execute the API - 2 default value
8282
8383
(Java 8 applications only)
8484
switcher.regextimeout -> Time in ms given to Timed Match Worker used for local Regex (ReDoS safety mechanism) - 3000 default value

src/main/java/com/github/switcherapi/client/ContextBuilder.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public ContextBuilder component(String component) {
9898
* @return ContextBuilder
9999
*/
100100
public ContextBuilder environment(String environment) {
101-
properties.setValue(ContextKey.ENVIRONMENT, properties.getEnvironmentOrDefault(environment));
101+
properties.setValue(ContextKey.ENVIRONMENT, properties.getValueDefault(environment, SwitcherProperties.DEFAULT_ENV));
102102
return this;
103103
}
104104

@@ -130,8 +130,8 @@ public ContextBuilder snapshotAutoUpdateInterval(String snapshotAutoUpdateInterv
130130
* @param regexTimeout Time in ms given to Timed Match Worker used for local Regex (ReDoS safety mechanism) - 3000 default value
131131
* @return ContextBuilder
132132
*/
133-
public ContextBuilder regexTimeout(String regexTimeout) {
134-
properties.setValue(ContextKey.REGEX_TIMEOUT, properties.getRegexTimeoutOrDefault(regexTimeout));
133+
public ContextBuilder regexTimeout(int regexTimeout) {
134+
properties.setValue(ContextKey.REGEX_TIMEOUT, regexTimeout);
135135
return this;
136136
}
137137

@@ -198,16 +198,16 @@ public ContextBuilder truststorePassword(String truststorePassword) {
198198
* @param timeoutMs Time in ms given to the API to respond - 3000 default value
199199
* @return ContextBuilder
200200
*/
201-
public ContextBuilder timeoutMs(String timeoutMs) {
202-
properties.setValue(ContextKey.TIMEOUT_MS, properties.getTimeoutMsOrDefault(timeoutMs));
201+
public ContextBuilder timeoutMs(int timeoutMs) {
202+
properties.setValue(ContextKey.TIMEOUT_MS, timeoutMs);
203203
return this;
204204
}
205205

206206
/**
207207
* @param poolSize Number of threads for the pool connection - 10 default value
208208
* @return ContextBuilder
209209
*/
210-
public ContextBuilder poolConnectionSize(String poolSize) {
210+
public ContextBuilder poolConnectionSize(int poolSize) {
211211
properties.setValue(ContextKey.POOL_CONNECTION_SIZE, poolSize);
212212
return this;
213213
}

src/main/java/com/github/switcherapi/client/SwitcherContextBase.java

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
import com.github.switcherapi.client.exception.SwitchersValidationException;
77
import com.github.switcherapi.client.model.ContextKey;
88
import com.github.switcherapi.client.model.Switcher;
9+
import com.github.switcherapi.client.remote.ClientWS;
910
import com.github.switcherapi.client.remote.ClientWSImpl;
1011
import com.github.switcherapi.client.service.SwitcherValidator;
1112
import com.github.switcherapi.client.service.ValidatorService;
1213
import com.github.switcherapi.client.service.WorkerName;
14+
import com.github.switcherapi.client.service.local.ClientLocal;
1315
import com.github.switcherapi.client.service.local.ClientLocalService;
1416
import com.github.switcherapi.client.service.local.SwitcherLocalService;
1517
import com.github.switcherapi.client.service.remote.ClientRemote;
@@ -31,6 +33,9 @@
3133
import java.util.concurrent.ScheduledExecutorService;
3234
import java.util.concurrent.TimeUnit;
3335

36+
import static com.github.switcherapi.client.remote.Constants.DEFAULT_POOL_SIZE;
37+
import static com.github.switcherapi.client.remote.Constants.DEFAULT_TIMEOUT;
38+
3439
/**
3540
* <b>Switcher Context Base Toolkit</b>
3641
*
@@ -113,25 +118,40 @@ public static void loadProperties(String contextFilename) {
113118
}
114119

115120
/**
116-
* Initialize Switcher Client
121+
* Initialize Switcher Client SDK.<br>
122+
*
123+
* - Validate the context<br>
124+
* - Validate Switcher Keys<br>
125+
* - Build the Switcher Executor instance<br>
126+
* - Load Switchers into memory<br>
127+
* - Pre-configure the context<br>
117128
*/
118129
public static void initializeClient() {
119130
validateContext();
120131
validateSwitcherKeys();
132+
instance = buildInstance();
133+
134+
loadSwitchers();
135+
scheduleSnapshotAutoUpdate(contextStr(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL));
136+
ContextBuilder.preConfigure(switcherProperties);
137+
}
121138

139+
/**
140+
* Build the Switcher Executor instance based on the context
141+
*
142+
* @return SwitcherExecutor instance
143+
*/
144+
private static SwitcherExecutor buildInstance() {
145+
final ClientWS clientWS = initRemotePoolExecutorService();
122146
final SwitcherValidator validatorService = new ValidatorService();
123-
final ClientRemote clientRemote = new ClientRemoteService(ClientWSImpl.build());
124-
final ClientLocalService clientLocal = new ClientLocalService(validatorService);
147+
final ClientRemote clientRemote = new ClientRemoteService(clientWS);
148+
final ClientLocal clientLocal = new ClientLocalService(validatorService);
125149

126150
if (contextBol(ContextKey.LOCAL_MODE)) {
127-
instance = new SwitcherLocalService(clientRemote, clientLocal);
151+
return new SwitcherLocalService(clientRemote, clientLocal);
128152
} else {
129-
instance = new SwitcherRemoteService(clientRemote, new SwitcherLocalService(clientRemote, clientLocal));
153+
return new SwitcherRemoteService(clientRemote, new SwitcherLocalService(clientRemote, clientLocal));
130154
}
131-
132-
loadSwitchers();
133-
scheduleSnapshotAutoUpdate(contextStr(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL));
134-
ContextBuilder.preConfigure(switcherProperties);
135155
}
136156

137157
/**
@@ -243,6 +263,24 @@ private static void initWatcherExecutorService() {
243263
return thread;
244264
});
245265
}
266+
267+
/**
268+
* Configure Executor Service for Switcher Remote Worker
269+
*/
270+
private static ClientWS initRemotePoolExecutorService() {
271+
int timeoutMs = Optional.ofNullable(contextInt(ContextKey.TIMEOUT_MS)).orElse(DEFAULT_TIMEOUT);
272+
int poolSize = Optional.ofNullable(contextInt(ContextKey.POOL_CONNECTION_SIZE)).orElse(DEFAULT_POOL_SIZE);
273+
String component = Optional.ofNullable(contextStr(ContextKey.COMPONENT)).orElse("switcher-client");
274+
275+
final ExecutorService remotePoolExecutorService = Executors.newFixedThreadPool(poolSize, r -> {
276+
Thread thread = new Thread(r);
277+
thread.setName(String.format("%s-%s", WorkerName.SWITCHER_REMOTE_WORKER, component));
278+
thread.setDaemon(true);
279+
return thread;
280+
});
281+
282+
return ClientWSImpl.build(remotePoolExecutorService, timeoutMs);
283+
}
246284

247285
/**
248286
* Return a ready-to-use Switcher that will invoke the criteria configured into the Switcher API or Snapshot
@@ -359,6 +397,16 @@ public static void checkSwitchers() {
359397
public static String contextStr(ContextKey contextKey) {
360398
return switcherProperties.getValue(contextKey);
361399
}
400+
401+
/**
402+
* Retrieve integer context parameter based on contextKey
403+
*
404+
* @param contextKey to be retrieved
405+
* @return Value configured for the context parameter
406+
*/
407+
public static Integer contextInt(ContextKey contextKey) {
408+
return switcherProperties.getInt(contextKey);
409+
}
362410

363411
/**
364412
* Retrieve boolean context parameter based on contextKey

src/main/java/com/github/switcherapi/client/SwitcherContextValidator.java

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
*/
1313
class SwitcherContextValidator {
1414

15-
public static final String ERR_FORMAT = "Invalid parameter format for [%s]. Expected %s.";
1615
public static final String ERR_URL = "URL not defined [add: switcher.url]";
1716
public static final String ERR_API = "API Key not defined [add: switcher.apikey]";
1817
public static final String ERR_DOMAIN = "Domain not defined [add: switcher.domain]";
@@ -39,22 +38,6 @@ public static void validate(final SwitcherProperties prop) {
3938
} else {
4039
validateLocal(prop);
4140
}
42-
43-
validateOptionals(prop);
44-
}
45-
46-
/**
47-
* Validate optional context arguments
48-
*
49-
* @param prop Configured properties
50-
*/
51-
public static void validateOptionals(final SwitcherProperties prop) {
52-
try {
53-
Integer.parseInt(prop.getValue(ContextKey.REGEX_TIMEOUT));
54-
} catch (NumberFormatException e) {
55-
throw new SwitcherContextException(
56-
String.format(ERR_FORMAT, ContextKey.REGEX_TIMEOUT.getParam(), Integer.class));
57-
}
5841
}
5942

6043
/**

src/main/java/com/github/switcherapi/client/SwitcherProperties.java

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.Map;
1010
import java.util.Properties;
1111

12+
import static com.github.switcherapi.client.remote.Constants.DEFAULT_POOL_SIZE;
13+
1214
/**
1315
* The configuration definition object contains all necessary SDK properties to
1416
* control the API client behaviors, access and snapshot location.
@@ -19,9 +21,9 @@ public class SwitcherProperties {
1921

2022
public static final String DEFAULT_ENV = "default";
2123

22-
public static final String DEFAULT_REGEX_TIMEOUT = "3000";
24+
public static final Integer DEFAULT_REGEX_TIMEOUT = 3000;
2325

24-
public static final String DEFAULT_TIMEOUT_MS = "3000";
26+
public static final Integer DEFAULT_TIMEOUT_MS = 3000;
2527

2628
private final Map<String, Object> properties = new HashMap<>();
2729

@@ -33,6 +35,7 @@ private void setDefaults() {
3335
setValue(ContextKey.ENVIRONMENT, DEFAULT_ENV);
3436
setValue(ContextKey.REGEX_TIMEOUT, DEFAULT_REGEX_TIMEOUT);
3537
setValue(ContextKey.TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
38+
setValue(ContextKey.POOL_CONNECTION_SIZE, DEFAULT_POOL_SIZE);
3639
setValue(ContextKey.SNAPSHOT_AUTO_LOAD, false);
3740
setValue(ContextKey.SNAPSHOT_SKIP_VALIDATION, false);
3841
setValue(ContextKey.LOCAL_MODE, false);
@@ -44,18 +47,18 @@ public void loadFromProperties(Properties prop) {
4447
setValue(ContextKey.APIKEY, SwitcherUtils.resolveProperties(ContextKey.APIKEY.getParam(), prop));
4548
setValue(ContextKey.DOMAIN, SwitcherUtils.resolveProperties(ContextKey.DOMAIN.getParam(), prop));
4649
setValue(ContextKey.COMPONENT, SwitcherUtils.resolveProperties(ContextKey.COMPONENT.getParam(), prop));
47-
setValue(ContextKey.ENVIRONMENT, getEnvironmentOrDefault(SwitcherUtils.resolveProperties(ContextKey.ENVIRONMENT.getParam(), prop)));
50+
setValue(ContextKey.ENVIRONMENT, getValueDefault(SwitcherUtils.resolveProperties(ContextKey.ENVIRONMENT.getParam(), prop), DEFAULT_ENV));
4851
setValue(ContextKey.SNAPSHOT_LOCATION, SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_LOCATION.getParam(), prop));
49-
setValue(ContextKey.SNAPSHOT_SKIP_VALIDATION, getBoolDefault(Boolean.parseBoolean(SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_SKIP_VALIDATION.getParam(), prop)), false));
50-
setValue(ContextKey.SNAPSHOT_AUTO_LOAD, getBoolDefault(Boolean.parseBoolean(SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_AUTO_LOAD.getParam(), prop)), false));
52+
setValue(ContextKey.SNAPSHOT_SKIP_VALIDATION, getBoolDefault(SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_SKIP_VALIDATION.getParam(), prop), false));
53+
setValue(ContextKey.SNAPSHOT_AUTO_LOAD, getBoolDefault(SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_AUTO_LOAD.getParam(), prop), false));
5154
setValue(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL, SwitcherUtils.resolveProperties(ContextKey.SNAPSHOT_AUTO_UPDATE_INTERVAL.getParam(), prop));
5255
setValue(ContextKey.SILENT_MODE, SwitcherUtils.resolveProperties(ContextKey.SILENT_MODE.getParam(), prop));
53-
setValue(ContextKey.LOCAL_MODE, getBoolDefault(Boolean.parseBoolean(SwitcherUtils.resolveProperties(ContextKey.LOCAL_MODE.getParam(), prop)), false));
54-
setValue(ContextKey.REGEX_TIMEOUT, getRegexTimeoutOrDefault(SwitcherUtils.resolveProperties(ContextKey.REGEX_TIMEOUT.getParam(), prop)));
56+
setValue(ContextKey.LOCAL_MODE, getBoolDefault(SwitcherUtils.resolveProperties(ContextKey.LOCAL_MODE.getParam(), prop), false));
57+
setValue(ContextKey.REGEX_TIMEOUT, getIntDefault(SwitcherUtils.resolveProperties(ContextKey.REGEX_TIMEOUT.getParam(), prop), DEFAULT_REGEX_TIMEOUT));
5558
setValue(ContextKey.TRUSTSTORE_PATH, SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PATH.getParam(), prop));
5659
setValue(ContextKey.TRUSTSTORE_PASSWORD, SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PASSWORD.getParam(), prop));
57-
setValue(ContextKey.TIMEOUT_MS, getTimeoutMsOrDefault(SwitcherUtils.resolveProperties(ContextKey.TIMEOUT_MS.getParam(), prop)));
58-
setValue(ContextKey.POOL_CONNECTION_SIZE, SwitcherUtils.resolveProperties(ContextKey.POOL_CONNECTION_SIZE.getParam(), prop));
60+
setValue(ContextKey.TIMEOUT_MS, getIntDefault(SwitcherUtils.resolveProperties(ContextKey.TIMEOUT_MS.getParam(), prop), DEFAULT_TIMEOUT_MS));
61+
setValue(ContextKey.POOL_CONNECTION_SIZE, getIntDefault(SwitcherUtils.resolveProperties(ContextKey.POOL_CONNECTION_SIZE.getParam(), prop), DEFAULT_POOL_SIZE));
5962
}
6063

6164
public String getValue(ContextKey contextKey) {
@@ -66,6 +69,10 @@ public boolean getBoolean(ContextKey contextKey) {
6669
return getValue(contextKey, Boolean.class);
6770
}
6871

72+
public Integer getInt(ContextKey contextKey) {
73+
return getValue(contextKey, Integer.class);
74+
}
75+
6976
private <T> T getValue(ContextKey contextKey, Class<T> type) {
7077
try {
7178
return type.cast(properties.get(contextKey.getParam()));
@@ -78,33 +85,21 @@ public void setValue(ContextKey contextKey, Object value) {
7885
properties.put(contextKey.getParam(), value);
7986
}
8087

81-
public String getEnvironmentOrDefault(String environment) {
82-
if (StringUtils.isNotBlank(environment)) {
83-
return environment;
84-
}
85-
86-
return DEFAULT_ENV;
87-
}
88-
89-
public String getRegexTimeoutOrDefault(String regexTimeout) {
90-
if (StringUtils.isNotBlank(regexTimeout)) {
91-
return regexTimeout;
92-
}
93-
94-
return DEFAULT_REGEX_TIMEOUT;
88+
public String getValueDefault(String value, String defaultValue) {
89+
return StringUtils.defaultIfBlank(value, defaultValue);
9590
}
9691

97-
public String getTimeoutMsOrDefault(String timeoutMs) {
98-
if (StringUtils.isNotBlank(timeoutMs)) {
99-
return timeoutMs;
92+
public Integer getIntDefault(String value, Integer defaultValue) {
93+
if (StringUtils.isNotBlank(value)) {
94+
return Integer.parseInt(value);
10095
}
10196

102-
return DEFAULT_TIMEOUT_MS;
97+
return defaultValue;
10398
}
10499

105-
public Boolean getBoolDefault(Boolean value, Boolean defaultValue) {
106-
if (value != null) {
107-
return value;
100+
public Boolean getBoolDefault(String value, Boolean defaultValue) {
101+
if (StringUtils.isNotBlank(value)) {
102+
return Boolean.parseBoolean(value);
108103
}
109104

110105
return defaultValue;

src/main/java/com/github/switcherapi/client/model/AsyncSwitcher.java

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.util.concurrent.ExecutorService;
1010
import java.util.concurrent.Executors;
1111

12+
import static com.github.switcherapi.client.service.WorkerName.SWITCHER_ASYNC_WORKER;
13+
1214
/**
1315
* Implementation handle asynchronous Criteria execution when using throttle.
1416
* <br>Threads are only created when the time calculated for the next run is lower than the current time.
@@ -17,7 +19,7 @@
1719
* @since 2021-11-27
1820
*
1921
*/
20-
public class AsyncSwitcher implements Runnable {
22+
public class AsyncSwitcher {
2123

2224
private static final Logger logger = LoggerFactory.getLogger(AsyncSwitcher.class);
2325

@@ -30,7 +32,13 @@ public class AsyncSwitcher implements Runnable {
3032
private long nextRun = 0;
3133

3234
public AsyncSwitcher(final SwitcherInterface switcherInterface, long delay) {
33-
this.executorService = Executors.newCachedThreadPool();
35+
this.executorService = Executors.newCachedThreadPool(r -> {
36+
Thread thread = new Thread(r);
37+
thread.setName(SWITCHER_ASYNC_WORKER.toString());
38+
thread.setDaemon(true);
39+
return thread;
40+
});
41+
3442
this.switcherInterface = switcherInterface;
3543
this.delay = delay;
3644
}
@@ -46,20 +54,14 @@ public synchronized void execute() {
4654
SwitcherUtils.debug(logger, "Running AsyncSwitcher");
4755

4856
this.nextRun = System.currentTimeMillis() + this.delay;
49-
this.executorService.submit(this);
57+
this.executorService.submit(this::run);
5058
}
5159
}
5260

53-
@Override
5461
public void run() {
5562
try {
5663
final CriteriaResponse response = switcherInterface.executeCriteria();
57-
58-
switcherInterface.getHistoryExecution().removeIf(item ->
59-
switcherInterface.getSwitcherKey().equals(item.getSwitcherKey()) &&
60-
switcherInterface.getEntry().equals(item.getEntry()));
61-
62-
switcherInterface.getHistoryExecution().add(response);
64+
switcherInterface.updateHistoryExecution(response);
6365
} catch (SwitcherException e) {
6466
logger.error(e.getMessage(), e);
6567
}

src/main/java/com/github/switcherapi/client/model/ContextKey.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@ public enum ContextKey {
8585
TRUSTSTORE_PASSWORD("switcher.truststore.password"),
8686

8787
/**
88-
* (Number) Defines the timeout in ms for the Remote client.
88+
* (Number) Defines the timeout in ms for the Remote client (default is 3000).
8989
*/
9090
TIMEOUT_MS("switcher.timeout"),
9191

9292
/**
93-
* (Number) Defines a fixed number of threads for the pool connection (default is 10).
93+
* (Number) Defines a fixed number of threads for the pool connection (default is 2).
9494
*/
9595
POOL_CONNECTION_SIZE("switcher.poolsize");
9696

0 commit comments

Comments
 (0)