feat(async-jobs): add deferred retry with cooldown for failed async jobs#1016
Conversation
Add failed_retry_wait_time_in_seconds parameter to AsyncRetriever and AsyncJobOrchestrator. When configured, FAILED jobs are not retried immediately but deferred until a cooldown period elapses. This is non-blocking: the orchestrator skips deferred jobs and continues processing other partitions normally. Changes: - AsyncJob: add set_retry_after(), retry_deferred(), ready_to_retry() - AsyncJobOrchestrator: defer FAILED job retries when wait time is set - Schema/models: add failed_retry_wait_time_in_seconds to AsyncRetriever - Factory: wire parameter through to orchestrator - Tests: add parametrized tests for retry_after and orchestrator behavior Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This CDK VersionYou can test this version of the CDK using the following: # Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@devin/1778159323-failed-retry-wait-time#egg=airbyte-python-cdk[dev]' --help
# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch devin/1778159323-failed-retry-wait-timePR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
PyTest Results (Fast)4 059 tests +8 4 048 ✅ +8 6m 34s ⏱️ -46s Results for commit ec1a0f0. ± Comparison against base commit 9fcc6de. This pull request removes 3 and adds 11 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
PyTest Results (Full)4 062 tests +8 4 050 ✅ +8 10m 56s ⏱️ -35s Results for commit ec1a0f0. ± Comparison against base commit 9fcc6de. This pull request removes 3 and adds 11 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds configurable cooldown-based deferred retries for FAILED async jobs: AsyncJob tracks a retry-after timestamp and creation-failure flag, orchestrator gates replacements using failed_retry_wait_time_in_seconds, configuration is wired through the factory/schema, and tests cover behaviors. ChangesAsync Job Retry Deferral
Sequence Diagram(s)sequenceDiagram
participant Client
participant AsyncJobOrchestrator
participant AsyncHttpJobRepository
participant AsyncJob
participant Clock as UTC
Client->>AsyncJobOrchestrator: trigger job handling
AsyncJobOrchestrator->>AsyncHttpJobRepository: fetch job(s)
AsyncJobOrchestrator->>AsyncJob: check status
AsyncJobOrchestrator->>AsyncJob: ready_to_retry()?
alt Not ready
AsyncJob-->>AsyncJobOrchestrator: false
AsyncJobOrchestrator-->>AsyncJob: skip replacement
else Ready
AsyncJob-->>AsyncJobOrchestrator: true
AsyncJobOrchestrator->>AsyncJob: retry_deferred()?
alt Not deferred
AsyncJob-->>AsyncJobOrchestrator: false
AsyncJobOrchestrator->>Clock: now + wait_seconds
Clock-->>AsyncJobOrchestrator: retry_after timestamp
AsyncJobOrchestrator->>AsyncJob: set_retry_after(retry_after)
AsyncJobOrchestrator-->>AsyncJob: defer replacement
else Deferred
AsyncJob-->>AsyncJobOrchestrator: true
AsyncJobOrchestrator->>AsyncHttpJobRepository: start replacement
AsyncHttpJobRepository-->>AsyncJobOrchestrator: new job (may be FAILED/creation-failure)
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Would you like me to flag specific places to tighten timezone handling or add more unit coverage around edge cases, wdyt? 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
airbyte_cdk/sources/declarative/async_job/job_orchestrator.py (1)
171-193:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winCould we reject negative retry cooldown values at construction time, wdyt?
Right now, a negative value can produce a
retry_afterin the past, which makes the deferral semantics confusing instead of explicit.Proposed fix
def __init__( self, job_repository: AsyncJobRepository, slices: Iterable[StreamSlice], job_tracker: JobTracker, message_repository: MessageRepository, exceptions_to_break_on: Iterable[Type[Exception]] = tuple(), has_bulk_parent: bool = False, job_max_retry: Optional[int] = None, failed_retry_wait_time_in_seconds: Optional[int] = None, ) -> None: + if ( + failed_retry_wait_time_in_seconds is not None + and failed_retry_wait_time_in_seconds < 0 + ): + raise ValueError( + "failed_retry_wait_time_in_seconds must be >= 0" + ) """🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py` around lines 171 - 193, Reject negative failed_retry_wait_time_in_seconds in the constructor by validating the parameter and raising a ValueError if it's < 0; add a check after parameters are received (near AsyncJobStatus/_KNOWN_JOB_STATUSES validation) to assert failed_retry_wait_time_in_seconds is either None or >= 0 and include a clear error message, then assign it to self._failed_retry_wait_time_in_seconds as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@airbyte_cdk/sources/declarative/async_job/job.py`:
- Around line 58-70: The set_retry_after method should guard against naive
datetimes and store a normalized UTC-aware datetime in self._retry_after: if
retry_after.tzinfo is None (naive) attach timezone.utc (e.g. retry_after =
retry_after.replace(tzinfo=timezone.utc)), then normalize any timezone-aware
value to UTC via retry_after = retry_after.astimezone(timezone.utc) before
assigning to self._retry_after; keep ready_to_retry using
datetime.now(tz=timezone.utc) and the _retry_after field for comparisons so no
TypeError occurs.
In `@airbyte_cdk/sources/declarative/declarative_component_schema.yaml`:
- Around line 4105-4111: Add a minimum bound to the integer branch of the
failed_retry_wait_time_in_seconds schema so negative or zero values are rejected
by the schema; specifically, in the failed_retry_wait_time_in_seconds anyOf
entry for type: integer add minimum: 1 (mirroring the pattern used by
DynamicStreamCheckConfig.stream_count) to ensure the orchestrator's
ready_to_retry() logic isn't fed non-positive values while keeping the existing
anyOf string branch and interpolation_context unchanged.
In `@airbyte_cdk/sources/declarative/models/declarative_component_schema.py`:
- Around line 3077-3080: The field failed_retry_wait_time_in_seconds currently
allows negative values; add validation to prevent negatives by adding ge=0 to
its Pydantic Field call in the declarative component model (update the
Field(...) for failed_retry_wait_time_in_seconds to include ge=0) and also add
minimum: 0 to the corresponding YAML schema definition for the same field so
generated models and schema both enforce a non-negative cooldown.
In `@unit_tests/sources/declarative/async_job/test_job.py`:
- Around line 30-55: The parametrized datetimes are computed at import time
which makes the test time-sensitive; change the test to compute retry_after
values at runtime inside test_retry_after instead of using datetime.now(...) in
the param list: keep the same parameter cases (None, future, past) but pass a
marker or enum in the parametrize tuple and in test_retry_after compute
datetime.now(timezone.utc) + timedelta(hours=1) or - timedelta(seconds=1) as
needed, then call AsyncJob.set_retry_after(retry_after) and assert
job.retry_deferred() and job.ready_to_retry() as before (refer to
test_retry_after, AsyncJob.set_retry_after, job.retry_deferred,
job.ready_to_retry).
---
Outside diff comments:
In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py`:
- Around line 171-193: Reject negative failed_retry_wait_time_in_seconds in the
constructor by validating the parameter and raising a ValueError if it's < 0;
add a check after parameters are received (near
AsyncJobStatus/_KNOWN_JOB_STATUSES validation) to assert
failed_retry_wait_time_in_seconds is either None or >= 0 and include a clear
error message, then assign it to self._failed_retry_wait_time_in_seconds as
before.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 708f4de0-2b2c-45d9-9671-f27afea41ede
📒 Files selected for processing (7)
airbyte_cdk/sources/declarative/async_job/job.pyairbyte_cdk/sources/declarative/async_job/job_orchestrator.pyairbyte_cdk/sources/declarative/declarative_component_schema.yamlairbyte_cdk/sources/declarative/models/declarative_component_schema.pyairbyte_cdk/sources/declarative/parsers/model_to_component_factory.pyunit_tests/sources/declarative/async_job/test_job.pyunit_tests/sources/declarative/async_job/test_job_orchestrator.py
…runtime offsets Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
|
A couple of concerns from review: 1. Double cooldown via synthetic FAILED jobs
For the motivating SP-API case, hitting
Either way, worth a regression test that simulates 2. Three different lower bounds for
|
…dation - When _start_job returns a synthetic FAILED job (API still in cooldown), carry _retry_after from the old job to prevent double-cooldown - Change constructor validation from < 0 to <= 0 to match YAML schema minimum: 1 constraint - Add regression test for synthetic FAILED replacement scenario Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
|
Both concerns addressed in 9ddf6ca: 1. Double cooldown via synthetic FAILED jobsFixed by carrying new_job = self._start_job(job.job_parameters(), job.api_job_id())
if (
self._failed_retry_wait_time_in_seconds
and new_job.status() == AsyncJobStatus.FAILED
and job.retry_deferred()
):
new_job.set_retry_after(job._retry_after)
partition.replace_job(job, [new_job])This ensures the total wall-clock wait equals one cooldown, not two. Added a regression test ( 2. Consistent lower boundConstructor validation now rejects if failed_retry_wait_time_in_seconds is not None and failed_retry_wait_time_in_seconds <= 0:
raise ValueError("failed_retry_wait_time_in_seconds must be >= 1")The three layers are now consistent:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
airbyte_cdk/sources/declarative/async_job/job_orchestrator.py (1)
204-223: ⚡ Quick winCould we flip the cooldown guard order to reduce hidden coupling, wdyt?
At Line 205, readiness is checked before we know whether cooldown was ever armed. Would you consider arming on first FAILED (
not retry_deferred) first, then checking readiness, so this path doesn’t depend on implicitready_to_retry()semantics?Proposed refactor
- if self._failed_retry_wait_time_in_seconds and job.status() == AsyncJobStatus.FAILED: - if not job.ready_to_retry(): - lazy_log( - LOGGER, - logging.DEBUG, - lambda: f"Job {job.api_job_id()} is not ready to retry yet (deferred). Skipping.", - ) - continue - if not job.retry_deferred(): + if self._failed_retry_wait_time_in_seconds and job.status() == AsyncJobStatus.FAILED: + if not job.retry_deferred(): job.set_retry_after( datetime.now(tz=timezone.utc) + timedelta(seconds=self._failed_retry_wait_time_in_seconds) @@ ) continue + if not job.ready_to_retry(): + lazy_log( + LOGGER, + logging.DEBUG, + lambda: f"Job {job.api_job_id()} is not ready to retry yet (deferred). Skipping.", + ) + continue🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py` around lines 204 - 223, Flip the cooldown guard order so the retry cooldown is explicitly armed on the first FAILED occurrence before relying on ready_to_retry(); inside the loop handling AsyncJobStatus.FAILED (use symbols _failed_retry_wait_time_in_seconds, job.retry_deferred(), job.set_retry_after(...), job.ready_to_retry(), job.api_job_id(), _start_job), first check if not job.retry_deferred() and then call job.set_retry_after(datetime.now(tz=timezone.utc) + timedelta(seconds=self._failed_retry_wait_time_in_seconds)) and log the deferral and continue; otherwise (if retry_deferred) then check job.ready_to_retry() and skip/continue if not ready, only proceed to call self._start_job(job.job_parameters(), job.api_job_id()) when readiness passes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py`:
- Around line 204-223: Flip the cooldown guard order so the retry cooldown is
explicitly armed on the first FAILED occurrence before relying on
ready_to_retry(); inside the loop handling AsyncJobStatus.FAILED (use symbols
_failed_retry_wait_time_in_seconds, job.retry_deferred(),
job.set_retry_after(...), job.ready_to_retry(), job.api_job_id(), _start_job),
first check if not job.retry_deferred() and then call
job.set_retry_after(datetime.now(tz=timezone.utc) +
timedelta(seconds=self._failed_retry_wait_time_in_seconds)) and log the deferral
and continue; otherwise (if retry_deferred) then check job.ready_to_retry() and
skip/continue if not ready, only proceed to call
self._start_job(job.job_parameters(), job.api_job_id()) when readiness passes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 242307c0-269e-4c29-aba3-70f104ce272f
📒 Files selected for processing (2)
airbyte_cdk/sources/declarative/async_job/job_orchestrator.pyunit_tests/sources/declarative/async_job/test_job_orchestrator.py
Synthetic FAILED jobs (created when API rejects with 429/budget-exceeded) are now marked with _is_synthetic=True and bypass the cooldown deferral. Only real FAILED jobs (report created but got FATAL from API) are deferred. This removes the _retry_after carry-over logic since synthetic jobs are naturally identified and retried immediately. Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
|
Addressed in 787db4b — synthetic vs real FAILED jobs are now distinguished: What changed:
Removed: The Test added: |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
airbyte_cdk/sources/declarative/async_job/job.py (1)
28-28: ⚡ Quick winConsider adding type annotation for consistency?
_is_syntheticis missing a type annotation while_retry_afteron line 27 has one. Adding: boolwould keep the typing consistent across the new fields, wdyt?✨ Proposed fix
- self._is_synthetic = False + self._is_synthetic: bool = False🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@airbyte_cdk/sources/declarative/async_job/job.py` at line 28, The new attribute _is_synthetic lacks a type annotation whereas nearby fields like _retry_after are typed; add an explicit boolean type to _is_synthetic (i.e., declare _is_synthetic: bool) in the same class where _retry_after is defined (look for the class containing _retry_after and the line setting self._is_synthetic) to keep typing consistent across the new fields.airbyte_cdk/sources/declarative/async_job/job_orchestrator.py (2)
204-226: 💤 Low valueConsider explicit None-check for the cooldown guard?
Line 205 uses truthiness (
if self._failed_retry_wait_time_in_seconds). Since the constructor already rejects 0, this is functionally equivalent tois not None, but being explicit might make the intent clearer for future readers, wdyt?✨ Proposed refactor
for job in jobs_to_replace: if ( - self._failed_retry_wait_time_in_seconds + self._failed_retry_wait_time_in_seconds is not None and job.status() == AsyncJobStatus.FAILED and not job.is_synthetic() ):🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py` around lines 204 - 226, The guard using truthiness on self._failed_retry_wait_time_in_seconds should be made explicit to indicate it’s intended to test for presence (None) rather than falsy values; update the conditional in job_orchestrator.py to use "is not None" for self._failed_retry_wait_time_in_seconds while keeping the rest of the logic around job.status(), job.is_synthetic(), job.ready_to_retry(), job.retry_deferred(), job.set_retry_after(), and job.api_job_id() unchanged so behavior remains the same but the intent is clearer.
311-315: 💤 Low valueDirect field access is working but could be cleaner?
Line 314 directly sets
job._is_synthetic = True. Would adding a method likejob.mark_as_synthetic()or a constructor parameter be cleaner for future maintainability, or is direct access fine here since it's internal framework code, wdyt?🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py` around lines 311 - 315, The code in _create_failed_job directly sets the private field job._is_synthetic = True; instead, add a clear public API on AsyncJob (e.g., a method mark_as_synthetic() or a constructor parameter like is_synthetic=False) and use that here: update the AsyncJob class to expose mark_as_synthetic(self) which sets the internal flag and any related invariants, then replace the direct assignment in _create_failed_job with job.mark_as_synthetic() (or pass is_synthetic=True when constructing the AsyncJob) to improve encapsulation and maintainability.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@airbyte_cdk/sources/declarative/async_job/job_orchestrator.py`:
- Around line 204-226: The guard using truthiness on
self._failed_retry_wait_time_in_seconds should be made explicit to indicate it’s
intended to test for presence (None) rather than falsy values; update the
conditional in job_orchestrator.py to use "is not None" for
self._failed_retry_wait_time_in_seconds while keeping the rest of the logic
around job.status(), job.is_synthetic(), job.ready_to_retry(),
job.retry_deferred(), job.set_retry_after(), and job.api_job_id() unchanged so
behavior remains the same but the intent is clearer.
- Around line 311-315: The code in _create_failed_job directly sets the private
field job._is_synthetic = True; instead, add a clear public API on AsyncJob
(e.g., a method mark_as_synthetic() or a constructor parameter like
is_synthetic=False) and use that here: update the AsyncJob class to expose
mark_as_synthetic(self) which sets the internal flag and any related invariants,
then replace the direct assignment in _create_failed_job with
job.mark_as_synthetic() (or pass is_synthetic=True when constructing the
AsyncJob) to improve encapsulation and maintainability.
In `@airbyte_cdk/sources/declarative/async_job/job.py`:
- Line 28: The new attribute _is_synthetic lacks a type annotation whereas
nearby fields like _retry_after are typed; add an explicit boolean type to
_is_synthetic (i.e., declare _is_synthetic: bool) in the same class where
_retry_after is defined (look for the class containing _retry_after and the line
setting self._is_synthetic) to keep typing consistent across the new fields.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 9eba2569-5cb8-466c-af30-b6675195fac2
📒 Files selected for processing (3)
airbyte_cdk/sources/declarative/async_job/job.pyairbyte_cdk/sources/declarative/async_job/job_orchestrator.pyunit_tests/sources/declarative/async_job/test_job_orchestrator.py
…on_failure Address review feedback from Daryna: 1. AsyncJob accepts is_creation_failure as constructor parameter (default False), eliminating private-attribute write from outside the class. 2. Rename is_synthetic() to is_creation_failure() for self-documenting call sites. 3. _create_failed_job passes is_creation_failure=True at construction time. 4. Apply CodeRabbit nitpick: use 'is not None' for cooldown guard. 5. Add full regression test: real FAILED -> defer -> cooldown elapses -> _start_job returns creation-failure -> verify no re-arm. 6. Add unit test verifying _create_failed_job correctly tags the job. Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
|
All 4 design points from Daryna Ishchenko (@darynaishchenko) addressed in e2e9e6b: 1. Constructor parameter instead of private-attribute write
2. Full regression testAdded 3. Unit test for
|
…FAILED jobs Co-Authored-By: Daryna Ishchenko <darina.ishchenko17@gmail.com>
Summary
Adds an optional
failed_retry_wait_time_in_secondsparameter toAsyncRetrieverthat defers retry of real FAILED async jobs (report created on the API but returned a failure status like Amazon SP-API's FATAL) until a cooldown period elapses — without blocking other jobs or usingtime.sleep().Key design decisions:
TIMED_OUTjobs are retried immediately, preserving existing behavior.AsyncJobdistinguishes the two via anis_creation_failureconstructor parameter (no private-attribute writes from outside the class).retry_afteron initial failure, then skips until the cooldown elapses.minimum: 1validation and config interpolation support.Related: airbytehq/airbyte#77837 configures
failed_retry_wait_time_in_seconds: 1800for source-amazon-seller-partner.Review & Testing Checklist for Human
_replace_failed_jobs— the two guards (ready_to_retrythenretry_deferred) must be checked in this order. On first failure both are false → second branch arms cooldown. Before cooldown expires,ready_to_retry()is false → first branch skips. After cooldown, both true → falls through to replacement. Verify no ordering produces unexpected behavior._create_failed_job()passesis_creation_failure=Trueand that the conditionnot job.is_creation_failure()correctly gates deferral so 429-driven retries are never delayed.pytest unit_tests/sources/declarative/async_job/ -v(43 tests). The regression testtest_given_real_failed_then_cooldown_elapses_then_start_returns_creation_failure_then_no_rearmcovers: real FAILED → arm cooldown → cooldown elapses → replacement is creation-failure → no re-arm on next tick.Notes
<= 0runtime guard in the orchestrator constructor is the only enforcement beyond the YAMLminimum: 1; Pydantic model usesUnion[int, str]without constraints.job_tracker._jobs(private set) directly to pre-register job IDs.Link to Devin session: https://app.devin.ai/sessions/2cbe53a30ea14f9f8151f407f423283b
Requested by: Daryna Ishchenko (@darynaishchenko)
Summary by CodeRabbit
New Features
failed_retry_wait_time_in_seconds).Tests