Repository navigation
Commit 9624924
authored
feat(topic): add Topic.latest() for a single-read value + timestamp (#31)
* feat(topic): add Topic.latest() for a single-read value + timestamp
Checking whether a published value is stale needs 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(defaultValue);
long age = snap.map(Topic.Latest::ageNanos).orElse(Long.MAX_VALUE);
Additive only. No existing signature or behaviour changes. Callers that
never check an age keep using `latestValueOr` -- the same single volatile
read, and allocation-free.
`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 by hand.
`latestPublishNanos()` still returns 0 before the first publish, so that
guard stays necessary for anyone composing by hand, and it is documented
as a heuristic rather than a proof -- System.nanoTime() is permitted to
return 0 too. It is stated 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. Its cross-check between
`latest()` being empty and `latestPublishNanos()` being nonzero is gone,
because a publish can legitimately land between those two reads.
Suite: 86 tests, 0 failures. javadoc: no errors, no new warnings.
* test(topic): drop absolute time bounds; document empty-read allocation
- Remove the one-second age ceiling. A GC pause between the publish and
the ageNanos() read legitimately makes the age arbitrarily large, so the
bound imposed a scheduling deadline on the test. Both bounds are now
relative to the clock.
- Remove the nonzero publishNanos() assertion. System.nanoTime() has an
arbitrary origin, so a published snapshot may carry 0; the present
Optional already proves a publish happened.
- latest() allocates an Optional wrapper only when it returns a value.
Empty reads return the shared Optional.empty() and allocate nothing,
which is now what the javadoc and the topics guide say.
Suite 86/86, javadoc clean.
* test(topic): bracket ageNanos() between two clock reads
Three review findings on the snapshot tests:
- ageNanos() was only bounded from above, so an implementation returning a
constant 0 satisfied the check while reporting nothing about the real age.
Bracket the call between two clock reads in both the unit test and the
concurrency reader, and require the age to fall between both deltas from
the snapshot's own stamp.
- second.ageNanos() < first.ageNanos() sampled at two different moments, so
a pause between the publish and the second read could make the newer value
look older. Derive both ages from a single clock reading: from one now, a
strictly later stamp is a strictly smaller age.
The one-second wall-clock ceiling and the nonzero-stamp assertion reported
earlier were already removed in the previous commit; these reviews landed
on the older SHA.
Suite 86/86, tear test green on a 2-core-pinned runner, javadoc clean.
* docs(topic): unwrap the snapshot once in every example
The value-and-age example called Optional.map() once per field, so each
non-empty read allocated two more Optional wrappers and boxed the long
age -- in a guide aimed at periodic loops, teaching the allocating form
three times over (the latest() wrapper plus two map() calls).
Every example now unwraps with isPresent()/get() and calls value() and
ageNanos() directly: the topics guide, the javadoc and the CHANGELOG.
* test(topic): bracket ageNanos() between clock reads taken around the call
The previous bounds sampled a clock reading outside the ageNanos() call, so
they could not bound the result in either direction: a pause between the
sampling and the call breaks the lower bound, and the age correctly
exceeds a reading taken before it.
Both the unit test and the concurrency reader now take one clock reading
immediately before the call and one immediately after, and require the age
to fall between the two deltas from the snapshot's own stamp. That is the
only bracketing that constrains the value, and it holds regardless of how
long the call itself takes.
Suite 86/86 across 8 consecutive runs, tear test green pinned to two
cores, javadoc clean.1 parent 2a8f572 commit 9624924
5 files changed
Lines changed: 281 additions & 55 deletions
File tree
- src
- main/java/com/aaravlabs/synapse
- test/java/com/aaravlabs/synapse
- website/src/content/docs
- api
- concepts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
| 99 | + | |
| 100 | + | |
103 | 101 | | |
104 | 102 | | |
105 | | - | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
106 | 125 | | |
107 | 126 | | |
108 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
53 | 61 | | |
54 | 62 | | |
55 | 63 | | |
56 | 64 | | |
57 | 65 | | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
58 | 102 | | |
59 | 103 | | |
60 | 104 | | |
| |||
74 | 118 | | |
75 | 119 | | |
76 | 120 | | |
77 | | - | |
78 | | - | |
79 | | - | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
80 | 124 | | |
81 | 125 | | |
82 | 126 | | |
83 | | - | |
| 127 | + | |
84 | 128 | | |
85 | 129 | | |
86 | 130 | | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | 131 | | |
91 | 132 | | |
92 | 133 | | |
93 | 134 | | |
94 | 135 | | |
95 | 136 | | |
96 | 137 | | |
97 | | - | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
98 | 141 | | |
99 | 142 | | |
100 | 143 | | |
| |||
119 | 162 | | |
120 | 163 | | |
121 | 164 | | |
122 | | - | |
| 165 | + | |
123 | 166 | | |
124 | 167 | | |
125 | 168 | | |
| |||
134 | 177 | | |
135 | 178 | | |
136 | 179 | | |
137 | | - | |
| 180 | + | |
| 181 | + | |
138 | 182 | | |
139 | 183 | | |
140 | 184 | | |
| |||
177 | 221 | | |
178 | 222 | | |
179 | 223 | | |
180 | | - | |
| 224 | + | |
| 225 | + | |
181 | 226 | | |
182 | 227 | | |
183 | 228 | | |
184 | 229 | | |
185 | 230 | | |
186 | 231 | | |
187 | | - | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
188 | 288 | | |
189 | 289 | | |
190 | 290 | | |
| |||
Lines changed: 107 additions & 22 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
81 | 84 | | |
82 | 85 | | |
83 | 86 | | |
| |||
106 | 109 | | |
107 | 110 | | |
108 | 111 | | |
109 | | - | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
117 | | - | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
118 | 126 | | |
119 | 127 | | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | | - | |
126 | | - | |
127 | | - | |
128 | | - | |
129 | | - | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
130 | 131 | | |
131 | | - | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
132 | 148 | | |
133 | | - | |
134 | | - | |
| 149 | + | |
135 | 150 | | |
136 | 151 | | |
137 | 152 | | |
| |||
240 | 255 | | |
241 | 256 | | |
242 | 257 | | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
243 | 328 | | |
244 | 329 | | |
245 | 330 | | |
| |||
0 commit comments