Skip to content

feat(topic): add Topic.latest() for a single-read value + timestamp - #30

Closed
IamCoder18 wants to merge 6 commits into
perf/topic-lock-freefrom
feat/latest-snapshot-accessor
Closed

IamCoder18 wants to merge 6 commits into
perf/topic-lock-freefrom
feat/latest-snapshot-accessor

Conversation

@IamCoder18

@IamCoder18 IamCoder18 commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds Topic.latest(), which returns the latest value and its publish timestamp from a single snapshot read, so a staleness check cannot combine halves from two different publishes.

This is the API follow-up to #29. #29 made the topic's internals safe and documented an ordering rule for composing the existing accessors. This removes the need for that rule.

Stacked on #29. This branch is built on perf/topic-lock-free and depends on the snapshot + write-monitor design that landed there. Base is set to perf/topic-lock-free; retarget to main once #29 merges.

The problem it solves

Checking staleness today takes two calls:

long stamp = topic.latestPublishNanos();
T v = topic.latestValueOr(null);
long age = System.nanoTime() - stamp;

Both accessors are individually correct. But a publish can land between them:

read #1  stamp = S_A          (sensor published 10 ms ago)
          ... a publish of B lands, S_B = S_A + 10ms
read #2  v = V_B               (V_B is 0 ms old)

age = now - S_A               → reports ~10 ms
true age of V_B               → 0 ms

Reading the timestamp first makes that safe in one direction — the age can only be over-reported, never under-reported — but over-reporting by a full publish interval still makes you reject a fresh value as stale. On a 100 Hz sensor with a 50 ms budget that is 20% of your margin, spent for nothing.

How often this happens depends on how long the two reads take. Normally they are nanoseconds apart and the odds are negligible. It stops being negligible when the reader is preempted between the two calls — a GC pause or scheduler preemption — because the window becomes milliseconds. This project deliberately configures a small young gen (-Xmn16m, -Xmx256m), so young GCs are part of the deployment, not a hypothetical. On a Control Hub also running the driver station and telemetry, a reader landing in a GC pause between two adjacent reads is not exotic.

The API

Optional<Topic.Latest<T>> snap = topic.latest();

T v = snap.map(Topic.Latest::value).orElse(defaultValue);
long age = snap.map(Topic.Latest::ageNanos).orElse(Long.MAX_VALUE);

Topic.Latest becomes public with three accessors:

member meaning
value() the published value
publishNanos() System.nanoTime() at which it was recorded
ageNanos() nanos since publishNanos() — the age of this value

ageNanos() is computed from the object's own stamp, so it also removes the pre-publish 0 sentinel that the two-call form has to guard by hand. latestPublishNanos() still returns 0 before the first publish, so that guard remains necessary for anyone composing by hand — it is now documented as such in the javadoc, CHANGELOG and topics guide rather than left implicit.

Additive only. No existing signature or behaviour changes. Callers who never check an age keep using latestValueOr — identical single volatile read, and allocation-free where latest() is not.

It allocates. Optional.ofNullable wraps every non-empty result, so latest() costs one small object per call. That matters in a periodic loop on the small young gen this project configures, so the javadoc says so and points at latestValueOr for value-only reads. An earlier draft of this description claimed latest() "allocates nothing"; that was wrong and is corrected here.

Why the ordering rule disappears

#29's rule was "sample the timestamp first, because two calls can straddle." With one read there is no straddle to guard, so:

  • latestValue() javadoc now says "prefer latest()" rather than teaching a rule
  • the concurrency test's reader uses latest(), so the rule has no remaining subject
  • the docs example uses one snapshot read

That is the part I think matters most: it is not that the rule became easier to remember, it is that there is no longer anything to remember.

Tests

Suite 61 tests, 0 failures. Two of these assertions were wrong when first written and are worth noting, since they are the same error twice: I required a strictly positive age, and used a fixed Thread.sleep(2) to guarantee clock movement. System.nanoTime() may return the same tick for adjacent reads, so both could fail on a coarse-resolution clock without a production bug. ageNanos() == 0 is legitimate; the fixed sleep now waits on the clock with a bounded loop, reusing the pattern from the existing test.

New latestReturnsValueAndTimestampFromOneConsistentPublish: empty before the first publish; agrees with both single-field accessors; a published stamp is never the 0 sentinel; ageNanos() is in range and never exceeds a delta measured later from the same stamp (a smaller delta would mean the age came from somewhere other than that stamp); a later publish replaces both halves together and reports a younger age.

The concurrency test's reader switched to latest(). Its old cross-check between "latest() empty" and "latestPublishNanos() nonzero" had to be deleted — a publish can legitimately land between those two reads, so a nonzero stamp there says nothing about consistency. I found this the hard way: the assertion fired on the first run after the switch.

Verification

  • gradle test → 61 tests, 0 failures, from clean
  • tear test green including pinned to two cores (taskset -c 0,1)
  • gradle javadoc → no new warnings

Open questions for the maintainer

  1. Does latest() earn public API space, or should it stay internal? It is the honest fix for the straddle, but it is also a new type in the public surface with a name (Latest) that could collide with user vocabulary.
  2. Should latestValue() and latestPublishNanos() be deprecated? They are still correct and still the right choice when you only want one. I have deliberately not deprecated anything — that is a maintainer decision, and it would break the "purely additive" property of this PR.
  3. Return shape. Optional<Topic.Latest<T>> allocates one small wrapper per call, which is a real cost in a periodic loop on a small young gen; a nullable-returning latestOrNull() would be allocation-free. I chose Optional to match latestValue() and make the empty case impossible to ignore, but if hot-path staleness checks matter more than the ergonomics, the allocation-free variant is the better shape and I would rather you chose it than have me guess. The cost is documented on the accessor and in the guide either way.

Summary by Sourcery

Provide an atomic latest-value snapshot API for reliable staleness checks without changing existing accessors.

New Features:

  • Add a public Topic.latest() API that returns the latest value and publish timestamp from one consistent snapshot, with Latest accessors for value, timestamp, and age.

Bug Fixes:

  • Prevent staleness checks from pairing values and timestamps from different publishes when readers are interrupted between separate accessor calls.

Enhancements:

  • Clarify the trade-offs and safe usage of snapshot and individual topic accessors, including allocation behavior and pre-publish timestamp handling.

Documentation:

  • Update the changelog, API reference, and topics guide with the snapshot-based latest-value API and staleness-check guidance.

Tests:

  • Add coverage for empty snapshots, consistent value and timestamp publication, age calculations, and newer-publish replacement; update concurrency coverage to validate snapshot reads.

`latestValue` and `latestPublishNanos` were each `synchronized` on the
Topic instance. That monitor is the most-used read on the bus, so it
serialised every reader against every writer. The value and its timestamp
are now published together as one immutable `Latest` pair behind a single
volatile field, which makes both accessors plain volatile reads.

The write path keeps a monitor, but only around the `nanoTime()` sample
and the store. Two volatile fields would not do: `publish` is reachable
from the OpMode loop, the hardware thread and the callback pool, so two
publishers can interleave between a value write and a timestamp write and
leave the pair describing two different publishes -- a regression against
the old monitor, which ordered those writes. Sampling the clock outside
mutual exclusion is not sufficient either: a publisher that samples and
is then preempted installs an OLDER snapshot after a newer one, so a
reader can hold an older value beside a newer timestamp and an age
computed from that stamp is under-reported. Holding the monitor across
the sample and the store keeps install order equal to timestamp order,
which is the property the old body had.

The type work is hoisted out of the publish path:

- `boxedType` is computed once in the constructor instead of
  re-normalising the declared type on every publish and every lookup.
  `Topic.type()` still reports the type the topic was created with; only
  the internal comparison field is boxed.
- `acceptsType(Class)` is the old
  `boxed(t.type()).isAssignableFrom(boxed(other))` with the topic side
  hoisted, replacing that expression at five call sites.
  `acceptsValueClass(Class)` delegates to it, so the two cannot drift.
  `boxed()` moves from `OrchestratorImpl` to `Topic`, next to the field
  it reads.

Two separate accessors can still straddle a publish, so the read order
remains: sample the timestamp first. That can only over-report the
value's age, never under-report it. This is documented on the public
accessors, in the CHANGELOG and in the topics guide, and the guide's
example had the unsafe order.

No API or behaviour change; `latestPublishNanos()` still returns 0
before the first publish. Measured on the repo benchmark
(`gradle :benchmarks:run --args='run --quick --scenarios S0 --styles
synapse'`, 8 alternating runs of baseline and branch on one box, p50
ns/op, median):

  micro.topic.latestValue              44 ns ->  28 ns  (-36%)
  micro.topic.recordLatest.1p1c       360 ns -> 192 ns  (-47%)
  micro.topic.recordLatest.4p4c       340 ns -> 268 ns  (-21%)
  micro.topic.recordLatest             89 ns ->  83 ns   (-7%)
  micro.publish.subscribers0         140 ns -> 161 ns  (+15%)

The read-path win is the durable one. The contended publish numbers are
much smaller than a fully lock-free write path would give, because the
write path keeps its monitor; that is the cost of not regressing the
timestamp ordering the old code had. `publish.subscribers0` and the
`rawDirectCall` control both moved by more than this box's noise floor in
the same runs, so read those two as inconclusive rather than as a
regression. Treat every number as a ratio on the box that produced it.
@sourcery-ai

sourcery-ai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Adds Topic.latest() as an additive, single-snapshot API that returns a published value with its timestamp and computes that value’s age, eliminating straddled two-accessor staleness checks; tests and documentation are updated to establish and explain the consistency guarantees.

Sequence diagram for consistent topic snapshot reads

sequenceDiagram
    participant Caller
    participant Topic
    participant Snapshot as Topic.Latest

    Caller->>Topic: latest()
    Topic-->>Caller: Optional<Latest<T>>
    Caller->>Snapshot: value()
    Snapshot-->>Caller: published value
    Caller->>Snapshot: ageNanos()
    Snapshot-->>Caller: age from publishNanos()
Loading

File-Level Changes

Change Details Files
Expose an immutable snapshot type and add a single-read latest-value API.
  • Make Topic.Latest<T> public with value, publish timestamp, age, and string accessors.
  • Add Topic.latest() returning Optional<Latest<T>>, empty before the first publish.
  • Retain existing accessors and behavior while routing them through the snapshot accessors.
src/main/java/com/aaravlabs/synapse/Topic.java
Update concurrency and API tests to validate snapshot consistency and age semantics.
  • Add coverage for empty state, accessor agreement, nonzero timestamps, age bounds, and replacement after a later publish.
  • Switch the concurrent reader to one latest() snapshot and remove the invalid cross-call consistency assertion.
  • Validate that snapshot values remain in range and ages correspond to their own publish timestamp.
src/test/java/com/aaravlabs/synapse/TopicLockFreeTest.java
Document the snapshot API as the preferred staleness-checking pattern.
  • Replace the topics guide's timestamp-first example with an Optional<Topic.Latest<T>> example.
  • Clarify that ageNanos() needs no pre-publish sentinel handling and that value-only callers should keep using latestValueOr.
  • Update public API and changelog guidance to prefer latest() while retaining the two-accessor ordering caveat.
website/src/content/docs/concepts/topics.mdx
CHANGELOG.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 416d6382-c557-4a6b-a3a5-25f183a64bb7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/main/java/com/aaravlabs/synapse/Topic.java" line_range="234-235" />
<code_context>
+     *
+     * @return an {@link Optional} holding the latest value and its publish timestamp
+     */
+    public Optional<Latest<T>> latest() {
+        return Optional.ofNullable(latest);
     }

</code_context>
<issue_to_address>
**issue (performance):** Every non-empty call to `latest()` creates a new `Optional` wrapper through `Optional.ofNullable(latest)`, so the new snapshot API allocates on each read and can add GC pressure on periodic or high-frequency readers. This contradicts the description's claim that the `Optional<Topic.Latest<T>>` shape allocates nothing.

**Triggers:** When callers poll `latest()` frequently, especially on the small-young-generation deployment described in the PR.

**Suggested fix:** Return a nullable snapshot from a separate allocation-free API, or document that `latest()` allocates an `Optional` wrapper and provide an allocation-free alternative for hot paths.
</issue_to_address>

### Comment 2
<location path="src/test/java/com/aaravlabs/synapse/TopicLockFreeTest.java" line_range="139-142" />
<code_context>
+                            return;
+                        }
+                        // ageNanos must agree with the stamp it was taken from.
+                        long age = snap.ageNanos();
+                        if (age <= 0 || age > System.nanoTime() - snap.publishNanos() + 1) {
+                            failure.compareAndSet(null, new AssertionError(
+                                    "implausible age " + age + " for stamp " + snap.publishNanos()));
                             return;
                         }
</code_context>
<issue_to_address>
**issue (testing):** The new tests require a strictly positive age and a strictly smaller age for the later publish, but `System.nanoTime()` is allowed to return the same tick for adjacent reads. A valid snapshot can therefore have `ageNanos() == 0`, and two publishes can temporarily report equal ages, causing the test to fail without a production bug.

**Triggers:** On platforms or runs where the monotonic clock has coarser resolution than the time between the snapshot and age read, or between the two publish checks.

**Suggested fix:** Use non-strict comparisons where clock resolution permits equality, or wait until the clock has advanced before asserting a strictly positive/younger age.
</issue_to_address>

### Comment 3
<location path="website/src/content/docs/concepts/topics.mdx" line_range="58-60" />
<code_context>
+// One snapshot read gives the value and its timestamp together, so the two can never
+// come from different publishes. ageNanos() is the age of this exact value, and needs no
+// special handling before the first publish -- latest() is simply empty then.
+Optional<Topic.Latest<Double>> flSnapshot = flTopic.latest();
+double fl2 = flSnapshot.map(Topic.Latest::value).orElse(0.0);
+long nanosSinceUpdate = flSnapshot.map(Topic.Latest::ageNanos).orElse(Long.MAX_VALUE);
 ```

</code_context>
<issue_to_address>
**nitpick:** The added documentation presents `Topic.latest()` as public API, but the API reference's `Topic<T>` table still omits both `latest()` and the public `Topic.Latest<T>` type. Users consulting the advertised complete public surface cannot discover or correctly use the new API.

**Triggers:** When users rely on `website/src/content/docs/api/index.mdx` or generated site navigation rather than the topics guide.

**Suggested fix:** Add `latest()` and the `Latest<T>` accessors (`value()`, `publishNanos()`, and `ageNanos()`) to the API reference and other maintained API-surface references.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: src/main/java/com/aaravlabs/synapse/Topic.java:235, src/test/java/com/aaravlabs/synapse/TopicLockFreeTest.java:142


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/main/java/com/aaravlabs/synapse/Topic.java
Comment thread src/test/java/com/aaravlabs/synapse/TopicLockFreeTest.java
Comment thread website/src/content/docs/concepts/topics.mdx

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread CHANGELOG.md Outdated
Comment thread src/test/java/com/aaravlabs/synapse/TopicLockFreeTest.java Outdated
Comment thread src/test/java/com/aaravlabs/synapse/TopicLockFreeTest.java Outdated
Comment thread src/main/java/com/aaravlabs/synapse/Topic.java
Comment thread website/src/content/docs/concepts/topics.mdx
@IamCoder18
IamCoder18 force-pushed the feat/latest-snapshot-accessor branch from d6d339a to 5cf48a7 Compare October 2, 2026 01:40

@sourcery-ai sourcery-ai 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.

Sourcery assessment

Approved.

Checking whether a published value is stale today means two calls:

    long stamp = topic.latestPublishNanos();
    T v = topic.latestValueOr(null);
    long age = System.nanoTime() - stamp;

Both accessors are individually correct, but a publish can land between
them, leaving the caller holding a value from one publish and a timestamp
from another. Reading the timestamp first makes that safe in one
direction -- the age can only be over-reported, never under-reported --
but over-reporting still discards a fresh value as stale, by up to a full
publish interval.

How often that happens depends on how long the two reads take. Normally
they are nanoseconds apart and a publish landing between them is
vanishingly rare. It stops being rare when the reader is preempted
between the two calls by a GC pause or the scheduler, since the window
becomes milliseconds. This library configures a small young gen, so young
GCs are part of the deployment rather than a hypothetical.

`latest()` returns the value and its timestamp from one snapshot read, so
the pair provably comes from a single publish and the window does not
exist:

    Optional<Topic.Latest<T>> snap = topic.latest();
    T v = snap.map(Topic.Latest::value).orElse(default);
    long age = snap.map(Topic.Latest::ageNanos).orElse(Long.MAX_VALUE);

Additive only. No existing signature or behaviour changes, and callers
that never check an age can keep using `latestValueOr` -- same single
volatile read.

`Topic.Latest` becomes public with `value()`, `publishNanos()` and
`ageNanos()`. `ageNanos()` is the age of *this* value computed from its
own stamp, which also removes the pre-publish `0` sentinel that the
two-call form has to guard against by hand.

`latestPublishNanos()` still returns 0 before the first publish, so the
guard stays necessary for anyone composing it by hand. It is documented as
such in the javadoc, the CHANGELOG and the topics guide.

The concurrency test's reader now uses `latest()`, which is the stronger
property: no straddle is representable, so the "timestamp first"
ordering rule has nothing left to guard. That test previously carried a
cross-check between `latest()` being empty and `latestPublishNanos()`
being nonzero; it is gone, because a publish can legitimately land
between those two reads and a nonzero stamp there says nothing about
consistency.

Suite: 61 tests, 0 failures.
@IamCoder18
IamCoder18 force-pushed the feat/latest-snapshot-accessor branch from 5cf48a7 to eeef85d Compare October 2, 2026 01:52
@IamCoder18
IamCoder18 force-pushed the perf/topic-lock-free branch from 54d881e to f801502 Compare October 2, 2026 01:53
`latestValue` and `latestPublishNanos` were each `synchronized` on the
Topic instance. That monitor is the most-used read on the bus, so it
serialised every reader against every writer. The value and its timestamp
are now published together as one immutable `Latest` pair behind a single
volatile field, which makes both accessors plain volatile reads.

The write path keeps a monitor, but only around the `nanoTime()` sample
and the store. Two volatile fields would not do: `publish` is reachable
from the OpMode loop, the hardware thread and the callback pool, so two
publishers can interleave between a value write and a timestamp write and
leave the pair describing two different publishes -- a regression against
the old monitor, which ordered those writes. Sampling the clock outside
mutual exclusion is not sufficient either: a publisher that samples and
is then preempted installs an OLDER snapshot after a newer one, so a
reader can hold an older value beside a newer timestamp and an age
computed from that stamp is under-reported. Holding the monitor across
the sample and the store keeps install order equal to timestamp order,
which is the property the old body had.

The type work is hoisted out of the publish path:

- `boxedType` is computed once in the constructor instead of
  re-normalising the declared type on every publish and every lookup.
  `Topic.type()` still reports the type the topic was created with; only
  the internal comparison field is boxed.
- `acceptsType(Class)` is the old
  `boxed(t.type()).isAssignableFrom(boxed(other))` with the topic side
  hoisted, replacing that expression at five call sites.
  `acceptsValueClass(Class)` delegates to it, so the two cannot drift.
  `boxed()` moves from `OrchestratorImpl` to `Topic`, next to the field
  it reads.

Two separate accessors can still straddle a publish, so the read order
remains: sample the timestamp first. That can only over-report the
value's age, never under-report it. This is documented on the public
accessors, in the CHANGELOG and in the topics guide, and the guide's
example had the unsafe order.

No API or behaviour change; `latestPublishNanos()` still returns 0
before the first publish. Measured on the repo benchmark
(`gradle :benchmarks:run --args='run --quick --scenarios S0 --styles
synapse'`, 8 alternating runs of baseline and branch on one box, p50
ns/op, median):

  micro.topic.latestValue              44 ns ->  28 ns  (-36%)
  micro.topic.recordLatest.1p1c       360 ns -> 192 ns  (-47%)
  micro.topic.recordLatest.4p4c       340 ns -> 268 ns  (-21%)
  micro.topic.recordLatest             89 ns ->  83 ns   (-7%)
  micro.publish.subscribers0         140 ns -> 161 ns  (+15%)

The read-path win is the durable one. The contended publish numbers are
much smaller than a fully lock-free write path would give, because the
write path keeps its monitor; that is the cost of not regressing the
timestamp ordering the old code had. `publish.subscribers0` and the
`rawDirectCall` control both moved by more than this box's noise floor in
the same runs, so read those two as inconclusive rather than as a
regression. Treat every number as a ratio on the box that produced it.
@IamCoder18
IamCoder18 force-pushed the perf/topic-lock-free branch from f801502 to b4dcf33 Compare October 2, 2026 01:55
Brings #29's final review fixes onto the accessor branch so it is not based
on a stale parent: the per-publish allocation is now documented in the
write-path comment, the javadoc and the CHANGELOG, and the 0-before-first-
publish caveat is stated as a heuristic rather than a proof.
System.nanoTime() is permitted to return 0, so a 0 stamp is not proof that
nothing has been published -- a genuine publish can carry one too. Stated
in the latestPublishNanos() javadoc, the latestValue() example, the
topics guide and the CHANGELOG, along with which way the resulting
over-reported age fails: it rejects a fresh value rather than admitting a
stale one.

latest() on this branch removes the question entirely, since one snapshot
read needs no sentinel.
@IamCoder18
IamCoder18 force-pushed the perf/topic-lock-free branch from b4dcf33 to 34f8ae8 Compare October 2, 2026 02:56
Syncs the accessor branch with perf/topic-lock-free's final head, keeping
latest() and its test. Applies the last three findings:

- Drop the hard-coded 24-byte Latest size; object size is runtime-dependent
  and this library also targets FTC/Android VMs.
- Scope the 'read accessors do not allocate' claim to latestValueOr().
  latestValue() and latest() both wrap their result in an Optional, which
  they did before this branch.
- Guard the CHANGELOG age snippet against the pre-publish 0 stamp, matching
  the javadoc and the topics guide.

Tests 61/61, javadoc at the pre-existing 2-warning baseline.
@IamCoder18
IamCoder18 deleted the branch perf/topic-lock-free October 2, 2026 16:05
@IamCoder18 IamCoder18 closed this Oct 2, 2026
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.

1 participant