Repository navigation
Adds the monotonic generator with an injectable draw source - #7
Merged
Merged
Conversation
Monotonic is an object the caller holds. It keeps the last time and random value per prefix and random width, as the wire format's monotonic rule states: a later time takes a fresh seed, a time at or before the last one steps the random field by a value drawn uniformly from 1 to 2^(4w), and an overflow moves the time one millisecond on with a fresh seed. A monotonic xs uses compact time unless the time form is set, since a standard xs has no field to step. The draws come from each call's byte source by default, the step masked from ceil(w / 2) bytes so it is uniform with no modulo bias. A DrawSource replaces them to replay known values, which is how the corpus's monotonic cases are reproduced exactly. A ReentrantLock inside the instance orders the calls, so one generator shared between threads still gives strictly increasing ids; a multi-thread test proves it. Nothing is kept in a static field. A from input is refused with from_with_monotonic and fixed random bytes with rand_with_monotonic, and a call that throws leaves the state as it was; the javadoc says so and tests pin it. Refs: uxjv-5tk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Ids minted in the same millisecond have to increase strictly, and one generator may be shared between threads. The wire format's monotonic rule says how; the state has to live in an object the caller holds, never in a static field.
What
Monotonic, a final class:new Monotonic()andnew Monotonic(DrawSource), withgenerate()andgenerate(UxidOptions). Each call takes the same optionsUxid.generatetakes, and the generator keeps the last timeTand random valueRper prefix and per random width, inside the instance.T,R + stepatT, the step uniform over 1 to 2^(4w); whenR + stepreaches 2^(8w),T + 1with a fresh seed. The clock is read per call, as in generation, unless the options fix the time.DrawSource(seed(width)andstep(width)) replaces the draws to replay known values. By default the draws come from each call's byte source: a seed iswbytes, and a step is the low4wbits ofceil(w / 2)bytes plus one, so it is uniform with no modulo bias and nothing redrawn.ReentrantLockinside the instance orders the calls: the time is read, the draws made and the state updated with the lock held. The javadoc says so; nothing is kept in a static field.frominput is refused withfrom_with_monotonicand fixed random bytes withrand_with_monotonic, checked in that order before anything else.Tdraws a new step and answers an id atTwhen that step does not overflow.xsuses compact time unless the options set the time form, as the wire format says; the random width must be at least one byte, so anxswith an explicit standard time and no random width is refused.The
ReentrantLockinside the instance and the multi-thread test were decided by the conductor under a standing consent, 2026-10-21.Tests
monotonic.jsonand both monotonic refusals ofinvalid.jsonfrom the uxid-ex v2.10.0 corpus, each named by its case id. The draws are replayed through aDrawSource, and each case asserts every recorded draw was taken.from,randand random-width refusals the next id isR + step; after an overflow onto the compact marker the next call steps fromT; after a draw outside its range the next id steps fromR.wbytes for a seed andceil(w / 2)for a step, and the step is masked (an all-ones byte gives 2^(4w)).Notes
Choices the bead did not fix:
generate(UxidOptions)), so one generator serves several prefixes and widths, as the rule keeps state per prefix and random width; the two refusals are therefore per call, and a refused call is defined to change nothing.DrawSource.seedanswers bytes, not an integer, so a 10-byte field needs no wider type; a seed of the wrong length, or a step outside 1 to 2^(4w), throwsIllegalStateException, as a byte source answering the wrong count already does.IllegalArgumentException: the wire format names no generation error for it. So does an overflow in millisecond 2^48 - 1, the last a time field holds, as a clock outside the time field already does.Gate:
mise run gategreen on this head (format check, compile with Error Prone, tests, JaCoCo, the javadoc and sources jars).Bead: uxjv-5tk