FileChangelogDB.removeDomain() reassigns the local variable which the monitor it holds is
supposed to protect:
// FileChangelogDB.java:509-521
Map<Integer, FileReplicaDB> domainMap = domainToReplicaDBs.get(baseDN);
if (domainMap != null)
{
...
synchronized (domainMap)
{
domainMap = domainToReplicaDBs.remove(baseDN);
for (FileReplicaDB replicaDB : domainMap.values())
shutdownDB() removes the same entry, under the same monitor, in its drain of
domainToReplicaDBs (:355-366). A removeDomain() which acquires that monitor after the
drain has released it therefore gets null back from remove(baseDN) and dereferences it on
the very next line.
The result is a NullPointerException where the caller expects a ChangelogException:
ReplicationServerDomain.clearDbs() (:2066-2076) catches ChangelogException only, so the
NPE propagates out of the generation id change which called it.
The same window exists between two concurrent removeDomain() calls, and between
removeDomain() and clearDB(), which calls it for every domain it finds (:478).
Proposed fix
Remove conditionally, and keep iterating the map the monitor was taken on:
synchronized (domainMap)
{
domainToReplicaDBs.remove(baseDN, domainMap);
for (FileReplicaDB replicaDB : domainMap.values())
A replica DB which the drain has already released is then visited twice, which is harmless:
Log.clear() returns at once on a closed log (Log.java:772-780) and FileReplicaDB.shutdown()
is guarded by a CAS (FileReplicaDB.java:218).
Found while analysing #813.
FileChangelogDB.removeDomain()reassigns the local variable which the monitor it holds issupposed to protect:
shutdownDB()removes the same entry, under the same monitor, in its drain ofdomainToReplicaDBs(:355-366). AremoveDomain()which acquires that monitor after thedrain has released it therefore gets
nullback fromremove(baseDN)and dereferences it onthe very next line.
The result is a
NullPointerExceptionwhere the caller expects aChangelogException:ReplicationServerDomain.clearDbs()(:2066-2076) catchesChangelogExceptiononly, so theNPE propagates out of the generation id change which called it.
The same window exists between two concurrent
removeDomain()calls, and betweenremoveDomain()andclearDB(), which calls it for every domain it finds (:478).Proposed fix
Remove conditionally, and keep iterating the map the monitor was taken on:
A replica DB which the drain has already released is then visited twice, which is harmless:
Log.clear()returns at once on a closed log (Log.java:772-780) andFileReplicaDB.shutdown()is guarded by a CAS (
FileReplicaDB.java:218).Found while analysing #813.