Skip to content

Commit cb6b51b

Browse files
committed
GEODE-10490: Reallocate ports in DUnit remote VMs after deserialization
In DUnit distributed tests, MemberStarterRule is serialized via RMI to remote VMs. After deserialization, the transient portSupplier becomes null, but the port numbers (memberPort, jmxPort, httpPort) retain their original values from the local VM. These port numbers may conflict on remote VMs since they were allocated for the local VM environment. The fix detects deserialization by checking for null portSupplier and reallocates fresh ports specific to each remote VM, preventing 'Address already in use' errors. Also removed 'final' modifiers from availableJmxPort and availableHttpPort to allow reassignment during deserialization handling.
1 parent 44a66aa commit cb6b51b

1 file changed

Lines changed: 28 additions & 19 deletions

File tree

geode-dunit/src/main/java/org/apache/geode/test/junit/rules/MemberStarterRule.java

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@
9090
* created in the test will be cleaned up after the test.
9191
*/
9292
public abstract class MemberStarterRule<T> extends SerializableExternalResource implements Member {
93-
private final int availableJmxPort;
94-
private final int availableHttpPort;
93+
// Non-final to allow re-allocation in DUnit remote VMs after deserialization
94+
private int availableJmxPort;
95+
private int availableHttpPort;
9596

9697
protected int memberPort;
9798
protected int jmxPort = -1; // -1 means not start on servere, 0 means use default, >0 means preset
@@ -128,23 +129,11 @@ public MemberStarterRule() {
128129
public MemberStarterRule(UniquePortSupplier portSupplier) {
129130
this.portSupplier = portSupplier;
130131

131-
try {
132-
// Reserve ports immediately to prevent race conditions
133-
memberPort = portSupplier.getAvailablePort();
134-
memberPortKeeper = new PortKeeper(memberPort);
135-
136-
// Reserve JMX and HTTP ports for potential use
137-
availableJmxPort = portSupplier.getAvailablePort();
138-
jmxPortKeeper = new PortKeeper(availableJmxPort);
139-
140-
availableHttpPort = portSupplier.getAvailablePort();
141-
httpPortKeeper = new PortKeeper(availableHttpPort);
142-
143-
} catch (java.io.IOException e) {
144-
// Clean up any successfully created keepers
145-
cleanupPortKeepers();
146-
throw new RuntimeException("Failed to reserve ports for test member", e);
147-
}
132+
// Allocate ports but don't create keepers yet
133+
// Keepers will be created just-in-time in before() to minimize hold time
134+
memberPort = portSupplier.getAvailablePort();
135+
availableJmxPort = portSupplier.getAvailablePort();
136+
availableHttpPort = portSupplier.getAvailablePort();
148137

149138
// initial values
150139
properties.setProperty(MCAST_PORT, "0");
@@ -159,6 +148,26 @@ public void before() {
159148
} catch (Throwable throwable) {
160149
throw new RuntimeException(throwable.getMessage(), throwable);
161150
}
151+
152+
// Handle DUnit deserialization: portSupplier is transient and will be null in remote VMs
153+
// Remote VMs need fresh port allocation since ports from local VM may not be available
154+
if (portSupplier == null) {
155+
portSupplier = new UniquePortSupplier();
156+
memberPort = portSupplier.getAvailablePort();
157+
availableJmxPort = portSupplier.getAvailablePort();
158+
availableHttpPort = portSupplier.getAvailablePort();
159+
}
160+
161+
// Create PortKeepers just-in-time to hold ports exclusively until member starts
162+
try {
163+
memberPortKeeper = new PortKeeper(memberPort);
164+
jmxPortKeeper = new PortKeeper(availableJmxPort);
165+
httpPortKeeper = new PortKeeper(availableHttpPort);
166+
} catch (java.io.IOException e) {
167+
cleanupPortKeepers();
168+
throw new RuntimeException("Failed to reserve ports for test member", e);
169+
}
170+
162171
normalizeProperties();
163172
if (httpPort < 0) {
164173
// at this point, httpPort is not being configured by api, we assume they do not

0 commit comments

Comments
 (0)