Skip to content

Commit 3e38d69

Browse files
authored
Lazily start IncrementingUuidGenerator sessions (#2931)
Even when not used, the `IncrementingUuidGenerator` is instantiated with each Cucumber execution. This somewhat fundamental to the way the `ServiceLoader` mechanism works. Each time an incrementing generator is created, a new session is started for that generator. This means that after 255 executions all sessions are exhausted. Unfortunately, when using the JUnit Platform, Maven issues a discovery request for each individual class. And Cucumber typically participates in discovery along with JUnit Jupiter. So after 255 classes, Cucumber fails discovery as seen in #2930. Fixes #2930.
1 parent 0eee069 commit 3e38d69

File tree

3 files changed

+39
-13
lines changed

3 files changed

+39
-13
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1010
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
## [Unreleased]
13+
### Fixed
14+
- [Core] Lazily start IncrementingUuidGenerator sessions([#2931](https://github.com/cucumber/cucumber-jvm/pull/2931) M.P. Korstanje)
1315

1416
## [7.20.0] - 2024-10-04
1517
### Added

cucumber-core/src/main/java/io/cucumber/core/eventbus/IncrementingUuidGenerator.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public class IncrementingUuidGenerator implements UuidGenerator {
6868
/**
6969
* Computed UUID MSB value.
7070
*/
71-
final long msb;
71+
private long msb;
7272

7373
/**
7474
* Counter for the UUID LSB.
@@ -77,13 +77,13 @@ public class IncrementingUuidGenerator implements UuidGenerator {
7777

7878
/**
7979
* Defines a new classloaderId for the class. This only affects instances
80-
* created after the call (the instances created before the call keep their
81-
* classloaderId). This method should be called to specify a classloaderId
82-
* if you are using more than one class loader, and you want to guarantee a
83-
* collision-free UUID generation (instead of the default random
84-
* classloaderId which produces about 1% collision rate on the
85-
* classloaderId, and thus can have UUID collision if the epoch-time,
86-
* session counter and counter have the same values).
80+
* created after the first call to {@link #generateId()} (the instances
81+
* created before the call keep their classloaderId). This method should be
82+
* called to specify a {@code classloaderId} if you are using more than one
83+
* class loader, and you want to guarantee a collision-free UUID generation
84+
* (instead of the default random classloaderId which produces about 1%
85+
* collision rate on the classloaderId, and thus can have UUID collision if
86+
* the epoch-time, session counter and counter have the same values).
8787
*
8888
* @param classloaderId the new classloaderId (only the least significant 12
8989
* bits are used)
@@ -94,6 +94,10 @@ public static void setClassloaderId(int classloaderId) {
9494
}
9595

9696
public IncrementingUuidGenerator() {
97+
98+
}
99+
100+
private long initializeMsb() {
97101
long sessionId = sessionCounter.incrementAndGet();
98102
if (sessionId == MAX_SESSION_ID) {
99103
throw new CucumberException(
@@ -103,7 +107,7 @@ public IncrementingUuidGenerator() {
103107
}
104108
long epochTime = System.currentTimeMillis();
105109
// msb = epochTime | sessionId | version | classloaderId
106-
msb = ((epochTime & MAX_EPOCH_TIME) << 24) | (sessionId << 16) | (8 << 12) | classloaderId;
110+
return ((epochTime & MAX_EPOCH_TIME) << 24) | (sessionId << 16) | (8 << 12) | classloaderId;
107111
}
108112

109113
/**
@@ -114,6 +118,11 @@ public IncrementingUuidGenerator() {
114118
*/
115119
@Override
116120
public UUID generateId() {
121+
if (msb == 0) {
122+
// Lazy init to avoid starting sessions when not used.
123+
msb = initializeMsb();
124+
}
125+
117126
long counterValue = counter.incrementAndGet();
118127
if (counterValue == MAX_COUNTER_VALUE) {
119128
throw new CucumberException(

cucumber-core/src/test/java/io/cucumber/core/eventbus/IncrementingUuidGeneratorTest.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package io.cucumber.core.eventbus;
22

33
import io.cucumber.core.exception.CucumberException;
4-
import org.hamcrest.Matchers;
54
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.function.ThrowingSupplier;
66

77
import java.io.IOException;
88
import java.lang.reflect.Field;
@@ -21,6 +21,8 @@
2121
import java.util.stream.IntStream;
2222

2323
import static org.hamcrest.MatcherAssert.assertThat;
24+
import static org.hamcrest.Matchers.containsString;
25+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2426
import static org.junit.jupiter.api.Assertions.assertEquals;
2527
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2628
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -119,20 +121,33 @@ void raises_exception_when_out_of_range() {
119121

120122
// Then
121123
assertThat(cucumberException.getMessage(),
122-
Matchers.containsString("Out of IncrementingUuidGenerator capacity"));
124+
containsString("Out of IncrementingUuidGenerator capacity"));
123125
}
124126

125127
@Test
126128
void version_overflow() {
127129
// Given
130+
IncrementingUuidGenerator generator = new IncrementingUuidGenerator();
128131
IncrementingUuidGenerator.sessionCounter.set(IncrementingUuidGenerator.MAX_SESSION_ID - 1);
129132

130133
// When
131-
CucumberException cucumberException = assertThrows(CucumberException.class, IncrementingUuidGenerator::new);
134+
CucumberException cucumberException = assertThrows(CucumberException.class, generator::generateId);
132135

133136
// Then
134137
assertThat(cucumberException.getMessage(),
135-
Matchers.containsString("Out of IncrementingUuidGenerator capacity"));
138+
containsString("Out of IncrementingUuidGenerator capacity"));
139+
}
140+
141+
@Test
142+
void lazy_init() {
143+
// Given
144+
IncrementingUuidGenerator.sessionCounter.set(IncrementingUuidGenerator.MAX_SESSION_ID - 1);
145+
146+
// When
147+
ThrowingSupplier<IncrementingUuidGenerator> instantiateGenerator = IncrementingUuidGenerator::new;
148+
149+
// Then
150+
assertDoesNotThrow(instantiateGenerator);
136151
}
137152

138153
private static void checkUuidProperties(List<UUID> uuids) {

0 commit comments

Comments
 (0)