-
Notifications
You must be signed in to change notification settings - Fork 7
Improve OrderableSlsVersion model and simplify comparison logic
#1183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Generate changelog in
|
| * The release candidate version number, if this version is a release candidate or release candidate snapshot. | ||
| */ | ||
| @Value.Derived | ||
| OptionalInt rcVersionNumber() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making this and the below intentionally package-private per #1179 (comment)
| import java.util.OptionalInt; | ||
|
|
||
| /** Compares {@link OrderableSlsVersion}s by "newness", i.e., "1.4.0" is greater/newer/later than "1.2.1", etc.. */ | ||
| public enum VersionComparator implements Comparator<OrderableSlsVersion> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fwiw, at this point, this entire comparator could simply be:
Comparator.comparingInt(OrderableSlsVersion::getMajorVersionNumber)
.thenComparingInt(OrderableSlsVersion::getMinorVersionNumber)
.thenComparingInt(OrderableSlsVersion::getPatchVersionNumber)
.thenComparingInt(version -> version.rcVersionNumber().orElse(Integer.MAX_VALUE))
.thenComparingInt(version -> version.snapshotVersionNumber().orElse(Integer.MIN_VALUE));
but I'm not sure if this would have a performance cost, and I can't make the VersionComparator.INSTANCE be this either
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For widely used library code like this, it's almost certainly better to write this ourselves instead of using a comparator with a bunch of lambdas.
| distanceFromVersion = version.firstSequenceVersionNumber().orElse(0); | ||
| } | ||
| int rcNumber = version.rcVersionNumber().orElse(0); | ||
| int distanceFromVersion = version.snapshotVersionNumber().orElse(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| int distanceFromVersion = version.snapshotVersionNumber().orElse(0); | |
| int snapshotNumber = version.snapshotVersionNumber().orElse(0); |
| * The release candidate version number, if this version is a release candidate or release candidate snapshot. | ||
| */ | ||
| @Value.Derived | ||
| OptionalInt rcVersionNumber() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should probably go on SlsVersion - that's where all other fields are declared. Even ones that are only relevant for OrderableSlsVersion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While that's true, these don't make sense outside of OrderableSlsVersion 🤔 If anything, I'd probably want to move down/remove the XXXsequenceVersionNumber methods from SlsVersion since they're never defined in NonOrderableSlsVersion
| /** | ||
| * The release candidate version number, if this version is a release candidate or release candidate snapshot. | ||
| */ | ||
| @Value.Derived | ||
| OptionalInt rcVersionNumber() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It feels more correct to drop the version - these are just "numbers" not "version numbers". That's also consistent with verbiage we use elsewhere.
| /** | |
| * The release candidate version number, if this version is a release candidate or release candidate snapshot. | |
| */ | |
| @Value.Derived | |
| OptionalInt rcVersionNumber() { | |
| /** | |
| * The release candidate number, if this version is a release candidate or release candidate snapshot. | |
| */ | |
| @Value.Derived | |
| OptionalInt rcNumber() { |
| /** | ||
| * The snapshot version number, if this version is a release snapshot or release candidate snapshot. | ||
| */ | ||
| @Value.Derived | ||
| OptionalInt snapshotVersionNumber() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
| /** | |
| * The snapshot version number, if this version is a release snapshot or release candidate snapshot. | |
| */ | |
| @Value.Derived | |
| OptionalInt snapshotVersionNumber() { | |
| /** | |
| * The snapshot number, if this version is a release snapshot or release candidate snapshot. | |
| */ | |
| @Value.Derived | |
| OptionalInt snapshotNumber() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see in your example commit that you used snapshotVersion rather than snapshotNumber. Is there one you'd prefer? I'm even considering just snapshot itself fwiw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like snapshotNumber. It's not a version but it is a number. snapshot sounds to vague.
| if (getType() == SlsVersionType.RELEASE_CANDIDATE || getType() == SlsVersionType.RELEASE_CANDIDATE_SNAPSHOT) { | ||
| // If the version is a release candidate (or RC snapshot), | ||
| // the first sequence number is always the RC version number. | ||
| return firstSequenceVersionNumber(); | ||
| } | ||
| return OptionalInt.empty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer to use a switch without a default case to ensure we're considering every type. I think it reads a bit nicer too.
| public abstract int getPatchVersionNumber(); | ||
|
|
||
| /** | ||
| * The first version number after major, minor, and patch. Typically either RC or snapshot version number. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nits:
- Avoid abbrevations
- The "typically" seems unnecessary - we know exactly when this will be set.
| * The first version number after major, minor, and patch. Typically either RC or snapshot version number. | |
| * The first version number after major, minor, and patch. The release candidate number, if this version is a release candidate or a release candidate snapshot, and the snapshot number, if this version is a release snapshot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We know exactly when this will be set.
That's not quite true, since this is a field on SlsVersion, not on OrderableSlsVersion. It's not quite determined that this must be empty if not an RC or a snapshot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These fields will always be empty on a NonOrderableSlsVersion, right?
sls-version-java/sls-versions/src/main/java/com/palantir/sls/versions/NonOrderableSlsVersion.java
Lines 48 to 54 in bc4fc92
| return Optional.of(new NonOrderableSlsVersion.Builder() | |
| .value(value) | |
| .majorVersionNumber(groups.groupAsInt(1)) | |
| .minorVersionNumber(groups.groupAsInt(2)) | |
| .patchVersionNumber(groups.groupAsInt(3)) | |
| .type(SlsVersionType.NON_ORDERABLE) | |
| .build()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They will, but that's not part of the definition of SlsVersion. This is an implementation detail. I meant that semantically, nothing would prevent someone from building their own NonOrderableSlsVersion and setting these.
With that said, that isn't done as far as a I know, and I'm not sure how you'd really even do it in the first place, considering there isn't necessarily a sequence version number for non-orderable versions.
But that's why I believe these shouldn't live in SlsVersion in the first place and why I'd like to move rc and snapshot back down into OrderableSlsVersion (since they don't make sense in NonOrderableSlsVersion)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's why I believe these shouldn't live in SlsVersion in the first place and why I'd like to move rc and snapshot back down into OrderableSlsVersion (since they don't make sense in NonOrderableSlsVersion)
Sure that makes sense. Putting these values in SlsVersion in the first place was probably a mistake.
| public abstract OptionalInt firstSequenceVersionNumber(); | ||
|
|
||
| /** | ||
| * The second version number after major, minor, and patch. Typically snapshot version number for RC snapshots. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * The second version number after major, minor, and patch. Typically snapshot version number for RC snapshots. | |
| * The first version number after major, minor, and patch. The snapshot number, if this version is a release candidate snapshot |
| // If RC version is not present, treat it as meaning positive infinite, | ||
| // as all releases/release snapshots are considered newer than any RC. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Avoid abbreviations and ambiguous / usage
| // If RC version is not present, treat it as meaning positive infinite, | |
| // as all releases/release snapshots are considered newer than any RC. | |
| // If release candidate version is not present, treat it as the maximum value. | |
| // All release and release snapshots versions are newer than any release candidate or release candidate snapshot version. |
| // If snapshot version is not present, treat it as meaning negative infinite, | ||
| // as all snapshots are considered newer than the release/RC they're tied to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // If snapshot version is not present, treat it as meaning negative infinite, | |
| // as all snapshots are considered newer than the release/RC they're tied to. | |
| // If snapshot version is not present, treat it as the minimum value. | |
| // All release snapshots and release candidate snapshots versions are newer than their corresponding release and release candidate versions, respectively. |
| /** | ||
| * The release candidate version number, if this version is a release candidate or release candidate snapshot. | ||
| */ | ||
| @Value.Derived |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can do better than @Value.Derived.
Here's a commit that:
- Makes the parsing logic a bit more explicit, allowing us to avoid
@Value.Derived - Avoids builder allocations
- Eliminates unnecessary fields from
ImmutableNonOrderableSlsVersion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I'd probably take this one step further and eliminate the firstSequenceVersionNumber and secondSequenceVersionNumber fields altogether. I'm not really concerned about performance if we're not using in an internal, potentially hot, code path like like the comparator - a single, trivial, non-allocating switch statement should be more than acceptable for an accessor method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this approach (and was able to verify that we're not degrading the parsing perf/allocations either). One thing to note though is that this introduces an ABI break by removing the builder methods for firstSequenceVersionNumber and secondSequenceVersionNumber (the first one is used in at least one project internally, though usage should be trivial to fix)
|
I'm on board with this change. I think I was hesitant to make a larger change to this API, but I think it's worthwhile to make the API a bit more sensible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are the JMH comparisons:
Develop
Benchmark (versionString) Mode Cnt Score Error Units
SlsVersionBenchmark.safeValueOf RELEASE avgt 3 16.873 ± 0.898 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate RELEASE avgt 3 12659.582 ± 666.309 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm RELEASE avgt 3 56.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count RELEASE avgt 3 187.000 counts
SlsVersionBenchmark.safeValueOf:gc.time RELEASE avgt 3 106.000 ms
SlsVersionBenchmark.safeValueOf SNAPSHOT avgt 3 329.685 ± 78.529 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate SNAPSHOT avgt 3 9349.215 ± 2210.692 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm SNAPSHOT avgt 3 808.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count SNAPSHOT avgt 3 193.000 counts
SlsVersionBenchmark.safeValueOf:gc.time SNAPSHOT avgt 3 102.000 ms
SlsVersionBenchmark.safeValueOf RC avgt 3 127.903 ± 15.892 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate RC avgt 3 10497.571 ± 1301.039 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm RC avgt 3 352.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count RC avgt 3 179.000 counts
SlsVersionBenchmark.safeValueOf:gc.time RC avgt 3 93.000 ms
SlsVersionBenchmark.safeValueOf RC_SNAPSHOT avgt 3 276.389 ± 6.290 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate RC_SNAPSHOT avgt 3 8059.568 ± 184.260 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm RC_SNAPSHOT avgt 3 584.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count RC_SNAPSHOT avgt 3 198.000 counts
SlsVersionBenchmark.safeValueOf:gc.time RC_SNAPSHOT avgt 3 96.000 ms
SlsVersionBenchmark.safeValueOf DIRTY avgt 3 362.697 ± 6.779 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate DIRTY avgt 3 7151.281 ± 132.109 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm DIRTY avgt 3 680.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count DIRTY avgt 3 176.000 counts
SlsVersionBenchmark.safeValueOf:gc.time DIRTY avgt 3 85.000 ms
SlsVersionBenchmark.safeValueOf DIRTY_RC avgt 3 344.146 ± 336.398 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate DIRTY_RC avgt 3 7547.803 ± 7256.864 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm DIRTY_RC avgt 3 680.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count DIRTY_RC avgt 3 178.000 counts
SlsVersionBenchmark.safeValueOf:gc.time DIRTY_RC avgt 3 92.000 ms
Current state of the PR:
Benchmark (versionString) Mode Cnt Score Error Units
SlsVersionBenchmark.safeValueOf RELEASE avgt 3 16.798 ± 0.887 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate RELEASE avgt 3 12715.520 ± 667.856 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm RELEASE avgt 3 56.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count RELEASE avgt 3 189.000 counts
SlsVersionBenchmark.safeValueOf:gc.time RELEASE avgt 3 100.000 ms
SlsVersionBenchmark.safeValueOf SNAPSHOT avgt 3 270.576 ± 22.299 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate SNAPSHOT avgt 3 11390.574 ± 935.650 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm SNAPSHOT avgt 3 808.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count SNAPSHOT avgt 3 194.000 counts
SlsVersionBenchmark.safeValueOf:gc.time SNAPSHOT avgt 3 101.000 ms
SlsVersionBenchmark.safeValueOf RC avgt 3 139.038 ± 3.726 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate RC avgt 3 9656.714 ± 257.133 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm RC avgt 3 352.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count RC avgt 3 194.000 counts
SlsVersionBenchmark.safeValueOf:gc.time RC avgt 3 97.000 ms
SlsVersionBenchmark.safeValueOf RC_SNAPSHOT avgt 3 235.163 ± 38.654 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate RC_SNAPSHOT avgt 3 9472.974 ± 1551.075 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm RC_SNAPSHOT avgt 3 584.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count RC_SNAPSHOT avgt 3 161.000 counts
SlsVersionBenchmark.safeValueOf:gc.time RC_SNAPSHOT avgt 3 86.000 ms
SlsVersionBenchmark.safeValueOf DIRTY avgt 3 247.986 ± 12.286 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate DIRTY avgt 3 10459.128 ± 520.746 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm DIRTY avgt 3 680.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count DIRTY avgt 3 215.000 counts
SlsVersionBenchmark.safeValueOf:gc.time DIRTY avgt 3 110.000 ms
SlsVersionBenchmark.safeValueOf DIRTY_RC avgt 3 242.851 ± 1.067 ns/op
SlsVersionBenchmark.safeValueOf:gc.alloc.rate DIRTY_RC avgt 3 10680.313 ± 47.130 MB/sec
SlsVersionBenchmark.safeValueOf:gc.alloc.rate.norm DIRTY_RC avgt 3 680.000 ± 0.001 B/op
SlsVersionBenchmark.safeValueOf:gc.count DIRTY_RC avgt 3 198.000 counts
SlsVersionBenchmark.safeValueOf:gc.time DIRTY_RC avgt 3 101.000 ms
| RC_SNAPSHOT("0.16.0-rc1-8-g116b425"), | ||
| DIRTY("0.16.0-8-g116b425.dirty"), | ||
| DIRTY_RC("0.16.0-rc1-8-g116b425.dirty"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a change to the benchmark here, since RC_SNAPSHOT was actually a dirty version
| @Override | ||
| public final OptionalInt rcNumber() { | ||
| return OptionalInt.empty(); | ||
| } | ||
|
|
||
| @Override | ||
| public final OptionalInt snapshotNumber() { | ||
| return OptionalInt.empty(); | ||
| } | ||
|
|
||
| @Override | ||
| public final SlsVersionType getType() { | ||
| return SlsVersionType.NON_ORDERABLE; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, these introduce an ABI break on the builder type. Somehow rev-api doesn't seem to run, so I'll accept these and ensure rev-api runs in a separate PR
Same comment for OrderableSlsVersion below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the lack of revapi check was a bug in gradle-revapi, which was fixed on develop by migrating to https://github.com/revapi/gradle-revapi as part of #1184
I rebased the PR
| if (groups != null) { | ||
| return Optional.of(construct(type, value, groups)); | ||
| } | ||
| MatchResult groups = SlsVersionType.RELEASE.getParser().tryParse(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not particularly fond of doing multiple parsings, when we could trivially extend the ReleaseVersionParser to parse everything for us (and be more efficient too).
I'm going to look into that in a separate follow-up PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /** | ||
| * The release candidate version number, if this version is a release candidate or release candidate snapshot. | ||
| */ | ||
| public abstract OptionalInt rcNumber(); | ||
|
|
||
| public abstract OptionalInt secondSequenceVersionNumber(); | ||
| /** | ||
| * The snapshot version number, if this version is a release snapshot or release candidate snapshot. | ||
| */ | ||
| public abstract OptionalInt snapshotNumber(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a break too, for anybody extending SlsVersion, though as far as I can tell, this isn't done on any of our projects
| /** | ||
| * The priority was used to have a predetermined order of version types, to ease comparison, | ||
| * but suffered problems due to RELEASE_CANDIDATE_SNAPSHOT and RELEASE_CANDIDATE versions not being systematically | ||
| * ordered (e.g. 1.2.3-rc1 < 1.2.3-rc1-1-gabc < 1.2.3-rc2 < 1.2.3-rc2-1-gdef). | ||
| * @deprecated This field is deprecated and will be removed in a future release. | ||
| */ | ||
| @Deprecated(forRemoval = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might need to remove this if we're still concerned about deprecations blocking upgrades
5067776 to
43cc394
Compare
|
There are a couple of issues with this PR:
In the end, the best tradeoff may actually be the original #1179. Ideally, we'd be able to improve the data model, but doing so has a cost on either speed or memory, so it's not particularly worth it, considering the approach in that other PR is still sensible |
Before this PR
See #1179 and discussion starting from #1179 (review)
The comparison logic in
VersionComparatoris fairly convoluted, because we tried to order version types using priorities. ButRELEASE_CANDIDATEis not universally ordered with respect toRELEASE_CANDIDATE_SNAPSHOT(for instance,1.2.3-rc1 < 1.2.3-rc1-1-gabc < 1.2.3-rc2). This means you can't simply define a priority on version types that you can leverage to order the versions of different types.Instead, we can simply model
OrderableSlsVersionas having 5 components:This leads to a simple lexicographic comparison of the different numbers, in order:
1.0.0-rc1[1, 0, 0, 1, -infinite]1.0.0-rc1-1-gabcedf[1, 0, 0, 1, 1]1.0.0-rc1-2-gabcedf[1, 0, 0, 1, 2]1.0.0-rc2[1, 0, 0, 2, -infinite]1.0.0[1, 0, 0, +infinite, -infinite]1.0.0-1-gabcedf[1, 0, 0, +infinite, 1]1.0.0-2-gabcedf[1, 0, 0, +infinite, 2]1.0.1-rc1[1, 0, 1, 1, -infinite]1.0.1[1, 0, 1, +infinite, -infinite]1.1.0[1, 1, 0, +infinite, -infinite]2.0.0[2, 0, 0, +infinite, -infinite]After this PR
==COMMIT_MSG==
Improve OrderableSlsVersion model and simplify comparison logic
==COMMIT_MSG==
Possible downsides?