Skip to content

Commit 433b345

Browse files
authored
Fix port range allocation with large worker IDs (#44213)
* Fix port range allocation with large worker IDs Relates to #43983 The IDs gradle uses are incremented for the lifetime of the daemon which can result in port ranges that are outside the valid range. This change implements a modulo based formula to wrap the port ranges when the IDs get too large. Adresses #44134 but #44157 is also required to be able to close it.
1 parent cc51a93 commit 433b345

File tree

5 files changed

+65
-29
lines changed

5 files changed

+65
-29
lines changed

test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,14 @@ public static void resetPortCounter() {
186186
}
187187

188188
// Allows distinguishing between parallel test processes
189-
public static final int TEST_WORKER_VM;
189+
public static final String TEST_WORKER_VM_ID;
190190

191-
protected static final String TEST_WORKER_SYS_PROPERTY = "org.gradle.test.worker";
191+
public static final String TEST_WORKER_SYS_PROPERTY = "org.gradle.test.worker";
192+
193+
public static final String DEFAULT_TEST_WORKER_ID = "--not-gradle--";
192194

193195
static {
194-
// org.gradle.test.worker starts counting at 1, but we want to start counting at 0 here
195-
// in case system property is not defined (e.g. when running test from IDE), just use 0
196-
TEST_WORKER_VM = RandomizedTest.systemPropertyAsInt(TEST_WORKER_SYS_PROPERTY, 1) - 1;
196+
TEST_WORKER_VM_ID = System.getProperty(TEST_WORKER_SYS_PROPERTY, DEFAULT_TEST_WORKER_ID);
197197
setTestSysProps();
198198
LogConfigurator.loadLog4jPlugins();
199199

test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ private static Settings getRandomNodeSettings(long seed) {
505505

506506
public static String clusterName(String prefix, long clusterSeed) {
507507
StringBuilder builder = new StringBuilder(prefix);
508-
builder.append("-TEST_WORKER_VM=[").append(ESTestCase.TEST_WORKER_VM).append(']');
508+
builder.append("-TEST_WORKER_VM=[").append(ESTestCase.TEST_WORKER_VM_ID).append(']');
509509
builder.append("-CLUSTER_SEED=[").append(clusterSeed).append(']');
510510
// if multiple maven task run on a single host we better have an identifier that doesn't rely on input params
511511
builder.append("-HASH=[").append(SeedUtils.formatSeed(System.nanoTime())).append(']');

test/framework/src/main/java/org/elasticsearch/test/transport/MockTransportService.java

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -106,27 +106,28 @@ public static MockTransportService createNewService(Settings settings, Version v
106106
}
107107

108108
/**
109-
* Some tests use MockTransportService to do network based testing. Yet, we run tests in multiple JVMs that means
110-
* concurrent tests could claim port that another JVM just released and if that test tries to simulate a disconnect it might
111-
* be smart enough to re-connect depending on what is tested. To reduce the risk, since this is very hard to debug we use
112-
* a different default port range per JVM unless the incoming settings override it
113-
* use a non-default base port otherwise some cluster in this JVM might reuse a port
114-
*/
115-
private static int getBasePort() {
116-
final int basePort = 10300 + (ESTestCase.TEST_WORKER_VM * 100);
117-
if (basePort < 10300 || basePort >= 65000) {
118-
// to ensure we don't get illegal ports above 65536 in the getPortRange method
119-
throw new AssertionError("Expected basePort to be between 10300 and 65000 but was " + basePort);
120-
}
121-
return basePort;
122-
}
123-
124-
/**
125-
* Returns a unique port range for this JVM starting from the computed base port (see {@link #getBasePort()})
109+
* Returns a unique port range for this JVM starting from the computed base port
126110
*/
127111
public static String getPortRange() {
128-
int basePort = getBasePort();
129-
return basePort + "-" + (basePort + 99); // upper bound is inclusive
112+
return getBasePort() + "-" + (getBasePort() + 99); // upper bound is inclusive
113+
}
114+
115+
protected static int getBasePort() {
116+
// some tests use MockTransportService to do network based testing. Yet, we run tests in multiple JVMs that means
117+
// concurrent tests could claim port that another JVM just released and if that test tries to simulate a disconnect it might
118+
// be smart enough to re-connect depending on what is tested. To reduce the risk, since this is very hard to debug we use
119+
// a different default port range per JVM unless the incoming settings override it
120+
// use a non-default base port otherwise some cluster in this JVM might reuse a port
121+
122+
// We rely on Gradle implementation details here, the worker IDs are long values incremented by one for the
123+
// lifespan of the daemon this means that they can get larger than the allowed port range.
124+
// Ephemeral ports on Linux start at 32768 so we modulo to make sure that we don't exceed that.
125+
// This is safe as long as we have fewer than 224 Gradle workers running in parallel
126+
// See also: https://github.com/elastic/elasticsearch/issues/44134
127+
final String workerId = System.getProperty(ESTestCase.TEST_WORKER_SYS_PROPERTY);
128+
final int startAt = workerId == null ? 0 : Math.floorMod(Long.valueOf(workerId), 223);
129+
assert startAt >= 0 : "Unexpected test worker Id, resulting port range would be negative";
130+
return 10300 + (startAt * 100);
130131
}
131132

132133
public static MockNioTransport newMockTransport(Settings settings, Version version, ThreadPool threadPool) {

test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
package org.elasticsearch.test.test;
2121

22-
import com.carrotsearch.randomizedtesting.RandomizedTest;
2322
import junit.framework.AssertionFailedError;
2423

2524
import org.elasticsearch.common.bytes.BytesReference;
@@ -43,6 +42,7 @@
4342

4443
import static org.hamcrest.Matchers.greaterThan;
4544
import static org.hamcrest.Matchers.hasSize;
45+
import static org.hamcrest.Matchers.not;
4646

4747
public class ESTestCaseTests extends ESTestCase {
4848

@@ -185,8 +185,7 @@ public void testRandomValueOtherThan() {
185185

186186
public void testWorkerSystemProperty() {
187187
assumeTrue("requires running tests with Gradle", System.getProperty("tests.gradle") != null);
188-
// org.gradle.test.worker starts counting at 1
189-
assertThat(RandomizedTest.systemPropertyAsInt(TEST_WORKER_SYS_PROPERTY, -1), greaterThan(0));
190-
assertEquals(RandomizedTest.systemPropertyAsInt(TEST_WORKER_SYS_PROPERTY, -1) - 1, TEST_WORKER_VM);
188+
189+
assertThat(ESTestCase.TEST_WORKER_VM_ID, not(equals(ESTestCase.DEFAULT_TEST_WORKER_ID)));
191190
}
192191
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* 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.elasticsearch.test.transport;
20+
21+
import org.elasticsearch.test.ESTestCase;
22+
23+
public class MockTransportServiceTests extends ESTestCase {
24+
25+
public void testBasePortGradle() {
26+
assumeTrue("requires running tests with Gradle", System.getProperty("tests.gradle") != null);
27+
// Gradle worker IDs are 1 based
28+
assertNotEquals(10300, MockTransportService.getBasePort());
29+
}
30+
31+
public void testBasePortIDE() {
32+
assumeTrue("requires running tests without Gradle", System.getProperty("tests.gradle") == null);
33+
assertEquals(10300, MockTransportService.getBasePort());
34+
}
35+
36+
}

0 commit comments

Comments
 (0)