Skip to content

Refactor task queue to resolve task-related failures - #964

Open
darinkrauss wants to merge 21 commits into
masterfrom
darin/dexcom-slowdown
Open

Refactor task queue to resolve task-related failures#964
darinkrauss wants to merge 21 commits into
masterfrom
darin/dexcom-slowdown

Conversation

@darinkrauss

@darinkrauss darinkrauss commented Jul 20, 2026

Copy link
Copy Markdown
Contributor
  • Refactor task queue to resolve task-related failures
  • Remove priority and expiration time from task as unused
  • Add task timeout watchdog
  • Add name to queue for debugging purposes
  • Add revision condition to task Client and Repository API
  • Set default available time for a new task to now
  • Update runner deadline to return duration, not time
  • Add Prometheus metrics to Dexcom client
  • Add Prometheus metrics to task queue
  • Add serialization mutex to test logger
  • Require OAuth client to specify configured http client
  • Capture response metrics on all outgoing OAuth client requests
  • Add Prometheus helpers for outgoing clients
  • Add common CloseCursor function to properly close Mongo cursors and log errors
  • Add and update tests
  • Fix typos
  • https://tidepool.atlassian.net/browse/BACK-4523
  • https://tidepool.atlassian.net/browse/BACK-4553

- Refactor task queue to resolve task-related failures
- Remove priority and expiration time from task as unused
- Add task timeout watchdog
- Add name to queue for debugging purposes
- Add revision condition to task Client and Repository API
- Set default available time for a new task to now
- Update runner deadline to return duration, not time
- Add Prometheus metrics to Dexcom client
- Add Prometheus metrics to task queue
- Add serialization mutex to test logger
- Require OAuth client to specify configured http client
- Capture response metrics on all outgoing OAuth client requests
- Add Prometheus helpers for outgoing clients
- Add common CloseCursor function to properly close Mongo cursors and log errors
- Add and update tests
- Fix typos
- https://tidepool.atlassian.net/browse/BACK-4523
@darinkrauss
darinkrauss requested a review from toddkazakov July 20, 2026 15:54
- Make task deadline time database only
- Add grace period to runner watchdog to remove spurious metrics
- Minor updates
@darinkrauss

Copy link
Copy Markdown
Contributor Author

Code review

Found 1 issue:

  1. AvailableTime is never cleared when a task completes or fails, leaving a stale value on terminal tasks in the database. The previous queue code explicitly set tsk.AvailableTime = nil on dispatch and before persisting non-pending tasks, but in the refactor: computeState has an empty case for TaskStateFailed/TaskStateCompleted, completeTask passes the stale pre-run AvailableTime into the TaskUpdate, and parseUpdate has no unset branch for availableTime (unlike Data/Error, which use non-nil wrapper semantics precisely so they can be unset). Since NewTask always sets a non-nil AvailableTime, every completed/failed task will retain it. Notably, dexcom_analyze in this same PR still flags Issue_Task_With_State_Failed_AvailableTime_Present as an anomaly, so this state will trip your own diagnostic tooling.

// Data and Error use non-nil wrappers so that a task whose data or error was cleared during
// the run has the corresponding field unset in the database, rather than left stale (a nil
// wrapper means "leave unchanged" to StopTask).
update := &task.TaskUpdate{
Data: pointer.From(tsk.Data),
AvailableTime: tsk.AvailableTime,
Error: &errors.Serializable{Error: tsk.GetError()},
}

tsk.SetFailed()
case task.TaskStateFailed, task.TaskStateCompleted:
default:

}
if update.AvailableTime != nil {
set["availableTime"] = *update.AvailableTime
}
if update.Error != nil {

Issue_Task_With_DeviceHashes_And_DataSource_LatestDataTime_Missing = "task with device hashes and data source latest data time missing"
Issue_Task_With_State_Failed_AvailableTime_Present = "task with state failed available time present"
Issue_Task_With_State_Failed_Error_Missing = "task with state failed error missing"
Issue_Task_With_State_Running_AvailableTime_Present = "task with state running available time present"

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Clear task available time appropriately
- Add and update tests
@darinkrauss

Copy link
Copy Markdown
Contributor Author

Resolve code review issue above.

Copilot AI 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 overview

This PR performs a broad refactor of the task queue/task repository to improve reliability under concurrent execution and shutdown, while also adding standardized Prometheus instrumentation for outgoing HTTP clients (Dexcom/Oura/Twiist) and a few supporting test/util updates.

Changes:

  • Refactors task lifecycle and persistence with revision/state-lock based compare-and-swap, new start/stop transition APIs, and improved unstick/deadline behavior.
  • Updates queue/runner contracts (deadlines as time.Duration, watchdog/stop timeouts) and adds Prometheus metrics for queue health and runner behavior.
  • Introduces shared Prometheus RoundTripper helpers for outgoing HTTP requests and updates OAuth/provider clients to require an explicit configured http.Client.

Reviewed changes

Copilot reviewed 76 out of 77 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
twiist/provider/provider.go Adds Prometheus-instrumented HTTP client for Twiist OAuth/provider traffic
test/time.go Exposes test.Now() helper
test/http/http.go Adds a test RoundTripper for HTTP client tests
task/test/task.go Adds task random generators aligned with new task fields (revision/state lock, etc.)
task/test/task_mocks.go Updates gomock client signatures to include revision conditions
task/test/task_accessor.go Removes legacy TaskAccessor test helper
task/test/client.go Replaces embedded TaskAccessor with an explicit test Client implementing new condition-aware APIs
task/task.go Refactors task models (remove priority/expiration, add revision/stateLock logging fields, default available time) and Client API condition support
task/task_test.go Adds coverage for NewTask available-time defaulting and LogFields()
task/store/test/task_session.go Removes unused TasksSession helper
task/store/store.go Refactors repository interface: adds Start/Stop transitions, typed conditions, and unstick returning IDs
task/store/mongo/mongo.go Implements new task repository semantics (CAS start/stop, revised unstick, iterate pending, metrics)
task/store/mongo/mongo_test.go Updates mongo repository tests for new APIs and semantics
task/service/service/service.go Updates service queue initialization to pass runners at MultiQueue construction
task/service/service/client.go Maps request-level conditions to store-level conditions
task/service/api/v1/v1.go Adds query-decoded request conditions to Get/Update/Delete task endpoints
task/queue/test/runner.go Updates runner deadline API (duration) and adds multiple test runners for failure/timeout scenarios
task/queue/queue.go Major queue refactor: single-use lifecycle, watchdog/metrics, CAS start/stop integration, shutdown behavior
task/queue/queue_test.go Adds extensive coverage for queue lifecycle, shutdown, timeouts, panics, and CAS behavior
task/queue/queue_internal_test.go Updates internal tests for iterator failure logging and computeState behavior
task/queue/multi.go Refactors MultiQueue to build per-runner queues at construction and stop them concurrently
task/queue/multi_test.go Updates tests to match new MultiQueue construction and runner registration model
task/client/client.go Adds condition support for Get/Update/Delete and improves error wrapping
summary/task/updaterunner.go Removes explicit AvailableTime set and updates deadline API to duration
summary/task/updaterunner_test.go Updates expectations after AvailableTime defaulting change
summary/task/migrationrunner.go Removes explicit AvailableTime set and updates deadline API to duration
summary/task/migrationrunner_test.go Updates expectations after AvailableTime defaulting change
store/structured/mongo/result.go Adds CloseCursor helper with timeout and logging
store/structured/mongo/result_test.go Adds unit tests for CloseCursor plus import renames
store/structured/mongo/config.go Fixes minor comment typo
store/structured/condition.go Adds NewConditionWithRevision helper and mapping support
services/tools/dexcom_analyze/dexcom_analyze.go Removes checks/resolutions for removed task fields (deadline/expiration)
request/inspector.go Removes deprecated Prometheus response inspector utilities
request/condition.go Adds NewConditionWithRevision helper
prometheus/test/prometheus.go Adds Prometheus test helpers for metric lookup and label maps
pointer/default.go Adds DefaultArray helper for slices
pointer/default_test.go Adds tests for DefaultArray
plugin/abbott/go.sum Adds github.com/gowebpki/jcs checksums and testify go.mod entry
plugin/abbott/go.mod Adds indirect dependency github.com/gowebpki/jcs
oura/provider/provider.go Adds Prometheus-instrumented HTTP client and path patterns for Oura requests
oura/provider/provider_test.go Adds tests for Oura Prometheus path pattern generation
oura/oura.go Adds DataTypeToPath helper to handle Oura path naming inconsistencies
oura/oura_test.go Adds tests for DataTypeToPath
oura/client/client.go Switches to oura.DataTypeToPath, removes old Prometheus inspector usage, increases request duration maximum
oura/client/client_test.go Updates tests to use oura.DataTypeToPath and removes inspector-related tests
oauth/provider/provider.go Requires explicit http.Client and wires it into oauth2 token source context
oauth/provider/client/client.go Updates provider client constructors to accept explicit http.Client
log/test/serializer.go Adds mutex to serialize access in test logger serializer
go.mod Promotes prometheus/client_model to a direct dependency
ehr/sync/task.go Removes explicit AvailableTime set (now defaulted in Task creation)
ehr/sync/task_test.go Updates expectations after AvailableTime defaulting change
ehr/sync/runner.go Updates deadline API to duration
ehr/reconcile/task.go Removes explicit AvailableTime set
ehr/reconcile/task_test.go Updates expectations after AvailableTime defaulting change
ehr/reconcile/runner.go Updates deadline API and condition-aware task client calls
ehr/reconcile/runner_test.go Updates mock expectations for condition-aware DeleteTask
dexcom/provider/provider.go Adds Prometheus-instrumented HTTP client, request-time header metric, and condition-aware task deletion
dexcom/provider/provider_test.go Adds tests for Dexcom request-time metric RoundTripper
dexcom/moment.go Renames/adjusts compacting behavior to exclude moments without system time
dexcom/moment_test.go Updates tests for new moments compaction semantics
dexcom/fetch/runner.go Updates deadline API to duration
dexcom/fetch/runner_test.go Updates deadline expectation accordingly
dexcom/event.go Fixes incorrect Warnf usage when transmitter ID is empty
dexcom/egv.go Fixes incorrect Warnf usage when transmitter ID is empty
dexcom/device.go Fixes typo in comment (“specfied” → “specified”)
dexcom/data_range.go Uses updated moments compaction function
dexcom/data_range_test.go Adds tests for data range behavior excluding moments without system time
dexcom/client/client.go Removes response inspector metrics and uses WarnIfDurationExceedsMaximum helper
dexcom/calibration.go Fixes incorrect Warnf usage when transmitter ID is empty
dexcom/alert.go Fixes incorrect Warnf usage when transmitter ID is empty
data/store/mongo/mongo_datum.go Fixes comment typo (“the the”)
data/service/service/standard.go Fixes log message typo (“the the”)
client/round_tripper.go Adds generic RoundTripper wrapper with default-transport resolution
client/round_tripper_test.go Adds tests for the generic RoundTripper wrapper
client/prometheus.go Adds Prometheus URL path matching + request count/duration RoundTripper utilities
client/prometheus_test.go Adds tests for Prometheus RoundTripper utilities

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread twiist/provider/provider.go Outdated
Comment thread dexcom/provider/provider.go Outdated
Comment thread oura/provider/provider.go Outdated
Comment thread pointer/default_test.go Outdated
Comment thread pointer/default_test.go Outdated
Comment thread dexcom/provider/provider_test.go Outdated
@darinkrauss
darinkrauss force-pushed the darin/dexcom-slowdown branch from 3ff73f9 to 8b90137 Compare July 21, 2026 15:09
- Correctly wire up round tripper in OAuth clients
- Remove unnecessary and outdated mocks
- Update tests
toddkazakov
toddkazakov previously approved these changes Jul 23, 2026
- Update Prometheus metrics
- Capture Dexcom response request-time header correctly
- Warn and capture if task revision changed during run
- Simplify task queue tests
- Add checks for task claims that have been lost
- Update Dexcom task runner to correctly update last import time
- Update Dexcom task runner to correctly reschedule
- Fix delay constants
- Update comments
- Update tests
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Prometheus metrics endpoints and HTTP request instrumentation.
    • Added configurable request timeouts, duration parsing, and HTTP client support.
    • Added revision-aware task conditions and enhanced task queue scheduling, monitoring, retries, and shutdown.
    • Added Oura path mapping and task utility improvements.
  • Bug Fixes

    • Improved streaming cancellation, task state handling, data-range calculation, and Mongo cursor cleanup.
  • Documentation

    • Updated Prometheus metrics descriptions.
  • Chores

    • Updated CI and local environment defaults to use 127.0.0.1.

Walkthrough

This change redesigns task scheduling and Mongo task lifecycle handling, adds conditional task operations, introduces HTTP and Prometheus instrumentation, exposes metrics endpoints, updates Dexcom and Oura integrations, and refreshes supporting tests, tooling, configuration, and documentation.

Changes

Task contracts and orchestration

Layer / File(s) Summary
Task model and conditional operations
task/..., request/..., task/test/...
Task revisions, claim tokens, condition-aware operations, state transitions, logging fields, task clients, and test helpers are updated.
Mongo task lifecycle
task/store/...
Type filtering, conditional CRUD, task transitions, unsticking, indexes, and lifecycle metrics are implemented.
Queue execution and integration
task/queue/..., task/service/..., ehr/..., summary/task/..., dexcom/...
Runner deadlines become durations. Queue scheduling, cancellation, watchdogs, claim monitoring, completion, shutdown, and multi-queue construction are redesigned.
HTTP and Prometheus instrumentation
client/..., oauth/..., oura/..., dexcom/client/..., auth/service/api/v1/..., data/service/api/v1/...
HTTP clients accept timeout and transport configuration. Request metrics are recorded, and Prometheus endpoints are registered.
Supporting behavior and maintenance
errors/..., README.md, test/..., environment and configuration files
Error serialization, test infrastructure, documentation, local environment settings, dependency metadata, and maintenance changes are updated.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TaskManager
  participant TaskQueue
  participant TaskRepository
  participant Runner
  TaskManager->>TaskRepository: IteratePending
  TaskQueue->>TaskRepository: StartTask
  TaskQueue->>Runner: Run with cancellation and watchdog
  Runner-->>TaskQueue: terminal task state
  TaskQueue->>TaskRepository: StopTask with claim token
Loading

Possibly related PRs

  • tidepool-org/platform#965: The main PR directly includes and extends the retrieved PR’s claim-token/lost-claim changes across the task queue, task store, task model, and Dexcom runner.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary task queue refactor and its purpose of resolving task-related failures.
Description check ✅ Passed The description directly covers the task queue refactor and its related API, timeout, metrics, OAuth, MongoDB, and testing changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch darin/dexcom-slowdown

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dexcom/fetch/runner.go (1)

406-437: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

importCompleted is never set on the deadline-exceeded path, so LastImportTime is not persisted.

In fetchSinceLatestDataTime, the early return when the run exceeds GetRunnerDurationMaximum() (lines 427-430) does not set t.importCompleted = true. Since updateDataSourceWithTaskState (lines 296-300) only stamps LastImportTime when importCompleted is true, data successfully fetched/stored just before hitting the deadline is never reflected in LastImportTime on the data source.

This contradicts the new tests in dexcom/fetch/runner_test.go ("is available soon if the deadline is exceeded" and "...and a later update fails"), both of which assert dataSrc.LastImportTime is non-nil after this exact code path runs.

🐛 Proposed fix
 		// If the task has been running for longer than the maximum duration, then stop fetching and repeat after a minute
 		if time.Since(t.runTime) > t.GetRunnerDurationMaximum() {
 			t.availableAfter = pointer.From(time.Minute)
+			t.importCompleted = true
 			return nil
 		}

Also applies to: 296-300

🤖 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 `@dexcom/fetch/runner.go` around lines 406 - 437, Set t.importCompleted = true
before the deadline-exceeded return in fetchSinceLatestDataTime, after the
successfully fetched range is stored and before assigning availableAfter.
Preserve the existing retry scheduling and ensure updateDataSourceWithTaskState
can persist LastImportTime for partial imports.
🧹 Nitpick comments (11)
oauth/client/client_test.go (1)

195-197: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the injected HTTP-client path.

Line 197 always supplies nil, and the mock only checks for a non-nil context. Add a case with a distinct *http.Client and verify HTTPClient receives it via ctx.Value(oauth2.HTTPClient).

Also applies to: 238-240

🤖 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 `@oauth/client/client_test.go` around lines 195 - 197, Add test coverage for
the injected HTTP-client path in the oauthClient.New setup blocks, including the
corresponding case around the second referenced setup. Pass a distinct
*http.Client instead of nil, then verify the mock receives it through
ctx.Value(oauth2.HTTPClient) while preserving the existing non-nil context
validation.
task/queue/queue_test.go (1)

495-495: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test description grammar.

"logs a warning if the runner update the task while it was running"updated; "logs a warning if a task that exceeds its maximum duration" → drop if a or reword to "logs a warning when a task exceeds its maximum duration".

Also applies to: 899-899

🤖 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 `@task/queue/queue_test.go` at line 495, Correct the grammar in the affected It
test descriptions in the queue tests: update the runner wording to use
“updated,” and reword the maximum-duration description to “logs a warning when a
task exceeds its maximum duration.”
task/queue/queue.go (1)

366-463: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Watchdog can double-count a single overrun run.

If a cooperative runner returns after GetRunnerTimeout()+RunnerWatchdogGracePeriod, the time.AfterFunc already fired ("blocked") and the reconciliation at Line 460 also records "recovered", so one run contributes to both dispositions. If the intent is that dispositions are mutually exclusive, guard the recovered increment on whether the watchdog fired (e.g. capture the Stop() return value).

♻️ Sketch
-	// Immediately stop the runner watchdog
-	runnerWatchdog.Stop()
+	// Immediately stop the runner watchdog; a false return means it already reported the run as blocked.
+	watchdogFired := !runnerWatchdog.Stop()
@@
 		} else if cause := context.Cause(runnerContext); errors.Is(cause, ErrRunnerTimeoutExceeded) {
 			lgr.Warn("Task runner exceeded timeout; task will be failed")
 			tsk.AppendError(cause)
-			RunnerTimeoutExceededTotal.WithLabelValues(runner.GetRunnerType(), "recovered").Inc()
+			if !watchdogFired {
+				RunnerTimeoutExceededTotal.WithLabelValues(runner.GetRunnerType(), "recovered").Inc()
+			}
 		}
🤖 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 `@task/queue/queue.go` around lines 366 - 463, Update the runner watchdog
handling in Queue.runTask so timeout dispositions are mutually exclusive:
capture whether runnerWatchdog.Stop() successfully prevented the callback, and
only increment the "recovered" RunnerTimeoutExceededTotal metric when the
watchdog did not already fire. Preserve the existing "blocked" metric behavior
for runs whose watchdog callback executes.
task/test/task.go (1)

75-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Failed fixtures contradict the new terminal-state invariant.

Task.SetFailed now clears AvailableTime (task/task.go Line 291), and the PR objectives note that a failed task retaining availableTime is treated as anomalous. RandomTask still populates AvailableTime for TaskStateFailed (and TaskStateCompleted), so fixtures model a state production code no longer produces.

♻️ Align failed/completed fixtures with the terminal-state invariant
 	case task.TaskStateFailed:
-		tsk.AvailableTime = pointer.From(test.RandomTimeFromRange(tsk.CreatedTime, now))
-		tsk.ModifiedTime = pointer.From(test.RandomTimeFromRange(*tsk.AvailableTime, now))
+		tsk.ModifiedTime = pointer.From(test.RandomTimeFromRange(tsk.CreatedTime, now))
 		tsk.Error = errorsTest.RandomSerializable()
🤖 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 `@task/test/task.go` around lines 75 - 88, Update the TaskStateFailed and
TaskStateCompleted branches in RandomTask so terminal-state fixtures leave
AvailableTime unset, matching SetFailed’s invariant. Remove the AvailableTime
assignments from both branches while preserving their existing ModifiedTime,
error, runtime, duration, and deadline setup.
task/store/mongo/mongo.go (1)

519-551: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Unfiltered UnstickTasks cannot use the new partial index.

The {type, deadlineTime} partial index requires a type prefix predicate, so when typeFilter is nil (non-partitioned repository) this find degenerates to a collection scan over tasks. Fine today since MultiQueue always partitions by type, but worth either documenting or adding a {deadlineTime} partial index for the unfiltered path.

🤖 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 `@task/store/mongo/mongo.go` around lines 519 - 551, Address the unfiltered
UnstickTasks query path: when typeFilter is nil, ensure the repository has a
suitable partial index on deadlineTime for running tasks, or document the
intentional collection-scan behavior if that is the chosen design. Keep the
existing {type, deadlineTime} index usage for filtered repositories and anchor
the change around TaskRepository.UnstickTasks and its findSelector construction.
client/config.go (1)

44-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider accepting a duration string rather than bare seconds.

strconv.ParseInt restricts config to whole seconds and drops the parse cause from the returned error. time.ParseDuration would accept "30s", "1m500ms", etc., and is the idiomatic choice for a time.Duration field.

♻️ Proposed refactor
-	if timeoutString, err := reporter.Get("timeout"); err == nil {
-		if timeout, parseErr := strconv.ParseInt(timeoutString, 10, 0); parseErr != nil {
-			return errors.New("timeout is invalid")
-		} else {
-			c.Timeout = time.Duration(timeout) * time.Second
-		}
-	}
+	if timeoutString, err := reporter.Get("timeout"); err == nil {
+		timeout, parseErr := time.ParseDuration(timeoutString)
+		if parseErr != nil {
+			return errors.Wrap(parseErr, "timeout is invalid")
+		}
+		c.Timeout = timeout
+	}

(strconv import then becomes unnecessary. Note this changes the expected config value format, so existing deployment values would need updating.)

🤖 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 `@client/config.go` around lines 44 - 50, Update the timeout parsing block to
use time.ParseDuration instead of strconv.ParseInt, allowing values such as
“30s” and “1m500ms” and assigning the parsed duration directly to c.Timeout.
Preserve the existing invalid-timeout error behavior and remove the now-unused
strconv import.
client/prometheus.go (2)

20-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

These label names/values are effectively constants.

PrometheusLabelNameMethod/Path/Status and PrometheusLabelValueError are exported mutable package vars; making them const prevents accidental reassignment by importers (they are already referenced from dexcom/client and tests).

🤖 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 `@client/prometheus.go` around lines 20 - 25, Change PrometheusLabelNameMethod,
PrometheusLabelNamePath, PrometheusLabelNameStatus, and
PrometheusLabelValueError from exported mutable variables to exported constants,
preserving their existing names and string values so references in dexcom/client
and tests continue to work.

92-115: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Document duplicate metric name registration or make the registry explicit.

promauto.NewCounterVec and NewHistogramVec register with the global default registerer and panic on duplicate metric names. Since this constructor is exported and named metrics are registered from package-level singletons, callers should be told it must be called once per name at init; otherwise, accept a custom prometheus.Registerer/auto.Registry so duplicate registration can be handled explicitly.

🤖 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 `@client/prometheus.go` around lines 92 - 115, Update
NewPrometheusRequestRoundTripperWithPathPatternsAndDurationBuckets and its
wrapper to avoid undocumented duplicate registration: either document that each
metric name may be constructed only once during initialization, or add an
explicit prometheus.Registerer/auto.Registry parameter and register the
CounterVec and HistogramVec through it so duplicate-registration errors can be
handled without relying on the global default registerer.
client/round_tripper.go (1)

26-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

WithRoundTripper name implies a builder but mutates in place.

With… conventionally returns a new/derived value in Go. Since this mutates the receiver and returns nothing, SetRoundTripper communicates intent better. Also worth noting: mutating this after the round tripper is shared (these are package-level singletons in dexcom/client and oura/provider) is unsynchronized, so restrict use to setup/test wiring.

🤖 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 `@client/round_tripper.go` around lines 26 - 28, Rename the mutating
RoundTripper.WithRoundTripper method to SetRoundTripper and update all callers
accordingly. Keep its in-place assignment behavior, and ensure usage is limited
to setup or test wiring rather than changing the shared round tripper after
concurrent use begins.
auth/service/api/v1/metrics.go (1)

17-21: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Unchecked type assertion on the response writer.

res.(http.ResponseWriter) panics if the underlying rest.ResponseWriter implementation ever doesn't implement http.ResponseWriter (e.g. a wrapped/test double). A checked assertion with a 500 fallback keeps a metrics scrape from taking down the handler goroutine.

🤖 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 `@auth/service/api/v1/metrics.go` around lines 17 - 21, Update
Router.PrometheusMetrics to safely assert that res implements
http.ResponseWriter before calling ServeHTTP; when the assertion fails, return
an HTTP 500 response and avoid invoking the Prometheus handler, while preserving
the existing metrics handling for valid writers.
dexcom/client/client.go (1)

32-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Don’t mutate the caller’s Config and make the instrumented client carry Dexcom requests.

cfg is a caller-owned pointer, so cfg.Timeout = 1 * time.Minute writes defaulting into the caller after validation. Default on a local copy instead. Also, build httpClient.Timeout from the configured timeout instead of http.DefaultClient.Timeout, and make the Dexcom TokenSource implementation return that same client from HTTPClient(...), not http.DefaultClient, so the Prometheus round tripper is actually used.

🤖 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 `@dexcom/client/client.go` around lines 32 - 41, Update the client construction
around the Config defaulting and Dexcom TokenSource implementation: copy the
caller-provided Config before applying the one-minute timeout default, set
httpClient.Timeout from that local configured timeout, and make
TokenSource.HTTPClient(...) return the instrumented httpClient instead of
http.DefaultClient so Dexcom requests use prometheusRequestMetricsRoundTripper.
🤖 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 `@auth/service/api/v1/metrics.go`:
- Around line 11-15: Update Router.MetricsRoutes and its route configuration so
GET /v1/metrics requires validation of the service secret before invoking
PrometheusMetrics. Ensure anonymous requests are rejected while preserving
authenticated metrics collection and the existing route behavior.

In `@client/client.go`:
- Around line 88-93: Update the request flow around createRequest and the
outbound httpClient.Do call so config.Timeout is applied to the request context
used by the HTTP transport. Remove the defer cancel in this function; invoke
cancel on every early-return/error path, and on success wrap the returned
io.ReadCloser so its Close method invokes cancel after streaming reads finish.

In `@README.md`:
- Line 204: Update the README description for
tidepool_task_type_lost_completion_total to use claim-token terminology instead
of state-lock, matching the metric behavior documented in
task/store/mongo/mongo.go.
- Around line 194-195: Update the descriptions of tidepool_task_workers_total
and tidepool_task_workers_available in the metrics documentation to remove the
hard-coded “5” worker count, and describe the per-queue values as determined by
deployment configuration.

In `@store/structured/mongo/result.go`:
- Around line 100-114: Update CloseCursor so nil contexts are replaced with
context.Background(), then always wrap the resulting context with
context.WithoutCancel and the existing 10-second context.WithTimeout before
calling cursor.Close. Preserve the current nil-cursor guard, cancellation
cleanup, and error logging behavior.

In `@task/store/mongo/mongo_test.go`:
- Around line 221-245: Update the timestamp tolerance assertions in the
“unsticks a running task with an expired deadline” test to compare against
wall-clock time from time.Now(), matching UnstickTasks’ UTC-based timestamps
instead of the frozen test.Now() value. Preserve the existing one-second
tolerance and the delayed available-time assertion in the adjacent test.

---

Outside diff comments:
In `@dexcom/fetch/runner.go`:
- Around line 406-437: Set t.importCompleted = true before the deadline-exceeded
return in fetchSinceLatestDataTime, after the successfully fetched range is
stored and before assigning availableAfter. Preserve the existing retry
scheduling and ensure updateDataSourceWithTaskState can persist LastImportTime
for partial imports.

---

Nitpick comments:
In `@auth/service/api/v1/metrics.go`:
- Around line 17-21: Update Router.PrometheusMetrics to safely assert that res
implements http.ResponseWriter before calling ServeHTTP; when the assertion
fails, return an HTTP 500 response and avoid invoking the Prometheus handler,
while preserving the existing metrics handling for valid writers.

In `@client/config.go`:
- Around line 44-50: Update the timeout parsing block to use time.ParseDuration
instead of strconv.ParseInt, allowing values such as “30s” and “1m500ms” and
assigning the parsed duration directly to c.Timeout. Preserve the existing
invalid-timeout error behavior and remove the now-unused strconv import.

In `@client/prometheus.go`:
- Around line 20-25: Change PrometheusLabelNameMethod, PrometheusLabelNamePath,
PrometheusLabelNameStatus, and PrometheusLabelValueError from exported mutable
variables to exported constants, preserving their existing names and string
values so references in dexcom/client and tests continue to work.
- Around line 92-115: Update
NewPrometheusRequestRoundTripperWithPathPatternsAndDurationBuckets and its
wrapper to avoid undocumented duplicate registration: either document that each
metric name may be constructed only once during initialization, or add an
explicit prometheus.Registerer/auto.Registry parameter and register the
CounterVec and HistogramVec through it so duplicate-registration errors can be
handled without relying on the global default registerer.

In `@client/round_tripper.go`:
- Around line 26-28: Rename the mutating RoundTripper.WithRoundTripper method to
SetRoundTripper and update all callers accordingly. Keep its in-place assignment
behavior, and ensure usage is limited to setup or test wiring rather than
changing the shared round tripper after concurrent use begins.

In `@dexcom/client/client.go`:
- Around line 32-41: Update the client construction around the Config defaulting
and Dexcom TokenSource implementation: copy the caller-provided Config before
applying the one-minute timeout default, set httpClient.Timeout from that local
configured timeout, and make TokenSource.HTTPClient(...) return the instrumented
httpClient instead of http.DefaultClient so Dexcom requests use
prometheusRequestMetricsRoundTripper.

In `@oauth/client/client_test.go`:
- Around line 195-197: Add test coverage for the injected HTTP-client path in
the oauthClient.New setup blocks, including the corresponding case around the
second referenced setup. Pass a distinct *http.Client instead of nil, then
verify the mock receives it through ctx.Value(oauth2.HTTPClient) while
preserving the existing non-nil context validation.

In `@task/queue/queue_test.go`:
- Line 495: Correct the grammar in the affected It test descriptions in the
queue tests: update the runner wording to use “updated,” and reword the
maximum-duration description to “logs a warning when a task exceeds its maximum
duration.”

In `@task/queue/queue.go`:
- Around line 366-463: Update the runner watchdog handling in Queue.runTask so
timeout dispositions are mutually exclusive: capture whether
runnerWatchdog.Stop() successfully prevented the callback, and only increment
the "recovered" RunnerTimeoutExceededTotal metric when the watchdog did not
already fire. Preserve the existing "blocked" metric behavior for runs whose
watchdog callback executes.

In `@task/store/mongo/mongo.go`:
- Around line 519-551: Address the unfiltered UnstickTasks query path: when
typeFilter is nil, ensure the repository has a suitable partial index on
deadlineTime for running tasks, or document the intentional collection-scan
behavior if that is the chosen design. Keep the existing {type, deadlineTime}
index usage for filtered repositories and anchor the change around
TaskRepository.UnstickTasks and its findSelector construction.

In `@task/test/task.go`:
- Around line 75-88: Update the TaskStateFailed and TaskStateCompleted branches
in RandomTask so terminal-state fixtures leave AvailableTime unset, matching
SetFailed’s invariant. Remove the AvailableTime assignments from both branches
while preserving their existing ModifiedTime, error, runtime, duration, and
deadline setup.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 10cfe943-b178-4499-9192-9203463d0543

📥 Commits

Reviewing files that changed from the base of the PR and between 0880a3a and 1503c64.

⛔ Files ignored due to path filters (1)
  • plugin/abbott/go.sum is excluded by !**/*.sum
📒 Files selected for processing (94)
  • .travis.yml
  • Makefile
  • README.md
  • auth/service/api/v1/metrics.go
  • auth/service/api/v1/router.go
  • client/client.go
  • client/config.go
  • client/prometheus.go
  • client/prometheus_test.go
  • client/round_tripper.go
  • client/round_tripper_test.go
  • data/service/api/v1/metrics.go
  • data/service/api/v1/v1.go
  • data/service/service/standard.go
  • data/store/mongo/mongo_datum.go
  • dexcom/alert.go
  • dexcom/calibration.go
  • dexcom/client/client.go
  • dexcom/client/client_test.go
  • dexcom/data_range.go
  • dexcom/data_range_test.go
  • dexcom/device.go
  • dexcom/egv.go
  • dexcom/event.go
  • dexcom/fetch/runner.go
  • dexcom/fetch/runner_test.go
  • dexcom/moment.go
  • dexcom/moment_test.go
  • dexcom/provider/provider.go
  • dexcom/provider/provider_test.go
  • ehr/reconcile/runner.go
  • ehr/reconcile/runner_test.go
  • ehr/reconcile/task.go
  • ehr/reconcile/task_test.go
  • ehr/sync/runner.go
  • ehr/sync/task.go
  • ehr/sync/task_test.go
  • env.sh
  • env.test.sh
  • errors/errors.go
  • errors/errors_test.go
  • go.mod
  • log/test/serializer.go
  • oauth/client/client.go
  • oauth/client/client_test.go
  • oauth/provider/client/client.go
  • oauth/test/token_source.go
  • oauth/test/token_source_source.go
  • oura/client/client.go
  • oura/client/client_test.go
  • oura/oura.go
  • oura/oura_test.go
  • oura/provider/provider.go
  • oura/provider/provider_test.go
  • plugin/abbott/go.mod
  • pointer/default.go
  • pointer/default_test.go
  • private/plugin/abbott
  • prometheus/test/prometheus.go
  • request/condition.go
  • request/inspector.go
  • services/tools/dexcom_analyze/dexcom_analyze.go
  • store/structured/condition.go
  • store/structured/mongo/config.go
  • store/structured/mongo/result.go
  • store/structured/mongo/result_test.go
  • summary/task/migrationrunner.go
  • summary/task/migrationrunner_test.go
  • summary/task/updaterunner.go
  • summary/task/updaterunner_test.go
  • task/client/client.go
  • task/queue/multi.go
  • task/queue/multi_test.go
  • task/queue/queue.go
  • task/queue/queue_internal_test.go
  • task/queue/queue_test.go
  • task/queue/runner.go
  • task/queue/test/runner.go
  • task/service/api/v1/v1.go
  • task/service/service/client.go
  • task/service/service/service.go
  • task/store/mongo/mongo.go
  • task/store/mongo/mongo_test.go
  • task/store/store.go
  • task/store/test/task_session.go
  • task/task.go
  • task/task_test.go
  • task/test/client.go
  • task/test/task.go
  • task/test/task_accessor.go
  • task/test/task_mocks.go
  • test/http/http.go
  • test/test.go
  • test/time.go
💤 Files with no reviewable changes (11)
  • dexcom/provider/provider_test.go
  • task/store/test/task_session.go
  • ehr/reconcile/task_test.go
  • task/test/task_accessor.go
  • ehr/sync/task_test.go
  • summary/task/migrationrunner_test.go
  • oauth/test/token_source.go
  • summary/task/updaterunner_test.go
  • oauth/test/token_source_source.go
  • request/inspector.go
  • services/tools/dexcom_analyze/dexcom_analyze.go

Comment thread auth/service/api/v1/metrics.go
Comment thread client/client.go
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread store/structured/mongo/result.go
Comment thread task/store/mongo/mongo_test.go

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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 `@client/client.go`:
- Around line 98-107: Synchronize the timeout callback and response completion
in the request flow around httpClient.Do: add a mutex-protected
response-received state, have the timer callback acquire the mutex and call
cancel only while Do is still pending, and mark the response as received under
the same mutex before stopping the timer. Update the relevant client method
without changing existing timeout behavior.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4fe07df0-f583-48b7-9093-1fecfe83529c

📥 Commits

Reviewing files that changed from the base of the PR and between f698aff and 25b1a82.

📒 Files selected for processing (5)
  • README.md
  • client/client.go
  • client/client_test.go
  • store/structured/mongo/result.go
  • test/time.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Comment thread client/client.go
@darinkrauss
darinkrauss requested a review from toddkazakov August 7, 2026 16:55
- Fix client duration parsing
- Add generalized duration parsing
- Add and update test
toddkazakov
toddkazakov previously approved these changes Aug 10, 2026

@toddkazakov toddkazakov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left two inline comments for your consideration

Comment thread dexcom/fetch/runner.go
Comment thread dexcom/fetch/runner.go

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
task/queue/queue.go (1)

437-448: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Explicitly unset AvailableTime for terminal task updates.

When a task completes or fails, the Mongo update must remove AvailableTime. A nil update field does not remove the stale field from an existing document. New tasks have AvailableTime, so failed tasks can retain it after this queue path persists the terminal state. This conflicts with Dexcom diagnostic tooling.

Add an explicit unset operation to the task update contract and add a persistence test for both completed and failed tasks.

🤖 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 `@task/queue/queue.go` around lines 437 - 448, Update the task persistence
contract used by the queue’s terminal-state updates to explicitly unset
AvailableTime rather than relying on a nil field value. Ensure both completed
and failed task updates remove any existing AvailableTime in Mongo, and add
persistence coverage for both terminal outcomes.
🤖 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.

Outside diff comments:
In `@task/queue/queue.go`:
- Around line 437-448: Update the task persistence contract used by the queue’s
terminal-state updates to explicitly unset AvailableTime rather than relying on
a nil field value. Ensure both completed and failed task updates remove any
existing AvailableTime in Mongo, and add persistence coverage for both terminal
outcomes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c5e52715-5754-4527-af59-869fbf2569d5

📥 Commits

Reviewing files that changed from the base of the PR and between ca3d2b0 and 355b240.

📒 Files selected for processing (13)
  • auth/client/external.go
  • auth/service/service/client.go
  • client/config.go
  • client/config_test.go
  • dexcom/fetch/runner.go
  • dexcom/fetch/runner_test.go
  • duration/duration.go
  • duration/duration_suite_test.go
  • duration/duration_test.go
  • service/server/config.go
  • task/queue/queue.go
  • task/queue/runner.go
  • task/task.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/config.go
  • task/task.go

@darinkrauss

Copy link
Copy Markdown
Contributor Author

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.

3 participants