com.slack.api.scim.impl.AsyncRateLimitExecutor Maven / Gradle / Ivy
The newest version!
package com.slack.api.scim.impl;
import com.slack.api.SlackConfig;
import com.slack.api.methods.impl.MethodsClientImpl;
import com.slack.api.methods.impl.TeamIdCache;
import com.slack.api.rate_limits.metrics.MetricsDatastore;
import com.slack.api.rate_limits.queue.MessageIdGenerator;
import com.slack.api.rate_limits.queue.MessageIdGeneratorUUIDImpl;
import com.slack.api.scim.*;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
@Slf4j
public class AsyncRateLimitExecutor {
private static final ConcurrentMap ALL_EXECUTORS = new ConcurrentHashMap<>();
private SCIMConfig config;
private MetricsDatastore metricsDatastore; // intentionally mutable
private final TeamIdCache teamIdCache;
private final MessageIdGenerator messageIdGenerator;
private AsyncRateLimitExecutor(MethodsClientImpl methods, SlackConfig config) {
this.config = config.getSCIMConfig();
this.metricsDatastore = config.getSCIMConfig().getMetricsDatastore();
this.teamIdCache = new TeamIdCache(methods);
this.messageIdGenerator = new MessageIdGeneratorUUIDImpl();
}
public static AsyncRateLimitExecutor get(String executorName) {
return ALL_EXECUTORS.get(executorName);
}
public static AsyncRateLimitExecutor getOrCreate(MethodsClientImpl methods, SlackConfig config) {
AsyncRateLimitExecutor executor = ALL_EXECUTORS.get(config.getSCIMConfig().getExecutorName());
if (executor != null && executor.metricsDatastore != config.getSCIMConfig().getMetricsDatastore()) {
// As the metrics datastore has been changed, we should replace the executor
executor.config = config.getSCIMConfig();
executor.metricsDatastore = config.getSCIMConfig().getMetricsDatastore();
}
if (executor == null) {
executor = new AsyncRateLimitExecutor(methods, config);
ALL_EXECUTORS.putIfAbsent(config.getSCIMConfig().getExecutorName(), executor);
}
return executor;
}
private static final List NO_TOKEN_METHOD_NAMES = Collections.emptyList();
public CompletableFuture execute(
SCIMEndpointName endpointName,
Map params,
AsyncExecutionSupplier methodsSupplier) {
String token = params.get("token");
final String teamId = token != null ? teamIdCache.lookupOrResolve(token) : null;
final ExecutorService executorService = teamId != null ? ThreadPools.getOrCreate(config, teamId) : ThreadPools.getDefault(config);
return CompletableFuture.supplyAsync(() -> {
String messageId = messageIdGenerator.generate();
addMessageId(teamId, endpointName, messageId);
syncCurrentQueueSizeStats(teamId, endpointName);
if (NO_TOKEN_METHOD_NAMES.contains(endpointName) || teamId == null) {
return runWithoutQueue(teamId, endpointName, methodsSupplier);
} else {
return enqueueThenRun(
messageId,
teamId,
endpointName,
params,
methodsSupplier
);
}
}, executorService);
}
private void syncCurrentQueueSizeStats(String teamId, SCIMEndpointName endpointName) {
if (teamId != null) {
metricsDatastore.updateCurrentQueueSize(config.getExecutorName(), teamId, endpointName.name());
}
}
private void addMessageId(
String teamId,
SCIMEndpointName endpointName,
String messageId) {
metricsDatastore.addToWaitingMessageIds(
config.getExecutorName(), teamId, endpointName.name(), messageId);
}
private void removeMessageId(
String teamId,
SCIMEndpointName endpointName,
String messageId) {
metricsDatastore.deleteFromWaitingMessageIds(
config.getExecutorName(), teamId, endpointName.name(), messageId);
}
public T runWithoutQueue(
String teamId,
SCIMEndpointName endpointName,
AsyncExecutionSupplier methodsSupplier) {
try {
return methodsSupplier.execute();
} catch (RuntimeException e) {
return handleRuntimeException(teamId, endpointName, e);
} catch (IOException e) {
return handleIOException(teamId, endpointName, e);
} catch (SCIMApiException e) {
logSCIMApiException(teamId, endpointName, e);
throw new SCIMApiCompletionException(null, e, null);
}
}
private T enqueueThenRun(
String messageId,
String teamId,
SCIMEndpointName endpointName,
Map params,
AsyncExecutionSupplier methodsSupplier) {
try {
AsyncRateLimitQueue activeQueue = AsyncRateLimitQueue.getOrCreate(config, teamId);
if (activeQueue == null) {
log.warn("Queue for teamId: {} was not found. Going to run the API call immediately.", teamId);
}
AsyncExecutionSupplier supplier = null;
activeQueue.enqueue(messageId, teamId, endpointName.name(), params, methodsSupplier);
long consumedMillis = 0L;
while (supplier == null && consumedMillis < config.getMaxIdleMills()) {
Thread.sleep(10);
consumedMillis += 10;
supplier = (AsyncExecutionSupplier) activeQueue.dequeueIfReady(
messageId, teamId, endpointName.name(), params);
removeMessageId(teamId, endpointName, messageId);
}
if (supplier == null) {
activeQueue.remove(endpointName.name(), messageId);
throw new RejectedExecutionException("Gave up executing the message after " + config.getMaxIdleMills() + " milliseconds.");
}
T response = supplier.execute();
return response;
} catch (RuntimeException e) {
return handleRuntimeException(teamId, endpointName, e);
} catch (IOException e) {
return handleIOException(teamId, endpointName, e);
} catch (SCIMApiException e) {
logSCIMApiException(teamId, endpointName, e);
if (e.getResponse().code() == 429) {
return enqueueThenRun(messageId, teamId, endpointName, params, methodsSupplier);
}
throw new SCIMApiCompletionException(null, e, null);
} catch (InterruptedException e) {
log.error("Got an InterruptedException (error: {})", e.getMessage(), e);
throw new RuntimeException(e);
}
}
private static T handleRuntimeException(
String teamId, SCIMEndpointName endpointName, RuntimeException e) {
log.error("Got an exception while calling {} API (team: {}, error: {})", endpointName, teamId, e.getMessage(), e);
throw new SCIMApiCompletionException(null, null, e);
}
private static T handleIOException(
String teamId, SCIMEndpointName endpointName, IOException e) {
log.error("Failed to connect to {} API (team: {}, error: {})", endpointName, teamId, e.getMessage(), e);
throw new SCIMApiCompletionException(e, null, null);
}
private static void logSCIMApiException(String teamId, SCIMEndpointName endpointName, SCIMApiException e) {
if (e.getResponse().code() == 429) {
String retryAfterSeconds = e.getResponse().header("Retry-After");
log.error("Got a rate-limited response from {} API (team: {}, error: {}, retry-after: {})",
endpointName,
teamId,
e.getMessage(),
retryAfterSeconds,
e
);
} else {
log.error("Got an unsuccessful response from {} API (team: {}, error: {}, status code: {})",
endpointName,
teamId,
e.getMessage(),
e.getResponse().code(),
e
);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy