question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RetryPolicy thread safety

See original GitHub issue

Is it safe to use RetryPolicy from multiple threads?

I can see it doesn’t set it’s own members but it does add members to (array)lists of predicates. This is potentially not thread-safe and can break when using the same RetryPolicy object from multiple threads.

Is there a standard to do this? I can copy the object for now when I modify it on specific threads, but perhaps making it thread safe is possible on your end.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Reactions:1
  • Comments:49 (17 by maintainers)

github_iconTop GitHub Comments

6reactions
Tembrelcommented, Jul 9, 2020

I’m sorry I didn’t notice that I was tagged back in October.

Initializing both condition lists with CopyOnWriteArrayList would avoid the CME between RetryPolicy#retryWhen and RetryPolicy#canRetryFor and similar situations, but there are other safety problems with sharing instances between threads: The double fields are vulnerable to out-of-thin-air values and the visibility of other fields is not guaranteed. Making all fields volatile (except the condition lists, which should be final) would provide visibility guarantees, but even then the non-atomicity of methods like withBackoff that modify several fields would still leave RetryPolicy fundamentally thread-unsafe.

You could slap the @ThreadUnsafe annotation on the class, but documenting safe uses of a thread-unsafe class is tricky. The only safe way to share is to copy before sharing to a different thread:

    RetryPolicy orig = ...;
    RetryPolicy copy = orig.copy();
    // ok to use orig and copy here (see correction note below) [1]
    CompletableFuture<Result> f = CompletableFuture.supplyAsync(() -> {
        // all use of copy should be in here, guarded by
        // implicit happens-before edge
    });
    // don't use copy here, ok to use orig [2]
    Result result = f.join();
    // safe to use copy here: happens-before edge due to join()

No one should have to perform this kind of delicate reasoning when using a class, so I think documented thread-unsafety is not a good answer.

The options, then, are either to make the class thread-safe mutable or to make it immutable, as @akincel noted. Unless someone can give compelling use cases for mutability in response to @jhalterman’s request, immutability is clearly preferable. The bar is high: To anyone thinking of presenting such a use case, I would ask whether they wouldn’t be able to make do with a MutableRetryPolicyHolder class with a mutable reference to an (immutable) RetryPolicy.

I don’t think the cost of creating a new RetryPolicy instance for each “mutable” method call is particularly high in practice. It’s hard to imagine uses that involve creating instances in a tight inner loop, because the RetryPolicy is designed to be applied to repeatable tasks that typically take some time.

Bottom line: @Immutable is the way to go.

Very late correction (2020-July-9)

I got this one (the line marked [1]) wrong initially: There is a happens-before edge between actions taking place before submission of a task (which is what supplyAsync does) and the execution of that task. It’s still the case that you can’t use copy at [2].

The fact that I got this wrong just goes to show how tricky the reasoning around thread-unsafe objects can be.

4reactions
jhaltermancommented, Aug 9, 2016

@reutsharabani From your scenario, it sounds like you need different RetryPolicy instances for each service no matter what since their retry patterns will vary. These can certainly be copied from a common base policy via copy(), but it’s not clear that thread-safety will solve anything in the scenario you described.

Thread-safety would only be an issue if you were configuring a single RetryPolicy from multiple threads concurrently, or configuring a RetryPolicy while using it via a Failsafe call. Is there a scenario where you need to do something like this?

@sunng87 RetryPolicy is certainly reusable and shareable. Failsafe will never modify a RetryPolicy, only read them. That said, since RetryPolicy is currently not thread-safe, unexpected behavior could result if reading/writing to it concurrently.

Read more comments on GitHub >

github_iconTop Results From Across the Web

RetryPolicy thread safety · Issue #47 · failsafe-lib ... - GitHub
Hey, I've just noticed that RetryPolicy is not thread safe as of 2.4.0. One example: delay field is not volatile, nor final, there...
Read more >
Frequently Asked Questions - Failsafe
Failsafe requires the use of separate threads for a few different situations: ... When a RetryPolicy is exceeded, the last execution result or...
Read more >
RetryPolicy(T) Class (Microsoft.WindowsAzure.Common ...
An instance of a callback delegate that will be invoked whenever a retry condition is encountered.(Inherited from RetryPolicy.) Thread Safety.
Read more >
How to make manual retry thread safe - java - Stack Overflow
I am trying to do a manual retry. But I feel the code is not thread safe. Can anyone please ...
Read more >
2 Things I learnt By Reading Failsafe Source code
Users want to reuse RetryPolicy object but because it is not immutable they fear thread safety concerns. Library author clarified that ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found