Skip to content

Commit 8f9245b

Browse files
committed
NoSQL: add jcstress tests for id-generator + monotonic clock
1 parent 736a856 commit 8f9245b

File tree

3 files changed

+287
-0
lines changed

3 files changed

+287
-0
lines changed

components/idgen/impl/build.gradle.kts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
plugins {
2121
alias(libs.plugins.jandex)
2222
alias(libs.plugins.jmh)
23+
alias(libs.plugins.jcstress)
2324
id("polaris-server")
2425
}
2526

@@ -58,3 +59,11 @@ dependencies {
5859
jmhImplementation(libs.jmh.core)
5960
jmhAnnotationProcessor(libs.jmh.generator.annprocess)
6061
}
62+
63+
tasks.named("jcstressJar") { dependsOn("jandex") }
64+
65+
tasks.named("compileJcstressJava") { dependsOn("jandex") }
66+
67+
tasks.named("check") { dependsOn("jcstress") }
68+
69+
jcstress { jcstressDependency = libs.jcstress.core.get().toString() }
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.ids.impl;
20+
21+
import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;
22+
import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN;
23+
import static org.openjdk.jcstress.annotations.Expect.UNKNOWN;
24+
25+
import java.time.Instant;
26+
import org.openjdk.jcstress.annotations.Actor;
27+
import org.openjdk.jcstress.annotations.Arbiter;
28+
import org.openjdk.jcstress.annotations.Description;
29+
import org.openjdk.jcstress.annotations.JCStressTest;
30+
import org.openjdk.jcstress.annotations.Outcome;
31+
import org.openjdk.jcstress.annotations.State;
32+
import org.openjdk.jcstress.infra.results.II_Result;
33+
34+
public class MonotonicClockStress {
35+
public static final MonotonicClockImpl CLOCK = new MonotonicClockImpl().start();
36+
37+
@JCStressTest
38+
@Description("Verify that monotonicity is guaranteed across different threads (nanos).")
39+
@Outcome.Outcomes({
40+
@Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Both newer"),
41+
@Outcome(id = "1, 0", expect = ACCEPTABLE, desc = "Newer + same time"),
42+
@Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Same time + newer"),
43+
@Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Both same time"),
44+
@Outcome(id = "-1, .*", expect = FORBIDDEN, desc = "Clock must not go backwards"),
45+
@Outcome(id = ".*, -1", expect = FORBIDDEN, desc = "Clock must not go backwards"),
46+
@Outcome(expect = UNKNOWN, desc = "Not sure what happened"),
47+
})
48+
@State()
49+
public static class Nanos {
50+
long ref;
51+
52+
long v1;
53+
long v2;
54+
55+
public Nanos() {
56+
ref = CLOCK.nanoTime();
57+
}
58+
59+
@Actor
60+
public void actor1() {
61+
v1 = CLOCK.nanoTime();
62+
}
63+
64+
@Actor
65+
public void actor2() {
66+
v2 = CLOCK.nanoTime();
67+
}
68+
69+
@Arbiter
70+
public void arbiter(II_Result r) {
71+
r.r1 = Long.compare(v1, ref);
72+
r.r2 = Long.compare(v2, ref);
73+
}
74+
}
75+
76+
@JCStressTest
77+
@Description("Verify that monotonicity is guaranteed across different threads (micros).")
78+
@Outcome.Outcomes({
79+
@Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Both newer"),
80+
@Outcome(id = "1, 0", expect = ACCEPTABLE, desc = "Newer + same time"),
81+
@Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Same time + newer"),
82+
@Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Both same time"),
83+
@Outcome(id = "-1, .*", expect = FORBIDDEN, desc = "Clock must not go backwards"),
84+
@Outcome(id = ".*, -1", expect = FORBIDDEN, desc = "Clock must not go backwards"),
85+
@Outcome(expect = UNKNOWN, desc = "Not sure what happened"),
86+
})
87+
@State()
88+
public static class Micros {
89+
long ref;
90+
91+
long v1;
92+
long v2;
93+
94+
public Micros() {
95+
ref = CLOCK.currentTimeMicros();
96+
}
97+
98+
@Actor
99+
public void actor1() {
100+
v1 = CLOCK.currentTimeMicros();
101+
}
102+
103+
@Actor
104+
public void actor2() {
105+
v2 = CLOCK.currentTimeMicros();
106+
}
107+
108+
@Arbiter
109+
public void arbiter(II_Result r) {
110+
r.r1 = Long.compare(v1, ref);
111+
r.r2 = Long.compare(v2, ref);
112+
}
113+
}
114+
115+
@JCStressTest
116+
@Description("Verify that monotonicity is guaranteed across different threads (millis).")
117+
@Outcome.Outcomes({
118+
@Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Both newer"),
119+
@Outcome(id = "1, 0", expect = ACCEPTABLE, desc = "Newer + same time"),
120+
@Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Same time + newer"),
121+
@Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Both same time"),
122+
@Outcome(id = "-1, .*", expect = FORBIDDEN, desc = "Clock must not go backwards"),
123+
@Outcome(id = ".*, -1", expect = FORBIDDEN, desc = "Clock must not go backwards"),
124+
@Outcome(expect = UNKNOWN, desc = "Not sure what happened"),
125+
})
126+
@State()
127+
public static class Millis {
128+
long ref;
129+
130+
long v1;
131+
long v2;
132+
133+
public Millis() {
134+
ref = CLOCK.currentTimeMillis();
135+
}
136+
137+
@Actor
138+
public void actor1() {
139+
v1 = CLOCK.currentTimeMillis();
140+
}
141+
142+
@Actor
143+
public void actor2() {
144+
v2 = CLOCK.currentTimeMillis();
145+
}
146+
147+
@Arbiter
148+
public void arbiter(II_Result r) {
149+
r.r1 = Long.compare(v1, ref);
150+
r.r2 = Long.compare(v2, ref);
151+
}
152+
}
153+
154+
@JCStressTest
155+
@Description("Verify that monotonicity is guaranteed across different threads (instants).")
156+
@Outcome.Outcomes({
157+
@Outcome(id = "1, 1", expect = ACCEPTABLE, desc = "Both newer"),
158+
@Outcome(id = "1, 0", expect = ACCEPTABLE, desc = "Newer + same time"),
159+
@Outcome(id = "0, 1", expect = ACCEPTABLE, desc = "Same time + newer"),
160+
@Outcome(id = "0, 0", expect = ACCEPTABLE, desc = "Both same time"),
161+
@Outcome(id = "-1, .*", expect = FORBIDDEN, desc = "Clock must not go backwards"),
162+
@Outcome(id = ".*, -1", expect = FORBIDDEN, desc = "Clock must not go backwards"),
163+
@Outcome(expect = UNKNOWN, desc = "Not sure what happened"),
164+
})
165+
@State()
166+
public static class Instants {
167+
Instant ref;
168+
169+
Instant v1;
170+
Instant v2;
171+
172+
public Instants() {
173+
ref = CLOCK.currentInstant();
174+
}
175+
176+
@Actor
177+
public void actor1() {
178+
v1 = CLOCK.currentInstant();
179+
}
180+
181+
@Actor
182+
public void actor2() {
183+
v2 = CLOCK.currentInstant();
184+
}
185+
186+
@Arbiter
187+
public void arbiter(II_Result r) {
188+
r.r1 = Integer.compare(v1.compareTo(ref), 0);
189+
r.r2 = Integer.compare(v2.compareTo(ref), 0);
190+
}
191+
}
192+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.ids.impl;
20+
21+
import static org.openjdk.jcstress.annotations.Expect.ACCEPTABLE;
22+
import static org.openjdk.jcstress.annotations.Expect.FORBIDDEN;
23+
import static org.openjdk.jcstress.annotations.Expect.UNKNOWN;
24+
25+
import org.apache.polaris.ids.api.IdGenerator;
26+
import org.openjdk.jcstress.annotations.Actor;
27+
import org.openjdk.jcstress.annotations.Arbiter;
28+
import org.openjdk.jcstress.annotations.Description;
29+
import org.openjdk.jcstress.annotations.JCStressTest;
30+
import org.openjdk.jcstress.annotations.Outcome;
31+
import org.openjdk.jcstress.annotations.State;
32+
import org.openjdk.jcstress.infra.results.I_Result;
33+
import org.openjdk.jcstress.infra.results.Z_Result;
34+
35+
public class SnowflakeIdGeneratorStress {
36+
public static final MonotonicClockImpl CLOCK = new MonotonicClockImpl().start();
37+
public static final IdGenerator IDGEN =
38+
new SnowflakeIdGeneratorImpl(42, CLOCK::currentTimeMillis, () -> true);
39+
40+
@JCStressTest
41+
@Description("Verify that generated IDs are unique for the same thread.")
42+
@Outcome.Outcomes({
43+
@Outcome(id = "1", expect = ACCEPTABLE, desc = "Not equal, greater"),
44+
@Outcome(id = "-1", expect = FORBIDDEN, desc = "Not equal, smaller"),
45+
@Outcome(id = "0", expect = FORBIDDEN, desc = "Equal"),
46+
@Outcome(expect = UNKNOWN, desc = "Not sure what happened"),
47+
})
48+
@State()
49+
public static class SameThread {
50+
@Actor
51+
public void actor(I_Result r) {
52+
var v1 = IDGEN.generateId();
53+
var v2 = IDGEN.generateId();
54+
55+
r.r1 = Long.compare(v2, v1);
56+
}
57+
}
58+
59+
@JCStressTest
60+
@Description("Verify that generated IDs are unique for the same thread.")
61+
@Outcome.Outcomes({
62+
@Outcome(id = "false", expect = ACCEPTABLE, desc = "Not equal"),
63+
@Outcome(id = "true", expect = FORBIDDEN, desc = "Equal"),
64+
@Outcome(expect = UNKNOWN, desc = "Not sure what happened"),
65+
})
66+
@State()
67+
public static class DifferentThreads {
68+
long v1;
69+
long v2;
70+
71+
@Actor
72+
public void actor1() {
73+
v1 = IDGEN.generateId();
74+
}
75+
76+
@Actor
77+
public void actor2() {
78+
v1 = IDGEN.generateId();
79+
}
80+
81+
@Arbiter
82+
public void arbiter(Z_Result r) {
83+
r.r1 = v1 == v2;
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)