FileChangelogDB.getOrCreateReplicaDB() inserts a domain map, and announces the domain to every
registered multi domain cursor, before it can know whether it will create anything in it:
// FileChangelogDB.java:229-241
final ConcurrentMap<Integer, FileReplicaDB> newValue = new ConcurrentHashMap<>();
final ConcurrentMap<Integer, FileReplicaDB> previousValue = domainToReplicaDBs.putIfAbsent(baseDN, newValue);
...
// we just created a new domain => update all cursors
for (MultiDomainDBCursor cursor : registeredMultiDomainCursors)
{
cursor.addDomain(baseDN, null);
}
The caller can then bail out of the creation without ever putting a replica DB in that map:
today when the domain map was concurrently removed (:266), and, once #813 is fixed, when a
shutdown was initiated. The empty map stays in domainToReplicaDBs for the lifetime of the
changelog. On the shutdown path it outlives shutdownDB() altogether, which drained
domainToReplicaDBs before that map was inserted into it.
What that empty domain map is then worth:
- it is a key of
domainToReplicaDBs, so getCursorFrom() adds it to every multi domain
cursor created afterwards (:697-704), i.e. the external changelog walks a domain which has
no replica DB at all;
getDomainMap(), getDomainOldestCSNs() and getDomainNewestCSNs() answer for it as they
would for an unknown domain, so those are unaffected;
clearDB() iterates the keys and calls removeDomain() on it (:427-430), which reaches
replicationEnv.clearGenerationId() for a domain the changelog holds nothing for — and, after
a shutdown, on an environment which is closed.
Proposed fix
Drop the map the caller inserted when the creation it was inserted for does not happen:
// FileChangelogDB.java:266
if (domainToReplicaDBs.get(baseDN) != domainMap)
{
return null;
}
becomes, together with the shutdown check of #813, a bail-out which first does
domainToReplicaDBs.remove(baseDN, domainMap);
The conditional remove is safe under the monitor of domainMap: a shutdown which had already
seen that map only finds the entry gone when it acquires the monitor, and Iterator.remove()
on an entry which is no longer mapped is a no-op.
Found while analysing #813.
FileChangelogDB.getOrCreateReplicaDB()inserts a domain map, and announces the domain to everyregistered multi domain cursor, before it can know whether it will create anything in it:
The caller can then bail out of the creation without ever putting a replica DB in that map:
today when the domain map was concurrently removed (
:266), and, once #813 is fixed, when ashutdown was initiated. The empty map stays in
domainToReplicaDBsfor the lifetime of thechangelog. On the shutdown path it outlives
shutdownDB()altogether, which draineddomainToReplicaDBsbefore that map was inserted into it.What that empty domain map is then worth:
domainToReplicaDBs, sogetCursorFrom()adds it to every multi domaincursor created afterwards (
:697-704), i.e. the external changelog walks a domain which hasno replica DB at all;
getDomainMap(),getDomainOldestCSNs()andgetDomainNewestCSNs()answer for it as theywould for an unknown domain, so those are unaffected;
clearDB()iterates the keys and callsremoveDomain()on it (:427-430), which reachesreplicationEnv.clearGenerationId()for a domain the changelog holds nothing for — and, aftera shutdown, on an environment which is closed.
Proposed fix
Drop the map the caller inserted when the creation it was inserted for does not happen:
becomes, together with the shutdown check of #813, a bail-out which first does
The conditional remove is safe under the monitor of
domainMap: a shutdown which had alreadyseen that map only finds the entry gone when it acquires the monitor, and
Iterator.remove()on an entry which is no longer mapped is a no-op.
Found while analysing #813.