Skip to content

fix(examples/jobs): wait on job triggers instead of wall-clock time - #834

Open
ihopenre-eng wants to merge 1 commit into
dapr:mainfrom
ihopenre-eng:fix/jobs-example-flaky-validation
Open

ihopenre-eng wants to merge 1 commit into
dapr:mainfrom
ihopenre-eng:fix/jobs-example-flaky-validation

Conversation

@ihopenre-eng

Copy link
Copy Markdown

Description

Fixes the flaky validate-example (jobs) job reported in #805.

The example sleeps for a fixed 3 seconds after scheduling the job and then relies on three triggers having already been printed. Two things make that unreliable:

  1. No margin for a retry. The job is created with WithJobConstantFailurePolicyInterval(time.Second*30). In the linked failing run the first trigger came back status code returned: 14 (Unavailable), so the next attempt was scheduled 30 seconds later, long after main had already deleted the job and exited. The job 0/1/2 received lines never appeared:

    ERROR expected lines not found:
        job 0 received
        payload: {db-backup {my-prod-db /backup-dir}}
        ...
    
  2. No margin for a slow sidecar. That run logged dapr initialized. Status: Running. Init Elapsed 20044ms, and the whole step is capped at sleep: 30. The 10 second intermission plus 3 second sleep leaves the triggers almost no room inside the window.

Because getjob is only ordered after the trigger output by that sleep, match_order: sequential is also only satisfied by chance today.

Changes

  • Wait on the triggers themselves (buffered channel signalled by the handler, 20s timeout) instead of time.Sleep(3 * time.Second). The example continues as soon as the job has fired the expected number of times, and the trigger output is now guaranteed to precede getjob.
  • Lower the constant failure policy retry interval to 1s so a transient failure can be retried inside the lifetime of the example rather than 30 seconds after it exits.
  • Raise the README background step from sleep: 30 to sleep: 45 to absorb variable sidecar start-up time.
  • Make the trigger counter an atomic.Int64; it is written from the handler goroutine.
  • The handler signals with a non-blocking send so it never delays its response to the sidecar.

Expected stdout in the README is unchanged.

Issue reference

Closes #805

Checklist

  • Code compiles correctly (go build ./jobs/..., go vet ./jobs/..., gofmt clean)
  • Extended the documentation (README timing note only; expected output unchanged)

The jobs example slept for a fixed 3 seconds after scheduling the job and
then asserted that three triggers had already been printed. That leaves no
margin for a sidecar that took longer to initialise, and none at all for a
trigger that failed and had to be retried: the constant failure policy used
a 30 second retry interval, which is longer than the whole example lives.
A single transient error therefore drops "job 0/1/2 received" from the
output and fails validate-examples.

Wait on the triggers themselves, with a timeout, so the example proceeds as
soon as the job has fired the expected number of times and the ordering of
the trigger output relative to getjob is deterministic. Lower the retry
interval so a failed trigger can actually be retried within the run, and
raise the background step timeout in the README to absorb variable sidecar
start-up time.

Also make the trigger counter atomic, since it is written from the handler
goroutine.

Closes dapr#805

Signed-off-by: ihopenre-eng <247072151+ihopenre-eng@users.noreply.github.com>
@ihopenre-eng
ihopenre-eng requested review from a team as code owners July 22, 2026 02:22
@ihopenre-eng

Copy link
Copy Markdown
Author

The CI workflows on this PR are queued at action_required and haven't started, so there are no check results to review yet. This is the first-time contributor approval gate for this repository.

Could a maintainer approve the workflow runs when convenient? Happy to rebase if anything has drifted in the meantime.

Comment thread examples/jobs/main.go
@@ -18,6 +19,14 @@ import (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added expectedJobRuns constant; makes expected count explicit and reusable.

Comment thread examples/jobs/main.go

var logger = log.New(os.Stdout, "", log.LstdFlags)

// expectedJobRuns is the number of triggers this example waits for before it

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added jobRuns channel with buffer size matching expected count; signals trigger receipt from handler goroutine.

Comment thread examples/jobs/main.go
daprc.WithJobConstantFailurePolicy(),
daprc.WithJobConstantFailurePolicyMaxRetries(4),
daprc.WithJobConstantFailurePolicyInterval(time.Second*30),
// The retry interval has to stay well below the lifetime of this

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Lowered retry interval from 30s to 1s; ensures retries can complete within example timeout.

Comment thread examples/jobs/main.go
fmt.Println("schedulejob - success")

time.Sleep(3 * time.Second)
// Wait for the job to actually fire. Sleeping for a fixed duration instead

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Replaced fixed sleep with waitForJobRuns using channel and timeout; more robust to sidecar init time and transient failures.

Comment thread examples/jobs/main.go
@@ -97,14 +114,35 @@ func main() {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Added waitForJobRuns function; properly uses timer with defer Stop() and select for timeout handling.

Comment thread examples/jobs/main.go
select {
case <-jobRuns:
case <-deadline.C:
return fmt.Errorf("timed out after %s waiting for %d job triggers, got %d", timeout, count, i)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Changed jobCount to atomic.Int64; correct for concurrent writes from handler goroutine. Usage Add(1)-1 prints current count before increment.

Comment thread examples/jobs/main.go
}
fmt.Printf("job %d received:\n type: %v \n payload: %v\n", jobCount, job.JobType, jobPayload)
jobCount++
fmt.Printf("job %d received:\n type: %v \n payload: %v\n", jobCount.Add(1)-1, job.JobType, jobPayload)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Non-blocking send to jobRuns in handler; prevents slow response from being treated as failure by Dapr sidecar.

Comment thread examples/jobs/README.md

background: true
sleep: 30
sleep: 45

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Raised background sleep from 30s to 45s; accommodates longer wait for trigger channel.

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.

flakey test jobs

2 participants