Skip to content

Split wall and monotonic clock semantics - #6807

Merged
tim-smart merged 2 commits into
Effect-TS:mainfrom
alecbuffi:bug/split-clock-semantics
Jul 31, 2026
Merged

Split wall and monotonic clock semantics#6807
tim-smart merged 2 commits into
Effect-TS:mainfrom
alecbuffi:bug/split-clock-semantics

Conversation

@alecbuffi

Copy link
Copy Markdown
Contributor

Type

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update

Description

Fixes Clock.currentTimeNanos permanently drifting from wall time when the runtime's monotonic clock pauses during host suspension or advances at a different rate from Date.now().

Previously, the live clock established its wall-clock origin once and never revisited it. This could leave nanosecond timestamps behind or ahead of Unix wall time for the lifetime of the process.

Changes

  • Add required monotonic APIs to Clock.Clock:
    • monotonicTimeNanosUnsafe()
    • monotonicTimeNanos
  • Export Clock.monotonicTimeNanos.
  • Keep currentTimeMillis and currentTimeNanos as Unix-epoch wall-clock timestamps.
  • Re-anchor live wall-clock nanoseconds when absolute skew exceeds one second.
  • Select the monotonic source from:
    1. process.hrtime.bigint()
    2. performance.now()
    3. A non-decreasing Date.now() fallback
  • Use monotonic time for:
    • Effect.timed
    • Effect.trackDuration
    • Sink.withDuration and Sink.timed
  • Give TestClock independent wall and monotonic counters.
  • Keep tracing, logging, metrics, and other protocol timestamps on wall time.
  • Document the wall-clock and monotonic semantics, including serialization and host-suspension considerations.
  • Add an effect minor changeset covering the required custom-Clock migration.

Tests

  • Add a live-clock regression test that freezes monotonic time while wall time jumps forward.
  • Verify large forward and backward wall-clock corrections re-anchor immediately.
  • Verify monotonic readings remain source-relative and non-decreasing.
  • Cover TestClock adjustment, forward setTime, backward setTime, and intermediate sleep deadlines.
  • Verify all migrated duration operators use monotonic differences.
  • Verify OpenTelemetry logs and spans continue using wall-clock timestamps.
  • Add Clock API typetests.

Local validation:

  • 314 focused Effect tests passed.
  • 4 focused OpenTelemetry tests passed.
  • Clock typetests passed against TypeScript 5.9 and 6.0.
  • pnpm check passed.
  • pnpm lint passed.

Related

  • No linked issue.

@github-project-automation github-project-automation Bot moved this to Discussion Ongoing in PR Backlog Jul 31, 2026
@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e620987

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Major
@effect/opentelemetry Major
@effect/platform-browser Major
@effect/platform-bun Major
@effect/platform-deno Major
@effect/platform-node-shared Major
@effect/platform-node Major
@effect/vitest Major
@effect/ai-anthropic Major
@effect/ai-openai-compat Major
@effect/ai-openai Major
@effect/ai-openrouter Major
@effect/atom-react Major
@effect/atom-solid Major
@effect/atom-vue Major
@effect/sql-clickhouse Major
@effect/sql-d1 Major
@effect/sql-libsql Major
@effect/sql-mssql Major
@effect/sql-mysql2 Major
@effect/sql-pg Major
@effect/sql-pglite Major
@effect/sql-sqlite-bun Major
@effect/sql-sqlite-do Major
@effect/sql-sqlite-node Major
@effect/sql-sqlite-react-native Major
@effect/sql-sqlite-wasm Major
@effect/openapi-generator Major
@effect/docgen Major
@effect/doctest Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@effect-slopcop effect-slopcop Bot added 4.0 bug Something isn't working labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
basic.ts 6.63 KB 6.63 KB 0.00 KB (0.00%)
batching.ts 9.42 KB 9.42 KB 0.00 KB (0.00%)
brand.ts 6.31 KB 6.31 KB 0.00 KB (0.00%)
cache.ts 10.18 KB 10.12 KB +0.06 KB (+0.60%)
config.ts 20.04 KB 20.04 KB 0.00 KB (0.00%)
differ.ts 19.93 KB 19.93 KB 0.00 KB (0.00%)
http-client.ts 21.03 KB 20.97 KB +0.06 KB (+0.29%)
logger.ts 10.34 KB 10.28 KB +0.06 KB (+0.56%)
metric.ts 8.55 KB 8.55 KB 0.00 KB (0.00%)
optic.ts 7.33 KB 7.33 KB 0.00 KB (0.00%)
pubsub.ts 14.47 KB 14.41 KB +0.06 KB (+0.40%)
queue.ts 11.15 KB 11.09 KB +0.06 KB (+0.54%)
schedule.ts 10.33 KB 10.27 KB +0.06 KB (+0.54%)
schema-class.ts 18.86 KB 18.86 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 28.67 KB 28.67 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 24.99 KB 24.99 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.00 KB 12.95 KB +0.06 KB (+0.44%)
schema-string.ts 10.65 KB 10.65 KB 0.00 KB (0.00%)
schema-template-literal.ts 14.85 KB 14.85 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 21.66 KB 21.66 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.08 KB 24.08 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 18.91 KB 18.91 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 18.73 KB 18.73 KB 0.00 KB (0.00%)
schema-toFormatter.ts 18.59 KB 18.59 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 22.33 KB 22.33 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.25 KB 19.25 KB 0.00 KB (0.00%)
schema.ts 18.12 KB 18.12 KB 0.00 KB (0.00%)
stm.ts 12.12 KB 12.05 KB +0.07 KB (+0.57%)
stream.ts 9.37 KB 9.37 KB 0.00 KB (0.00%)

@pullfrog pullfrog Bot 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.

✅ No new issues found.

Reviewed changes

  • Clock interface split: currentTimeNanos* redefined as Unix wall-clock timestamps; new monotonicTimeNanos* members added for elapsed duration measurement. JSDoc on both explicitly documents suitability and gotchas.
  • Wall-clock re-anchoring (wallTimeNanos): Periodically (min 1s interval) re-computes the monotonic→wall origin when skew from Date.now() exceeds 1 second, plus on wall-clock backward movement. Correctly handles both sudden and gradual drift.
  • Monotonic source selection: process.hrtime.bigint()performance.now() → non-decreasing Date.now() fallback. The fallback's previous guard ensures it never decreases.
  • Duration migration: Effect.timed, Effect.trackDuration, and Sink.withDuration (and transitively Sink.timed) switch from currentTimeNanosUnsafe to monotonicTimeNanosUnsafe. All tracing/log/metric timestamp call sites correctly remain on wall clock.
  • TestClock dual counters: adjust and forward setTime advance both wall and monotonic; backward setTime advances only wall, preserving monotonic as a non-decreasing counter. Sleep deadlines advance monotonic to match.
  • Test coverage: Live-clock regression test with mocked process.hrtime.bigint/Date.now, wall-backward duration tests for timed/trackDuration/withDuration, TestClock counter tests for all adjustment scenarios, and OTel clock alignment test updated.
  • Changeset: "effect": minor correctly flags the breaking change for custom Clock implementors.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot 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.

✅ No new issues found.

Reviewed changes

  • Removed skew-check rate limiting (wallTimeNanos): Eliminated lastCheckMillis / checkIntervalMillis guard; skew vs Date.now() is now checked every call instead of at minimum 1s intervals. Fewer state variables, and Date.now() is cheap enough that the per-call check is harmless.
  • Simplified Date.now() fallback: Initialized previous to BigInt(0) instead of bigint | undefined, removing the undefined branch. The first call always sets it higher since Date.now() after epoch is always > 0.
  • Fixed floating-point precision in advanceTo: Replaced Math.floor(timestamp * 1e6) - Math.floor(currentTimestamp * 1e6) with Math.round(deltaMillis * 1_000_000). The old two-floor approach could lose up to 1ns per term from floating-point truncation; Math.round on the delta is exact for integer deltas and simpler.
  • Simplified live-clock regression test: Replaced Effect.acquireUseRelease with Effect.gen + Effect.ensuring — same assertions, far fewer indirections.
  • Added far-future precision test: Verifies setTime at timestamp 1_000_000_000_000 produces correct monotonic nanos with Math.round.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@tim-smart
tim-smart merged commit d0f1a22 into Effect-TS:main Jul 31, 2026
16 of 17 checks passed
@github-project-automation github-project-automation Bot moved this from Discussion Ongoing to Done in PR Backlog Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 bug Something isn't working ready for review

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants