Skip to content

feat: add Timeout pattern (#2845) - #3598

Open
ylcn91 wants to merge 2 commits into
iluwatar:masterfrom
ylcn91:feat/timeout
Open

feat: add Timeout pattern (#2845)#3598
ylcn91 wants to merge 2 commits into
iluwatar:masterfrom
ylcn91:feat/timeout

Conversation

@ylcn91

@ylcn91 ylcn91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds the Timeout pattern as a new timeout module.

  • Problem: without a time limit a single slow dependency can hold threads and user requests hostage until the whole system stalls.
  • Solution: every downstream call runs under a per-service TimeoutPolicy. When the limit is exceeded the call is cancelled with an interrupt, the event is logged and counted, and a fallback answer is returned.
  • Key components:
    • TimeoutPolicy (record) and TimeoutRegistry: per-service configurable limits with a default.
    • TimeoutExecutor: enforces the limit (Future.get(timeout)), cancels the overrunning call, records the event in TimeoutMetrics, invokes the fallback. Service failures are surfaced as ServiceCallException, not as timeouts.
    • ProductCatalogService (fast) and RecommendationService (slow, interruptible): simulated dependencies.
    • App: catalog answers within its 500 ms limit; recommendations exceed their 100 ms limit, get cancelled and replaced by popular items; timeout counters are printed. Logging traces every step.
    • README.md: intent, real-world example, sequence diagram, code walkthrough, applicability, trade-offs, related patterns (including how this differs from the existing fallback module, where the time limit is only one of several triggers). PlantUML class diagram under etc/.
  • Tests: 13 JUnit 5 tests covering result within limit, fallback plus cancellation on overrun (interruption verified), failure propagation, per-service limits and counters, registry and policy validation, plus AppTest.
  • Module registered in the parent pom.xml. ./mvnw clean verify -pl timeout passes locally on JDK 21 and inside an eclipse-temurin:21 container.

Fixes #2845

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

PR Summary

Integrated the Timeout pattern as a new module, introducing per-service timeouts, cancellation, and fallback handling to prevent slow dependencies from stalling the system. The module includes core components (TimeoutPolicy, TimeoutExecutor, TimeoutMetrics), a simulated DownstreamService and App wiring, plus comprehensive tests and documentation with a PlantUML diagram.

Changes

File Summary
pom.xml Updated the parent POM to register a new timeout module; includes module entry for timeout.
timeout/README.md Introduces the Timeout pattern with per-service TimeoutPolicy, TimeoutExecutor, and TimeoutMetrics; includes usage, concepts, and examples.
timeout/etc/timeout.urm.png PlantUML class diagram image for the Timeout module.
timeout/etc/timeout.urm.puml PlantUML diagram describing TimeoutPolicy, TimeoutExecutor, TimeoutMetrics, DownstreamService, App; used for docs.
timeout/pom.xml Module pom for the timeout pattern; declares dependencies, test setup, and main class for assembly.
timeout/src/main/java/com/iluwatar/timeout/App.java Demo App wiring two DownstreamService instances with separate TimeoutPolicy limits; demonstrates fallback usage and timeout metrics.
timeout/src/main/java/com/iluwatar/timeout/DownstreamService.java Simulated downstream service with interruptible latency; returns payload or throws on interruption.
timeout/src/main/java/com/iluwatar/timeout/ServiceCallException.java Custom runtime exception for non-timeout service failures surfaced by TimeoutExecutor.
timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java Core executor enforcing per-service timeouts, cancelling overrunning calls, logging, updating metrics, and combining with fallbacks.
timeout/src/main/java/com/iluwatar/timeout/TimeoutMetrics.java Thread-safe metrics tracking per-service timeouts with snapshot view.
timeout/src/main/java/com/iluwatar/timeout/TimeoutPolicy.java Immutable per-service policy validating service name and positive timeout; factory for milliseconds.
timeout/src/test/java/com/iluwatar/timeout/AppTest.java Tests for app-level behavior including successful responses and timeout-backed fallbacks.
timeout/src/test/java/com/iluwatar/timeout/DownstreamServiceTest.java Tests for DownstreamService basic behavior and interruption handling.
timeout/src/test/java/com/iluwatar/timeout/TimeoutExecutorTest.java Tests for TimeoutExecutor: success, timeout cancellation, failure propagation, per-service limits, and close behavior.
timeout/src/test/java/com/iluwatar/timeout/TimeoutPolicyTest.java Tests for TimeoutPolicy constructor and input validation.

autogenerated by presubmit.ai

@ylcn91 ylcn91 mentioned this pull request Sep 3, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 Pull request needs attention.

Review Summary

Commits Considered (1)
Files Processed (16)
  • pom.xml (1 hunk)
  • timeout/README.md (1 hunk)
  • timeout/etc/timeout.urm.puml (1 hunk)
  • timeout/pom.xml (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/App.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/ServiceCallException.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutMetrics.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutPolicy.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutRegistry.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/AppTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutExecutorTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutPolicyTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutRegistryTest.java (1 hunk)
Actionable Comments (7)
  • timeout/src/main/java/com/iluwatar/timeout/App.java [61-61]

    possible bug: "Logger name should match Lombok's @slf4j field"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [67-67]

    readability: "Use the Lombok 'log' logger"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [73-73]

    readability: "Use the Lombok 'log' logger"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [81-81]

    readability: "Use the Lombok 'log' logger for final status"

  • timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java [59-59]

    possible bug: "Logger field name mismatch in ProductCatalogService"

  • timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java [60-66]

    possible bug: "Logger field name mismatch in RecommendationService"

  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java [82-82]

    possible bug: "Logger name mismatch in TimeoutExecutor"

Skipped Comments (1)
  • timeout/pom.xml [46-50]

    enhancement: "Add junit-jupiter-api for tests"

Comment thread timeout/src/main/java/com/iluwatar/timeout/App.java
Comment thread timeout/src/main/java/com/iluwatar/timeout/App.java Outdated
Comment thread timeout/src/main/java/com/iluwatar/timeout/App.java Outdated
Comment thread timeout/src/main/java/com/iluwatar/timeout/App.java
Comment thread timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java Outdated
Comment thread timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java Outdated
Comment thread timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java
@ylcn91

ylcn91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Note on the automated review comments: LOGGER is the Lombok logger field name configured for this repository in lombok.config (lombok.log.fieldName = LOGGER), the same name every other module uses, so the code compiles as is. Local ./mvnw clean verify -pl timeout passes on JDK 21, also inside an eclipse-temurin:21 container.

@codecov

codecov Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.87%. Comparing base (41625d8) to head (2458cf8).

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3598      +/-   ##
============================================
+ Coverage     83.79%   83.87%   +0.07%     
- Complexity     4277     4302      +25     
============================================
  Files          1121     1127       +6     
  Lines         15144    15230      +86     
  Branches        723      726       +3     
============================================
+ Hits          12690    12774      +84     
- Misses         2159     2161       +2     
  Partials        295      295              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ylcn91

ylcn91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the Codecov note: added a test for an interrupted caller in TimeoutExecutorTest and a new RecommendationServiceTest (normal reply and interrupted computation). 16 tests, no uncovered lines outside App. Tests only, no production change. ./mvnw clean verify -pl timeout passes locally on JDK 21 and in an eclipse-temurin:21 container.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 Pull request needs attention.

Review Summary

Commits Considered (1)
Files Processed (17)
  • pom.xml (1 hunk)
  • timeout/README.md (1 hunk)
  • timeout/etc/timeout.urm.puml (1 hunk)
  • timeout/pom.xml (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/App.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/ServiceCallException.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutMetrics.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutPolicy.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutRegistry.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/AppTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/RecommendationServiceTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutExecutorTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutPolicyTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutRegistryTest.java (1 hunk)
Actionable Comments (3)
  • timeout/src/main/java/com/iluwatar/timeout/App.java [61-61]

    readability: "Replace Lombok-generated logger usage with 'log'."

  • timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java [59-59]

    readability: "Use Lombok 'log' instead of 'LOGGER' in logging."

  • timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java [60-64]

    readability: "Replace Lombok-generated logger usage with 'log' in multi-line logging."

Skipped Comments (0)

Comment thread timeout/src/main/java/com/iluwatar/timeout/App.java
Comment thread timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java Outdated
Comment thread timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java Outdated
@ylcn91

ylcn91 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Coverage follow-up: extracted the two demo calls in App into package-private helpers (loadProducts, loadRecommendations) so both the normal and the fallback paths are exercised by AppTest, and added shouldBeInstantiable. Demo output unchanged. JaCoCo now reports 100% instruction, branch and line coverage. Verified locally on JDK 21 and in an eclipse-temurin:21 container; the packaged jar runs end to end.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

Review Summary

Commits Considered (1)
Files Processed (17)
  • pom.xml (1 hunk)
  • timeout/README.md (1 hunk)
  • timeout/etc/timeout.urm.puml (1 hunk)
  • timeout/pom.xml (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/App.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/ServiceCallException.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutMetrics.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutPolicy.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutRegistry.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/AppTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/RecommendationServiceTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutExecutorTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutPolicyTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutRegistryTest.java (1 hunk)
Actionable Comments (0)
Skipped Comments (8)
  • timeout/src/main/java/com/iluwatar/timeout/App.java [61-61]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [67-67]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [73-73]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [75-75]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/ProductCatalogService.java [59-59]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/RecommendationService.java [60-66]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java [82-82]

    best_practice: "Use Lombok's generated logger"

  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java [88-90]

    best_practice: "Use Lombok's generated logger"

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

Review Summary

Commits Considered (3)
  • afd73f2: docs: embed the rendered class diagram in the timeout README

Render etc/timeout.urm.puml to PNG and embed it in the detailed explanation section, matching the other modules, instead of the inline mermaid block.

  • 4d79669: refactor: drop timeout registry and merge the demo services

TimeoutRegistry was configuration scaffolding rather than part of the
pattern, so App now builds its TimeoutPolicy values directly.

ProductCatalogService and RecommendationService only differed in name,
latency and payload, so they collapse into a single parameterised
DownstreamService and App's two near-identical helpers into one call
helper.

README, PlantUML diagram and tests follow; the sample output block is
taken from a real run of the demo.

  • 9124ada: fix: keep metrics snapshot sorted and translate rejected submissions

  • TimeoutMetrics.snapshot() returns an unmodifiable SortedMap so the sorted view promised by its javadoc survives, covered by a new ordering test

  • TimeoutExecutor submits inside the try and wraps RejectedExecutionException in ServiceCallException, matching its @throws contract

  • timeout.urm.puml: mark TimeoutPolicy as a record, drop the synthetic App constructor, show ServiceCallException extending RuntimeException, add the App to TimeoutPolicy and TimeoutMetrics dependencies

  • README: inline mermaid class diagram instead of the raw .puml link, and note that the timeout warning and the worker interrupt lines may interleave

Files Processed (10)
  • timeout/README.md (1 hunk)
  • timeout/etc/timeout.urm.png (0 hunks)
  • timeout/etc/timeout.urm.puml (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/App.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/DownstreamService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutMetrics.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/AppTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/DownstreamServiceTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutExecutorTest.java (1 hunk)
Actionable Comments (0)
Skipped Comments (10)
  • timeout/src/main/java/com/iluwatar/timeout/App.java [66-66]

    best_practice: "Use Lombok's log field instead of LOGGER"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [69-69]

    best_practice: "Replace LOGGER with log for per-call logging"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [71-71]

    best_practice: "Replace LOGGER with log for products logging"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [73-73]

    best_practice: "Replace LOGGER with log for recommendations logging"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [75-75]

    best_practice: "Replace LOGGER with log for final recommendations payload"

  • timeout/src/main/java/com/iluwatar/timeout/App.java [77-77]

    best_practice: "Replace LOGGER with log for timeout metrics"

  • timeout/src/main/java/com/iluwatar/timeout/DownstreamService.java [74-74]

    best_practice: "Use Lombok's log instead of LOGGER in DownstreamService"

  • timeout/src/main/java/com/iluwatar/timeout/DownstreamService.java [78-78]

    best_practice: "Use Lombok's log in DownstreamService on interruption"

  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java [86-86]

    best_practice: "Use Lombok's log for successful completion logging"

  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java [92-92]

    best_practice: "Replace timeout warning log with log"

The metrics snapshot stays sorted, a rejected submission surfaces as ServiceCallException, TimeoutRegistry is dropped and the two demo services merge into DownstreamService. The class diagram is a rendered PNG.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

Review Summary

Commits Considered (1)
  • 2458cf8: refactor: simplify the timeout demo and address review findings

The metrics snapshot stays sorted, a rejected submission surfaces as ServiceCallException, TimeoutRegistry is dropped and the two demo services merge into DownstreamService. The class diagram is a rendered PNG.

Files Processed (10)
  • timeout/README.md (1 hunk)
  • timeout/etc/timeout.urm.png (0 hunks)
  • timeout/etc/timeout.urm.puml (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/App.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/DownstreamService.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java (1 hunk)
  • timeout/src/main/java/com/iluwatar/timeout/TimeoutMetrics.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/AppTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/DownstreamServiceTest.java (1 hunk)
  • timeout/src/test/java/com/iluwatar/timeout/TimeoutExecutorTest.java (1 hunk)
Actionable Comments (0)
Skipped Comments (3)
  • timeout/src/main/java/com/iluwatar/timeout/App.java [66-77]

    readability: "Use the Lombok-provided logger instead of LOGGER"

  • timeout/src/main/java/com/iluwatar/timeout/DownstreamService.java [73-82]

    readability: "Logger usage mismatch in DownstreamService"

  • timeout/src/main/java/com/iluwatar/timeout/TimeoutExecutor.java [86-93]

    readability: "Logger usage mismatch in TimeoutExecutor"

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.

Timeout pattern

1 participant