Skip to content

Commit

Permalink
feat: optional thread executor
Browse files Browse the repository at this point in the history
  • Loading branch information
brenoepics committed Apr 30, 2024
1 parent 2a32a98 commit 9d7c006
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 210 deletions.
34 changes: 19 additions & 15 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import { defineConfigWithTheme } from 'vitepress'
import type { ThemeConfig } from 'vitepress-carbon'
import {defineConfigWithTheme} from 'vitepress'
import type {ThemeConfig} from 'vitepress-carbon'
import baseConfig from 'vitepress-carbon/config'

const nav = [
{
text: 'Docs',
activeMatch: `^/(guide|examples)/`,
items: [
{ text: 'Guide', link: '/guide/introduction' },
{ text: 'Examples', link: '/examples/' },
{ text: 'Error Reference', link: '/error-reference/' },
{ text: 'JavaDoc', link: 'https://brenoepics.github.io/at4j/javadoc/' }
{text: 'Guide', link: '/guide/introduction'},
{text: 'Examples', link: '/examples/'},
{text: 'Error Reference', link: '/error-reference/'},
{text: 'JavaDoc', link: 'https://brenoepics.github.io/at4j/javadoc/'}
]
},
{
text: 'About',
activeMatch: `^/about/`,
items: [
{ text: 'FAQ', link: '/about/faq' },
{ text: 'Releases', link: '/about/releases' },
{ text: 'Code of Conduct', link: '/about/coc' }
{text: 'FAQ', link: '/about/faq'},
{text: 'Releases', link: '/about/releases'},
{text: 'Code of Conduct', link: '/about/coc'}
]
},
]
Expand All @@ -28,7 +28,7 @@ const sidebar = {
{
text: 'Getting Started',
items: [
{ text: 'Introduction', link: '/guide/introduction' },
{text: 'Introduction', link: '/guide/introduction'},
{
text: 'Download/Installation',
link: '/guide/installation'
Expand All @@ -41,6 +41,10 @@ const sidebar = {
text: 'Basic Usage',
link: '/guide/basic-usage'
},
{
text: 'Threading',
link: '/guide/threading'
},
{
text: 'Examples',
link: '/examples/'
Expand Down Expand Up @@ -79,10 +83,10 @@ export default defineConfigWithTheme<ThemeConfig>({
base: '/at4j/',

head: [
['meta', { name: 'theme-color', content: '#3c8772' }],
['meta', { property: 'og:url', content: 'https://github.com/brenoepics/at4j' }],
['meta', { property: 'og:type', content: 'Repository' }],
['meta', { property: 'og:title', content: 'AT4J' }],
['meta', {name: 'theme-color', content: '#3c8772'}],
['meta', {property: 'og:url', content: 'https://github.com/brenoepics/at4j'}],
['meta', {property: 'og:type', content: 'Repository'}],
['meta', {property: 'og:title', content: 'AT4J'}],
[
'meta',
{
Expand All @@ -109,7 +113,7 @@ export default defineConfigWithTheme<ThemeConfig>({
},
link: 'https://www.postman.com/maintenance-astronaut-2993290/workspace/brenoepics/collection/18589822-dfe7a640-9b94-47a8-b19f-46cb9cc8843e?action=share&creator=18589822'
},
{ icon: 'github', link: 'https://github.com/brenoepics/at4j' }
{icon: 'github', link: 'https://github.com/brenoepics/at4j'}

],

Expand Down
1 change: 1 addition & 0 deletions docs/src/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@ public class ExampleDetector {
}
}
```

31 changes: 31 additions & 0 deletions docs/src/guide/threading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Creating an AzureApi with a Custom ExecutorService

This guide will walk you through the process of creating an `AzureApiBuilder` with a custom `ExecutorService`.

## Steps

1. **Create a custom ExecutorService**

For this example, we'll create a custom `ExecutorService` using the Virtual Threads API introduced in Java 21:

```java
ExecutorService customExecutorService = Executors.newVirtualThreadPerTaskExecutor();
```

2. **Create an AzureApiBuilder**

You can create an `AzureApi` by using the `AzureApiBuilder` class. Here's an example:
```java
String azureKey = "<Your Azure Subscription Key>";
String azureRegion = "<Your Azure Subscription Region>";
AzureApi azureApi = new AzureApiBuilder()
.setKey(azureKey)
.region(azureRegion)
.executorService(customExecutorService).build();
```
This will create an `AzureApi` with the settings you specified in the `AzureApiBuilder`.
That's it! You've successfully created an `AzureApi` with a custom `ExecutorService`. You can now use this `AzureApi` to
make requests to the Azure API.
23 changes: 22 additions & 1 deletion src/main/java/io/github/brenoepics/at4j/AzureApiBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.net.ProxySelector;
import java.net.http.HttpClient;
import java.time.Duration;
import java.util.concurrent.ExecutorService;

/**
* Builder class for constructing instances of AzureApi.
Expand All @@ -23,6 +24,7 @@ public class AzureApiBuilder {
private SSLContext sslContext;
private SSLParameters sslParameters;
private Duration connectTimeout;
private ExecutorService executorService;

/** Default constructor initializes the base URL to the global endpoint. */
public AzureApiBuilder() {
Expand Down Expand Up @@ -125,6 +127,21 @@ public AzureApiBuilder sslParameters(SSLParameters sslParameters) {
return this;
}


/**
* Sets the executor service for the Azure API.
*
* @param executorService The executor service for the Azure API.
* @return The current instance of AzureApiBuilder for method chaining.
* @see <a
* href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ExecutorService.html"
* >ExecutorService</a>
*/
public AzureApiBuilder executorService(ExecutorService executorService) {
this.executorService = executorService;
return this;
}

/**
* Builds and returns an instance of AzureApi with the configured parameters.
*
Expand Down Expand Up @@ -156,6 +173,10 @@ public AzureApi build() {
httpClient.connectTimeout(connectTimeout);
}

return new AzureApiImpl<>(httpClient.build(), baseURL, subscriptionKey, subscriptionRegion);
if (executorService != null) {
httpClient.executor(executorService);
}

return new AzureApiImpl<>(httpClient.build(), baseURL, subscriptionKey, subscriptionRegion, executorService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

/**
* This class is an implementation of the AzureApi interface. It provides methods to interact with
Expand All @@ -45,7 +46,7 @@ public class AzureApiImpl<T> implements AzureApi {
private final RateLimitManager<T, ?, ?> ratelimitManager = new RateLimitManager<>(this);

/** The thread pool which is used internally. */
private final ThreadPoolImpl threadPool = new ThreadPoolImpl();
private final ThreadPool threadPool;

/**
* Constructor for AzureApiImpl.
Expand All @@ -56,11 +57,12 @@ public class AzureApiImpl<T> implements AzureApi {
* @param subscriptionRegion The subscription region for this instance.
*/
public AzureApiImpl(
HttpClient httpClient, BaseURL baseURL, String subscriptionKey, String subscriptionRegion) {
HttpClient httpClient, BaseURL baseURL, String subscriptionKey, String subscriptionRegion, ExecutorService executor) {
this.httpClient = httpClient;
this.baseURL = baseURL;
this.subscriptionKey = subscriptionKey;
this.subscriptionRegion = subscriptionRegion;
this.threadPool = new ThreadPoolImpl(executor);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,61 +18,8 @@ public interface ThreadPool {
ExecutorService getExecutorService();

/**
* Gets the used scheduler.
* Shutdowns the thread pool.
*
* @return The used scheduler.
*/
ScheduledExecutorService getScheduler();

/**
* Gets the used daemon scheduler.
*
* @return The used daemon scheduler.
*/
ScheduledExecutorService getDaemonScheduler();

/**
* Gets an executor service which only uses a single thread.
*
* @param threadName The thread name of the executor service. Will create a new one if the thread
* name is used the first time.
* @return The executor service with the given thread name. Never {@code null}!
*/
ExecutorService getSingleThreadExecutorService(String threadName);

/**
* Gets an executor service which only uses a single daemon thread.
*
* @param threadName The thread name of the executor service. Will create a new one if the thread
* name is used the first time.
* @return The executor service with the given thread name. Never {@code null}!
*/
ExecutorService getSingleDaemonThreadExecutorService(String threadName);

/**
* Removes an existing executor service.
*
* <p>This allows you to get a fresh executor service when calling {@link
* #getSingleThreadExecutorService(String)} again.
*
* @param threadName The thread name of the executor service.
* @return The removed and shutdown executor service with the given thread name.
*/
Optional<ExecutorService> removeAndShutdownSingleThreadExecutorService(String threadName);

/**
* Executes code after a given duration.
*
* <p>Tasks will be scheduled on the daemon executor, allowing the bot to shut down without all
* tasks being executed. This method is not meant to persist a scheduled task over multiple bot
* life cycles.
*
* @param task The code to run.
* @param duration The duration to run the code after.
* @param unit The unit of the duration given.
* @return A future that completes when the scheduled task is finished.
* @param <T> The return type of the future.
*/
<T> CompletableFuture<T> runAfter(
Supplier<CompletableFuture<T>> task, long duration, TimeUnit unit);
void shutdown();
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.github.brenoepics.at4j.core.thread;

import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.*;
import java.util.function.Supplier;

/** The implementation of {@link ThreadPool}. */
public class ThreadPoolImpl implements ThreadPool {
Expand All @@ -11,102 +12,39 @@ public class ThreadPoolImpl implements ThreadPool {
private static final int MAXIMUM_POOL_SIZE = Integer.MAX_VALUE;
private static final int KEEP_ALIVE_TIME = 60;
private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
private final Logger logger = LoggerFactory.getLogger(ThreadPoolImpl.class);

private final ExecutorService executorService =
new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TIME_UNIT,
new SynchronousQueue<>(),
new AT4JThreadFactory("AT4J - Central ExecutorService - %d", false));
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(
CORE_POOL_SIZE, new AT4JThreadFactory("AT4J - Central Scheduler - %d", false));
private final ScheduledExecutorService daemonScheduler =
Executors.newScheduledThreadPool(
CORE_POOL_SIZE, new AT4JThreadFactory("AT4J - Central Daemon Scheduler - %d", true));
private final ConcurrentHashMap<String, ExecutorService> executorServiceSingleThreads =
new ConcurrentHashMap<>();

/** Shutdowns the thread pool. */
public void shutdown() {
executorService.shutdown();
scheduler.shutdown();
daemonScheduler.shutdown();
executorServiceSingleThreads.values().forEach(ExecutorService::shutdown);
}

@Override
public ExecutorService getExecutorService() {
return executorService;
}
private final ExecutorService executorService;

@Override
public ScheduledExecutorService getScheduler() {
return scheduler;
}

@Override
public ScheduledExecutorService getDaemonScheduler() {
return daemonScheduler;
}
public ThreadPoolImpl(ExecutorService executorService) {
if (executorService == null) {
logger.debug("Starting with default AT4J executor service.");
this.executorService = newAt4jDefault();
return;
}

@Override
public ExecutorService getSingleThreadExecutorService(String threadName) {
return executorServiceSingleThreads.computeIfAbsent(
threadName,
key ->
new ThreadPoolExecutor(
0,
1,
KEEP_ALIVE_TIME,
TIME_UNIT,
new LinkedBlockingQueue<>(),
new AT4JThreadFactory("AT4J - " + threadName, false)));
logger.debug("Starting with custom AT4J executor service.");
this.executorService = executorService;
}

@Override
public ExecutorService getSingleDaemonThreadExecutorService(String threadName) {
return executorServiceSingleThreads.computeIfAbsent(
threadName,
key ->
new ThreadPoolExecutor(
0,
1,
KEEP_ALIVE_TIME,
TIME_UNIT,
new LinkedBlockingQueue<>(),
new AT4JThreadFactory("AT4J - " + threadName, true)));
public void shutdown() {
logger.debug("Shutting down AT4J executor service.");
executorService.shutdown();
}

@Override
public Optional<ExecutorService> removeAndShutdownSingleThreadExecutorService(String threadName) {
ExecutorService takenExecutorService = executorServiceSingleThreads.remove(threadName);
if (takenExecutorService != null) {
takenExecutorService.shutdown();
}
return Optional.ofNullable(takenExecutorService);
public ExecutorService getExecutorService() {
return executorService;
}

@Override
public <T> CompletableFuture<T> runAfter(
Supplier<CompletableFuture<T>> task, long duration, TimeUnit unit) {
CompletableFuture<T> future = new CompletableFuture<>();
getDaemonScheduler()
.schedule(
() ->
task.get()
.whenComplete(
(result, ex) -> {
if (ex != null) {
future.completeExceptionally(ex);
} else {
future.complete(result);
}
}),
duration,
unit);
return future;
public static ExecutorService newAt4jDefault() {
return new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE_TIME,
TIME_UNIT,
new SynchronousQueue<>(),
new AT4JThreadFactory("AT4J - Central ExecutorService - %d", false));
}
}
Loading

0 comments on commit 9d7c006

Please sign in to comment.