[#800] Allocate the replication test listen ports per invocation - #803
Open
vharseko wants to merge 1 commit into
Open
[#800] Allocate the replication test listen ports per invocation#803vharseko wants to merge 1 commit into
vharseko wants to merge 1 commit into
Conversation
… per invocation AssuredReplicationServerTest allocated the four listen ports of its real replication servers once, in @BeforeClass, and its 360 invocations then re-created those servers on the same four numbers for ~20 minutes. TestCaseUtils.findFreePorts() closes its probe socket before it returns the number, so between two invocations nothing holds the port: an ephemeral local port, a socket the previous invocation is still closing or a self-connect can take it, and the bind of the next replication server fails. That is how OpenIdentityPlatform#792 happened, on invocation 26 of the class. The ports are now allocated in initTest(), which every invocation runs before it creates anything -- the four methods which have no initTest() of their own delegate to the bodies which do. Each invocation therefore binds numbers which were never bound in this JVM before, and the window during which nothing holds the port shrinks from the whole teardown of the previous invocation to the microseconds between the probe socket being closed and the listen socket being bound. All four ports are allocated at once because the multi-replication-server tests configure each server with the URLs of the others. FractionalReplicationTest (36 invocations) and AssuredReplicationPluginTest (13 invocations, fake replication server) had the same shape and move to the same per invocation allocation; the latter has no per test hook, hence a @BeforeMethod. The other replication tests which take their ports in @BeforeClass do not re-bind them: ReplicationServerTest and ChangelogBackendTestCase create their replication server once per class, and GenerationIdTest re-allocates its ports in postTest(). TopologyViewTest, ReplicationServerFailoverTest, GroupIdHandshakeTest, ReplicationServerLoadBalancingTest, ReplicationDomainTest and ReplicationServerDynamicConfTest already allocate per test method. TestCaseUtils.findFreePorts() closed its probe sockets inside the try block and left an empty finally: an allocation which failed halfway left the sockets it had already bound open, holding their ports in listen state, and bindFreePort() hands out each number only once. They are closed in the finally block now.
Member
Author
|
The whole |
maximthomas
approved these changes
Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #800 — item 2 of the Proposed fixes of #792, deliberately left out of #795.
Problem
AssuredReplicationServerTestallocated the four listen ports of its real replicationservers once, in
@BeforeClass, and its 360 invocations then re-created those serverson the same four numbers for ~20 minutes.
TestCaseUtils.findFreePorts()binds a probe socket, closes it and returns the number, sobetween two invocations nothing holds the port: an ephemeral local port, a socket the
previous invocation is still closing, or a self-connect can take it, and the bind of the
next replication server fails. That is how #792 happened, on invocation 26 of the class.
Note what the window actually is. While a replication server listens on its port, the
kernel will not hand that number out as an ephemeral local port, so the port is only
unprotected between the teardown of one invocation and the bind of the next — tens to
hundreds of milliseconds, 360 times. Allocating a fresh number per invocation shrinks that
window to the microseconds between the probe socket being closed and the listen socket
being bound, and, more importantly, hands out a number which was never bound in this JVM
before, so nothing left over from the previous invocation can be sitting on it.
Changes
AssuredReplicationServerTestallocates its four ports ininitTest(), which everyinvocation runs before it creates anything — the four methods which have no
initTest()of their own (
testSafeDataLevelOnePrecommit,testSafeDataLevelHighPrecommit,testSafeDataLevelHighNightly,testSafeReadOneRSComplexPrecommit) delegate to thebodies which do. All four ports are taken at once because the multi-replication-server
tests configure each server with the URLs of the others, so the numbers cannot change in
the middle of a topology. The
setUp()override becamesuper.setUp()only and isdropped:
@BeforeClass setUp()is inherited fromReplicationTestCase, as in the thirtyother classes of the package which do not override it.
FractionalReplicationTest(36 invocations) had the same shape — port pinned in@BeforeClass, a replication server created on it by every invocation — and moves theallocation into its existing
initTest().AssuredReplicationPluginTest(13 invocations) is the same again, with the fakereplication server binding the pinned port in
start(). It has no per test hook, hence a@BeforeMethod; both the fake replication server and the domain configuration entrywhich points at it are created by the test method itself, so they see the new number.
TestCaseUtils.findFreePorts()closed its probe sockets inside thetryblock and leftan empty
finally: an allocation which failed halfway left the sockets it had alreadybound open, holding their ports in listen state, and
bindFreePort()hands out eachnumber only once. They are closed in the
finallyblock now.Port budget: the class which needs it most now takes 360 × 4 = 1440 numbers, and
bindFreePort()walks down from 65531, so this JVM ends around 64091 — well above theLinux ephemeral range (32768–60999). Worth keeping in mind if
testSafeDataLevelHigh(enabled = false) is ever switched on.Not changed
The issue lists nine classes calling
TestCaseUtils.findFreePorts(). Eight of them do notre-bind a pinned port and are left alone:
TopologyViewTest,ReplicationServerFailoverTest,GroupIdHandshakeTest,ReplicationServerLoadBalancingTest,ReplicationDomainTestandReplicationServerDynamicConfTestalready allocate per test method — none of them isdata-provider driven, so a test method is an invocation.
GenerationIdTestre-allocates its three ports inpostTest()(
GenerationIdTest.java:1064); four test methods, seven replication servers.ReplicationServerTesttakes its port inconfigure()from@BeforeClassbut createsthe replication server once for the class, and takes a fresh port when it restarts it
(
stopChangelog()).ChangelogBackendTestCase, which is not in the list, is the same:one replication server per class, removed in
@AfterClass.Relation to #795
#795 is not merged yet, so on master a replication server whose listen port is taken still
only logs
ERR_COULD_NOT_BIND_CHANGELOGand comes up without a listener. Once it is in,its retry makes a momentarily occupied port self-healing and its probe reports what was
observed on the port, which is what will identify the holder if this ever happens again.
This change is independent of it and reduces the number of chances to find out.
Tests
The whole
replicationpackage is running locally as well, sinceTestCaseUtilsis sharedby every test; the result will be added as a comment.