So far in this series I wrote about PRs that got merged. This post is about one that did not, and why I think it taught me more than some of the merged ones.

The issue

Riptide is Zalando's HTTP client library for Java. Issue #1859 described a real problem someone had hit in production.

Riptide created a separate Failsafe instance for every policy (Retry, Timeout and so on). Failsafe is a library for resilience policies. The problem is that when policies are chained inside one Failsafe instance, Failsafe has built-in protection against deadlocks between combinations like Timeout and Retry. When each policy runs in its own instance, that protection is bypassed.

The reporter included a thread dump from their service. Two threads were each waiting for a lock the other one held, and the shared thread pool got blocked. A resilience feature ended up hurting the service.

The expected fix: compose all policies into one chain in one Failsafe instance, like this:

// One Failsafe instance with both policies chained together, // so Failsafe's own deadlock protection applies. Failsafe.with(timeout, retryPolicy) .getStageAsync(context -> fetchData()); 

What I did

I read the Riptide code, the Failsafe source and the thread dump, worked out how the policies should be composed, and opened PR #1871 with my analysis.

What happened

The maintainer replied that the Zalando team already had a fix in progress. While building it, they had found a few related changes needed in the framework itself, and they were validating them, especially around Spring Boot starter compatibility. They asked me not to run two parallel fixes for the same issue, and said they would open their PR once validation was done.

My honest first reaction: a little disappointed. I had put real time into it.

My second thought was clearer: the maintainers are right. Two PRs for the same bug only create extra work for the reviewers. So I closed mine.

I also left a note offering to look at their PR when it was up, and shared the order I had used for the policies. Later the maintainer opened #1872, explained why the Timeout sits on the outside (so that retries do not reset it and the whole execution is time-boxed), and thanked me for the analysis. I closed my PR in favor of theirs. Another maintainer also wrote that Riptide is open to contributions from the community.

What I learned

  1. Ask if someone is already working on it. A comment like "is anyone on this?" before writing code would have saved us both time. I do this first now.
  2. A closed PR is not wasted work. I now understand how Failsafe composes policies and where its deadlock guards live. That knowledge stays with me.
  3. Learn the reason behind a design, not only the code. The maintainer's explanation of why Timeout wraps everything was something I could not have worked out from the code alone.
  4. Close it gracefully. The conversation ended friendly, and I left with a better relationship with the maintainers than I had before.

Proof of my work

Everything here is public, so you can check it yourself:

Next post: how I choose which issue to work on, so I waste less time than I did with this one.

If you are a maintainer, a recruiter or a team looking for someone who learns in public and handles good and bad outcomes the same way, my DMs are open. Tell me in the comments about a PR of yours that did not go through.