Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add executor service param #1503

Merged
merged 20 commits into from
Oct 5, 2023

Conversation

Semernitskaya
Copy link
Contributor

@Semernitskaya Semernitskaya commented Aug 27, 2023

Description

Changes allow to specify custom ExecutorService for the FailsafePlugin. If ExecutorService is not specified - we don't use any default value and delegate executor creation to the Failsafe library.

It is also possible to provide custom ExecutorService as a bean if Spring Boot starter is used. Since 4 different FailsafePlugin-s can be created for each Http client (see FailsafePluginFactory) - 4 custom ExecutorService-s can be specified (naming convention is provided in Spring Boot starter documentation). If no custom ExecutorService is provided - ExecutorService creation again will be delegated to the the Failsafe library, no ExecutorService beans will be created.

Motivation and Context

Closes #1501

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.

@fatroom
Copy link
Member

fatroom commented Sep 11, 2023

@Semernitskaya will you continue working on this feature? Do you need any support?

@Semernitskaya
Copy link
Contributor Author

@Semernitskaya will you continue working on this feature? Do you need any support?

Hello! Yes, sorry for the delay - had several urgent tasks in the team's projects, so I'm a bit behind the schedule with this task

@@ -57,10 +64,14 @@ public RequestExecution aroundAsync(final RequestExecution execution) {

if (policies.isEmpty()) {
return execution.execute(arguments);
} else if (executorService != null) {
return Failsafe.with(select(arguments))
.with(executorService)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Failsafe doc for the with(ExecutorService) method:

Note: The {@code executorService} should have a core pool size or parallelism of at least 2 in order for {@link
Timeout timeouts} to work

See the whole method description: https://github.com/failsafe-lib/failsafe/blob/master/core/src/main/java/dev/failsafe/FailsafeExecutor.java#L324

I've checked Timeout logic with single thread ExecutorService and it seems to be working - probably I've missed something in my check.
The question is - do we want to add validation for the ExecutorService parameter and throw an exception if parallelism is 1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is not considered as a configuration error, a warning message should be sufficient, I'd say.

Copy link
Contributor Author

@Semernitskaya Semernitskaya Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: I've added a warning when it is possible to check corePoolSize i.e. when executorService instanceof ThreadPoolExecutor

@@ -606,10 +606,17 @@ The following table shows all beans with their respective name (for the `example
| `exampleFaultClassifier` | `FaultClassifier` |
| `exampleCircuitBreakerListener` | `CircuitBreakerListener` |
| `exampleAuthorizationProvider` | `AuthorizationProvider` |
| `exampleRetryPolicyExecutorService` | `ExecutorService` |
Copy link
Contributor Author

@Semernitskaya Semernitskaya Sep 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add these beans to the beans diagram as well https://github.com/zalando/riptide/pull/1503/files#diff-cac26b0853dfe7f7df5898467d9a7d21fe3c0d656f8debcaedab57f4c6d20f39L567 - do we have an editable version of the diagram?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume, you are also using IntelliJ… Can you use its feature to align the table so it is properly aligned?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

.build()))
.build();
.build())
.withExecutor(Executors.newFixedThreadPool(2)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I read the description of this section, I am wondering why I need to specify the withExececutor part. At least my 🧠 does not connect the content of it mentioning retries and circuit breakers with anything executor related.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree - deleted executor part from this example, it will be shown only in the Custom executor section

@@ -57,10 +64,14 @@ public RequestExecution aroundAsync(final RequestExecution execution) {

if (policies.isEmpty()) {
return execution.execute(arguments);
} else if (executorService != null) {
return Failsafe.with(select(arguments))
.with(executorService)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is not considered as a configuration error, a warning message should be sufficient, I'd say.

import okhttp3.mockwebserver.MockWebServer;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.jetbrains.annotations.NotNull;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be from javax.annotation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Slf4j
final class FailsafePluginCustomExecutorTest {

public static class CountingExecutorService extends ThreadPoolExecutor {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making it private to avoid any other test class from using it?!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@@ -606,10 +606,17 @@ The following table shows all beans with their respective name (for the `example
| `exampleFaultClassifier` | `FaultClassifier` |
| `exampleCircuitBreakerListener` | `CircuitBreakerListener` |
| `exampleAuthorizationProvider` | `AuthorizationProvider` |
| `exampleRetryPolicyExecutorService` | `ExecutorService` |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume, you are also using IntelliJ… Can you use its feature to align the table so it is properly aligned?

.setFactoryMethod("createCircuitBreakerPlugin")
.addConstructorArgValue(registerCircuitBreaker(id, client))
.addConstructorArgValue(createTaskDecorators(id, client));
return executorService.map(executorServiceBeanName -> genericBeanDefinition(FailsafePluginFactory.class)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that registry.find gives you an optional, can't you just use map(name -> ref(name)).orElse(null) and then just pass this to the last addConstructorArg call given that you nevertheless allow to call it with null? Then you only need to call the genericBeanDefiniton once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 🙂, fixed this as well

@cberg-zalando
Copy link
Member

cberg-zalando commented Sep 19, 2023

To avoid these merges as in b3e9ebb due to different commits on local and remote side, you can next time use git fetch && git rebase --onto=origin/Semernitskaya:specify-failsafe-executor.

@@ -113,6 +113,26 @@ Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();
```

### Custom executor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When looking at the preview of the README file, I noticed that we still link to the failsafe-actuator project. Given that it was archived in December of last year, I don't know if we should keep this or not. @fatroom Any opinion?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit broad question, I'm considering either merge that project, or take over maintenance of it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we had the same issue, @nantipov fixed it by copying the class into our guniea pig project and do the necessary adjustments for Spring Boot 3.

@Semernitskaya
Copy link
Contributor Author

Semernitskaya commented Sep 20, 2023

@fatroom , @cberg-zalando
I also started to think that providing custom ExecutorService can be dangerous in some cases (e.g. user specified single thread executor and this affects performance, or user shared the same ExecutorService between several Plugins or Http clients) - should we write some warning in the documentation or we expect that if users do such customization - they know what they are doing

@cberg-zalando
Copy link
Member

👍

@cberg-zalando
Copy link
Member

@fatroom , @cberg-zalando I also started to think that providing custom ExecutorService can be dangerous in some cases (e.g. user specified single thread executor and this affects performance, or user shared the same ExecutorService between several Plugins or Http clients) - should we write some warning in the documentation or we expect that if users do such customization - they know what they are doing

I would assume the classical With great power comes great responsibility attitude…

@fatroom
Copy link
Member

fatroom commented Sep 25, 2023

@Semernitskaya

I also started to think that providing custom ExecutorService can be dangerous in some cases (e.g. user specified single thread executor and this affects performance, or user shared the same ExecutorService between several Plugins or Http clients) - should we write some warning in the documentation or we expect that if users do such customization - they know what they are doing

Generally riptide already declaring that it's providing a 'sane' defaults, but maybe worth to put a warning related to this case (warn agains impact of single thread pool)

@@ -133,6 +133,9 @@ Http.builder().requestFactory(new HttpComponentsClientHttpRequestFactory())
If no executor is specified, the default executor configured by `Failsafe` is used. See [Failsafe DelegatingScheduler class](https://github.com/failsafe-lib/failsafe/blob/master/core/src/main/java/dev/failsafe/internal/util/DelegatingScheduler.java#L111),
and also [Failsafe documentation](https://failsafe.dev/async-execution/#executorservice-configuration) for more information.

**Beware** when specifying a custom `ExecutorService` - the `ExecutorService` should have a core pool size or parallelism
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't @fatroom also mention something about mentioning to not use the same ExecutorService instance for everything?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see it, but I can add this recommendation as well

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To quote from @fatroom user shared the same ExecutorService between several Plugins or Http clients.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But maybe he meant something different here…

@cberg-zalando
Copy link
Member

👍

@fatroom
Copy link
Member

fatroom commented Oct 5, 2023

Thank you for the contribution!

@fatroom fatroom merged commit 89ce17e into zalando:main Oct 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to specify failsafe executor
3 participants