Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Deterministic, fully controllable time and time-ordered identifiers for distributed-system simulations and testing. Time is just another dependency.

Notifications You must be signed in to change notification settings

dexcompiler/Clockworks

Repository files navigation

Clockworks is a .NET library for deterministic, fully controllable time in distributed-system simulations and tests. It is built around TimeProvider so that time becomes an injectable dependency you can control (including timers/timeouts), while also providing time-ordered identifiers and causal timestamps.

Features

  • Deterministic TimeProvider

    • SimulatedTimeProvider with controllable wall time (SetUtcNow, etc.)
    • Monotonic scheduler time that advances only via Advance(...)
    • Deterministic timer ordering and predictable periodic behavior
  • TimeProvider-driven timeouts

    • Timeouts.CreateTimeout(...) for a CancellationTokenSource cancelled by the provider
    • Timeouts.CreateTimeoutHandle(...) for a disposable handle that ties timer lifetime to disposal
  • UUIDv7 generation

    • UuidV7Factory produces RFC 9562 UUIDv7 values as Guid
    • Works with real or simulated time
    • Configurable counter overflow behavior
  • Hybrid Logical Clock (HLC)

    • HLC timestamps and utilities to preserve causality in distributed simulations
    • Helpers to witness remote timestamps and generate outbound timestamps
    • Provides both a canonical 10-byte big-endian encoding (HlcTimestamp.WriteTo/ReadFrom) and an optimized packed 64-bit encoding (ToPackedInt64/FromPackedInt64)
  • Vector Clock

    • Full vector clock implementation for exact causality tracking and concurrency detection
    • Sorted-array representation optimized for sparse clocks
    • VectorClockCoordinator for thread-safe clock management across distributed nodes (allocation-conscious hot path)
    • Canonical binary wire format (VectorClock.WriteTo/ReadFrom) and string form for HTTP/gRPC headers
  • Lightweight instrumentation

    • Counters for timers, advances, and timeouts useful in simulation/test assertions

Quick start

Deterministic timers with simulated time

var tp = new SimulatedTimeProvider();
var fired = 0;
using var timer = tp.CreateTimer(_ => fired++, null, TimeSpan.FromSeconds(1), Timeout.InfiniteTimeSpan);
tp.Advance(TimeSpan.FromSeconds(1));
// fired == 1

TimeProvider-driven timeouts

var tp = new SimulatedTimeProvider();
using var timeout = Timeouts.CreateTimeoutHandle(tp, TimeSpan.FromSeconds(5));
tp.Advance(TimeSpan.FromSeconds(5));
// timeout.Token.IsCancellationRequested == true

UUIDv7 generation

var factory = new UuidV7Factory(TimeProvider.System);
var id = factory.NewGuid();

Vector Clock usage

// Create coordinators for two nodes
var nodeA = new VectorClockCoordinator(nodeId: 1);
var nodeB = new VectorClockCoordinator(nodeId: 2);
// Node A sends a message
var clockA = nodeA.BeforeSend();
// Node B receives the message
nodeB.BeforeReceive(clockA);
// Node B sends a reply
var clockB = nodeB.BeforeSend();
// Verify causality
Console.WriteLine(clockA.HappensBefore(clockB)); // true
// Propagate via HTTP headers
var header = new VectorClockMessageHeader(
 clock: clockA,
 correlationId: Guid.NewGuid()
);
var headerString = header.ToString(); // "1:1;{correlationId}" (example)

Hybrid Logical Clock (HLC) propagation

In Clockworks, a "remote timestamp" is the HlcTimestamp produced on a different node and carried over the wire via HlcMessageHeader (format: walltime.counter@node). The receiver should call BeforeReceive(...) with that timestamp to preserve causality.

Note: HlcTimestamp.ToPackedInt64()/FromPackedInt64() is an optimization encoding with a 48-bit wall time and a 4-bit node id (node id is truncated). Use WriteTo/ReadFrom when you need a full-fidelity representation.

var tp = new SimulatedTimeProvider();
using var aFactory = new HlcGuidFactory(tp, nodeId: 1);
using var bFactory = new HlcGuidFactory(tp, nodeId: 2);
var a = new HlcCoordinator(aFactory);
var b = new HlcCoordinator(bFactory);
// A sends
var t1 = a.BeforeSend();
var header = new HlcMessageHeader(t1, correlationId: Guid.NewGuid());
var headerValue = header.ToString();
// B receives
var parsed = HlcMessageHeader.Parse(headerValue);
b.BeforeReceive(parsed.Timestamp);
// B sends after receiving; should be > received timestamp
var t2 = b.BeforeSend();
Console.WriteLine(t1 < t2); // true

Distributed Systems Support

Hybrid Logical Clock (HLC)

HLC provides causality tracking that stays close to physical time, with a configurable maximum drift (HlcOptions.MaxDriftMs). When ThrowOnExcessiveDrift=true, drift is enforced by throwing HlcDriftException once the bound is exceeded. Best for:

  • Systems where wall-clock time matters (e.g., trading systems with time-based SLAs)
  • High-throughput systems where O(1) overhead is critical
  • Scenarios where approximate causality is sufficient

Trade-offs:

  • ✅ O(1) space and time complexity
  • ✅ Stays close to physical time (bounded drift enforcement is configurable)
  • ❌ Cannot detect concurrency (only ordering)
  • ❌ Requires synchronized physical clocks for best results

Example: strict vs. high-throughput drift behavior

var tp = TimeProvider.System;
// Strict: enforce bounded drift by throwing
using var strict = new HlcGuidFactory(tp, nodeId: 1, options: new HlcOptions
{
 MaxDriftMs = 1_000,
 ThrowOnExcessiveDrift = true
});
// High-throughput: allow drift to exceed MaxDriftMs (maintains monotonicity)
using var highThroughput = new HlcGuidFactory(tp, nodeId: 1, options: new HlcOptions
{
 MaxDriftMs = 60_000,
 ThrowOnExcessiveDrift = false
});

Vector Clock

Vector clocks provide exact causality tracking and concurrency detection. Best for:

  • Systems requiring precise happens-before relationships
  • Conflict detection in replicated data stores
  • Debugging distributed race conditions
  • Academic/research scenarios

Trade-offs:

  • ✅ Exact causality tracking
  • ✅ Detects concurrent events (neither happened-before the other)
  • ✅ No dependency on physical time
  • ❌ O(n) space per clock (where n = number of nodes)
  • ❌ O(n) time for merge and compare operations
  • ❌ Metadata grows with cluster size

Example: Detecting concurrency

var coordA = new VectorClockCoordinator(1);
var coordB = new VectorClockCoordinator(2);
// Two nodes generate events independently (no message passing)
var clockA = coordA.BeforeSend();
var clockB = coordB.BeforeSend();
// Vector clocks detect they are concurrent
Console.WriteLine(clockA.IsConcurrentWith(clockB)); // true
// HLC would show one as "less than" the other based on physical time

Wire formats

  • Binary (canonical): VectorClock.WriteTo/VectorClock.ReadFrom
    • Format: [count:u32 big-endian][(nodeId:u16 big-endian, counter:u64 big-endian)]*
    • ReadFrom canonicalizes unsorted input and deduplicates node IDs by taking the maximum counter.
  • String (for headers): VectorClock.ToString() / VectorClock.Parse(...) ("node:counter,node:counter", sorted by node id)

Demos

Console demos (demo/Clockworks.Demo)

The demo/Clockworks.Demo project is a small CLI with multiple focused demos.

List demos:

dotnet run --project demo/Clockworks.Demo -- list

Run a demo:

dotnet run --project demo/Clockworks.Demo -- uuidv7

Useful ones to try:

# UUIDv7 sortability and time decoding
dotnet run --project demo/Clockworks.Demo -- uuidv7-sortability
# Fast-forwardable timeouts driven by simulated time
dotnet run --project demo/Clockworks.Demo -- timeouts
# Simulated timers, periodic coalescing, and scheduler statistics
dotnet run --project demo/Clockworks.Demo -- simulated-time
# Propagating HLC across service boundaries (header format)
dotnet run --project demo/Clockworks.Demo -- hlc-messaging
# BeforeSend/BeforeReceive workflow with coordinator stats
dotnet run --project demo/Clockworks.Demo -- hlc-coordinator
 # Distributed simulation: at-least-once delivery + idempotency + HLC + vector clocks + timeouts
 dotnet run --project demo/Clockworks.Demo -- ds-atleastonce

The uuidv7 demo also has an optional benchmark mode:

dotnet run --project demo/Clockworks.Demo -- uuidv7 --bench

ASP.NET Core integration demo (demo/Clockworks.IntegrationDemo)

demo/Clockworks.IntegrationDemo is a minimal ASP.NET Core app that demonstrates a realistic integration:

  • SQLite-backed outbox + inbox (idempotency)
  • An in-memory queued "transport" to keep the demo simple
  • Clockworks HLC propagation (HlcCoordinator + HlcMessageHeader)
  • A deterministic simulation using SimulatedTimeProvider
  • Failure injection (drop/duplicate/reorder/delay)

Run it:

dotnet run --project demo/Clockworks.IntegrationDemo

Then POST to /simulate (and watch the console trace):

# Default is simulated time mode
curl -X POST "http://localhost:5000/simulate"
# Run the same simulation under real wall clock time
curl -X POST "http://localhost:5000/simulate?mode=System"
# Tweak knobs
curl -X POST "http://localhost:5000/simulate?mode=Simulated&orders=10&tickMs=5&maxSteps=20000"

Package

Versioning & releases

Clockworks follows Semantic Versioning (SemVer).

  • Package version is defined in src/Clockworks.csproj.
  • Release tags use the format vX.Y.Z (example: v1.2.0).
  • See CHANGELOG.md for notable changes.

Build-wide defaults are centralized in Directory.Build.props.

Security considerations

UUIDv7 time exposure

UUIDv7 values embed a millisecond-resolution timestamp by design (RFC 9562). As a result, any UUIDv7 generated by UuidV7Factory can be decoded to reveal an approximate creation time, and ordering/rate information can sometimes be inferred from sequences of IDs.

If you are issuing identifiers across untrusted/public boundaries (URLs, externally-visible resource IDs, third-party logs), do not treat UUIDv7 as opaque. Common mitigations are:

  • Use a random UUID (e.g., UUIDv4) for externally-visible identifiers.
  • Keep UUIDv7 as an internal primary key, and expose a separate opaque token externally.
  • Wrap/encrypt identifiers for external presentation if you need internal ordering but external opacity.

Notes on determinism

Clockworks is designed so that advancing simulated scheduler time deterministically drives timers/timeouts. Wall time can be modified independently for clock-skew/rewind simulations.

Contributing

Issues and PRs are welcome. Please include tests for behavioral changes.

Property tests

Property-based tests live under property-tests/ and are implemented with FsCheck + xUnit. See property-tests/README.md for how to run them and what invariants are covered.

About

Deterministic, fully controllable time and time-ordered identifiers for distributed-system simulations and testing. Time is just another dependency.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

AltStyle によって変換されたページ (->オリジナル) /