Skip to content

Commit 06e91a1

Browse files
authored
[#730] Fix import/export context leak on failed initializeRemote validation (#731)
1 parent 3cc8bed commit 06e91a1

3 files changed

Lines changed: 121 additions & 18 deletions

File tree

opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,24 +1442,64 @@ protected void initializeRemote(int serverToInitialize,
14421442
int serverRunningTheTask, Task initTask, int initWindow)
14431443
throws DirectoryException
14441444
{
1445-
final ImportExportContext ieCtx = acquireIEContext(false);
1446-
14471445
/*
14481446
We manage the list of servers to initialize in order :
14491447
- to test at the end that all expected servers have reconnected
14501448
after their import and with the right genId
14511449
- to update the task with the server(s) where this test failed
14521450
*/
14531451

1454-
Map<Integer, DSInfo> replicaInfos = getReplicaInfos();
1452+
// Validate the request before acquiring the import/export context: a
1453+
// validation failure must not leave the context acquired, otherwise
1454+
// ieRunning() would remain true forever and the domain would reject every
1455+
// subsequent total update as a simultaneous import/export.
1456+
final Map<Integer, DSInfo> replicaInfos = getReplicaInfos();
1457+
final DSInfo targetDsi;
14551458
if (serverToInitialize == RoutableMsg.ALL_SERVERS)
14561459
{
14571460
if (replicaInfos.isEmpty())
14581461
{
14591462
throw new DirectoryException(UNWILLING_TO_PERFORM,
14601463
ERR_FULL_UPDATE_NO_REMOTES.get(getBaseDN(), getServerId()));
14611464
}
1465+
targetDsi = null;
1466+
}
1467+
else
1468+
{
1469+
targetDsi = getDsInfoOrNull(replicaInfos.values(), serverToInitialize);
1470+
if (targetDsi == null)
1471+
{
1472+
throw new DirectoryException(UNWILLING_TO_PERFORM,
1473+
ERR_FULL_UPDATE_MISSING_REMOTE.get(getBaseDN(), getServerId(), serverToInitialize));
1474+
}
1475+
}
14621476

1477+
final ImportExportContext ieCtx = acquireIEContext(false);
1478+
try
1479+
{
1480+
initializeRemote(ieCtx, replicaInfos, targetDsi, serverToInitialize,
1481+
serverRunningTheTask, initTask, initWindow);
1482+
}
1483+
finally
1484+
{
1485+
// Release the context whatever the outcome, otherwise ieRunning() would
1486+
// remain true forever (resolves the historical "FIXME should not this
1487+
// be in a finally?").
1488+
releaseIEContext();
1489+
}
1490+
}
1491+
1492+
/**
1493+
* Performs the remote initialization with the import/export context already
1494+
* acquired - and released - by the caller.
1495+
*/
1496+
private void initializeRemote(ImportExportContext ieCtx,
1497+
Map<Integer, DSInfo> replicaInfos, DSInfo targetDsi,
1498+
int serverToInitialize, int serverRunningTheTask, Task initTask,
1499+
int initWindow) throws DirectoryException
1500+
{
1501+
if (serverToInitialize == RoutableMsg.ALL_SERVERS)
1502+
{
14631503
logger.info(NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START_ALL,
14641504
countEntries(), getBaseDN(), getServerId());
14651505

@@ -1475,18 +1515,11 @@ protected void initializeRemote(int serverToInitialize,
14751515
}
14761516
else
14771517
{
1478-
DSInfo dsi = getDsInfoOrNull(replicaInfos.values(), serverToInitialize);
1479-
if (dsi == null)
1480-
{
1481-
throw new DirectoryException(UNWILLING_TO_PERFORM,
1482-
ERR_FULL_UPDATE_MISSING_REMOTE.get(getBaseDN(), getServerId(), serverToInitialize));
1483-
}
1484-
14851518
logger.info(NOTE_FULL_UPDATE_ENGAGED_FOR_REMOTE_START, countEntries(),
14861519
getBaseDN(), getServerId(), serverToInitialize);
14871520

14881521
ieCtx.startList.add(serverToInitialize);
1489-
ieCtx.setAckVal(dsi.getDsId(), 0);
1522+
ieCtx.setAckVal(targetDsi.getDsId(), 0);
14901523
}
14911524

14921525
DirectoryException exportRootException = null;
@@ -1625,9 +1658,6 @@ protected void initializeRemote(int serverToInitialize,
16251658
ERR_INIT_NO_SUCCESS_END_FROM_SERVERS.get(getGenerationID(), ieCtx.failureList));
16261659
}
16271660

1628-
// Don't forget to release IEcontext acquired at beginning.
1629-
releaseIEContext(); // FIXME should not this be in a finally?
1630-
16311661
final String cause = exportRootException == null ? ""
16321662
: exportRootException.getLocalizedMessage();
16331663
if (serverToInitialize == RoutableMsg.ALL_SERVERS)

opendj-server-legacy/src/test/java/org/opends/server/replication/InitOnLineTest.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ public void initializeTargetExport() throws Exception
656656
}
657657

658658
// Launch in S1 the task that will initialize S2
659+
waitForRemoteReplicas(server2ID);
659660
addTask(taskInitTargetS2, ResultCode.SUCCESS, null);
660661

661662
// Signal RS we just entered the full update status
@@ -714,6 +715,7 @@ public void initializeTargetExportAll() throws Exception
714715
}
715716

716717
// Launch in S1 the task that will initialize S2
718+
waitForRemoteReplicas(server2ID, server3ID);
717719
addTask(taskInitTargetAll, ResultCode.SUCCESS, null);
718720

719721
// Tests that entries have been received by S2
@@ -1004,6 +1006,7 @@ public void initializeTargetExportMultiSS() throws Exception
10041006

10051007
// Launch in S1 the task that will initialize S2
10061008
log(testCase + " add task " + Thread.currentThread());
1009+
waitForRemoteReplicas(server2ID);
10071010
addTask(taskInitTargetS2, ResultCode.SUCCESS, null);
10081011

10091012
log(testCase + " " + server2.getServerId() + " wait target " + Thread.currentThread());
@@ -1027,6 +1030,47 @@ public void initializeTargetExportMultiSS() throws Exception
10271030
}
10281031
}
10291032

1033+
/**
1034+
* Tests that an InitializeTarget task failing its validation (the remote
1035+
* replica is unknown to the domain) does not leave the import/export
1036+
* context acquired (issue #730): the domain used to reject every subsequent
1037+
* total update as a simultaneous import/export.
1038+
*/
1039+
@Test(enabled=true)
1040+
public void initializeTargetUnknownRemote() throws Exception
1041+
{
1042+
String testCase = "initializeTargetUnknownRemote";
1043+
log("Starting " + testCase);
1044+
try
1045+
{
1046+
replServer1 = createReplicationServer(replServer1ID, testCase);
1047+
connectServer1ToReplServer(replServer1ID);
1048+
addTestEntriesToDB();
1049+
1050+
// DS(42424) is unknown in the topology: the task must fail to start...
1051+
Entry taskInitTargetUnknown = TestCaseUtils.makeEntry(
1052+
"dn: ds-task-id=" + UUID.randomUUID() + ",cn=Scheduled Tasks,cn=Tasks",
1053+
"objectclass: top",
1054+
"objectclass: ds-task",
1055+
"objectclass: ds-task-initialize-remote-replica",
1056+
"ds-task-class-name: org.opends.server.tasks.InitializeTargetTask",
1057+
"ds-task-initialize-domain-dn: " + EXAMPLE_DN,
1058+
"ds-task-initialize-replica-server-id: " + 42424);
1059+
addTask(taskInitTargetUnknown, ResultCode.SUCCESS, null);
1060+
waitTaskState(taskInitTargetUnknown, STOPPED_BY_ERROR, 20000, null);
1061+
1062+
// ...but must not leave the import/export context acquired
1063+
assertFalse(replDomain.ieRunning(),
1064+
"ReplicationDomain: Import/Export is not expected to be running after a failed InitializeTarget");
1065+
1066+
log("Successfully ending " + testCase);
1067+
}
1068+
finally
1069+
{
1070+
afterTest(testCase);
1071+
}
1072+
}
1073+
10301074
private void waitForInitializeTargetMsg(String testCase,
10311075
ReplicationBroker server) throws Exception
10321076
{
@@ -1293,9 +1337,30 @@ public void initializeSimultaneous() throws Exception
12931337
* Disconnect broker and remove entries from the local DB
12941338
* @param testCase The name of the test case.
12951339
*/
1340+
/**
1341+
* Waits until the local replication domain sees the provided replicas in
1342+
* its topology view, so that an InitializeTarget task does not race the
1343+
* topology propagation and fail to start with "the remote directory server
1344+
* DS(x) is unknown" - which, combined with the import/export context leak
1345+
* (issue #730), used to poison the domain for the rest of the test class.
1346+
*/
1347+
private void waitForRemoteReplicas(Integer... serverIds) throws Exception
1348+
{
1349+
for (int serverId : serverIds)
1350+
{
1351+
for (int i = 0; i < 100 && !replDomain.getReplicaInfos().containsKey(serverId); i++)
1352+
{
1353+
sleep(100);
1354+
}
1355+
assertTrue(replDomain.getReplicaInfos().containsKey(serverId),
1356+
"DS(" + serverId + ") is not known to the local replication domain");
1357+
}
1358+
}
1359+
12961360
private void afterTest(String testCase) throws Exception
12971361
{
12981362
// Check that the domain has completed the import/export task.
1363+
boolean ieStillRunning = false;
12991364
if (replDomain != null)
13001365
{
13011366
// race condition could cause the main thread to reach
@@ -1309,7 +1374,10 @@ private void afterTest(String testCase) throws Exception
13091374
}
13101375
sleep(500);
13111376
}
1312-
assertFalse(replDomain.ieRunning(), "ReplicationDomain: Import/Export is not expected to be running");
1377+
// asserted only after the cleanup below: failing before it would leak
1378+
// the domain config, the brokers and the replication servers into the
1379+
// following tests and cascade the failure over the whole class
1380+
ieStillRunning = replDomain.ieRunning();
13131381
}
13141382
// Remove domain config
13151383
super.cleanConfigEntries();
@@ -1330,6 +1398,8 @@ private void afterTest(String testCase) throws Exception
13301398

13311399
Arrays.fill(replServerPort, 0);
13321400
log("Successfully cleaned " + testCase);
1401+
1402+
assertFalse(ieStillRunning, "ReplicationDomain: Import/Export is not expected to be running");
13331403
}
13341404

13351405
/**

opendj-server-legacy/src/test/java/org/opends/server/replication/ReplicationTestCase.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*
1414
* Copyright 2006-2010 Sun Microsystems, Inc.
1515
* Portions Copyright 2011-2016 ForgeRock AS.
16+
* Portions Copyrighted 2026 3A Systems, LLC.
1617
*/
1718
package org.opends.server.replication;
1819

@@ -726,11 +727,13 @@ public Entry call() throws Exception
726727
}
727728
}
728729

729-
if (expectedTaskState == RUNNING && taskState == COMPLETED_SUCCESSFULLY)
730+
if (expectedTaskState == RUNNING
731+
&& (taskState == COMPLETED_SUCCESSFULLY || taskState == STOPPED_BY_ERROR))
730732
{
731733
// We usually wait the running state after adding the task
732-
// and if the task is fast enough then it may be already done
733-
// and we can go on.
734+
// and if the task is fast enough then it may already be done
735+
// (successfully or not) and we can go on: callers interested in the
736+
// final state wait for it explicitly afterwards.
734737
}
735738
else
736739
{

0 commit comments

Comments
 (0)