mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
27.12.2015 2af7525dc754ba78d7dc21d059d7c0b30b97ece9
OPENDJ-2335: prevent JE index corruption due to phantom reads

During an index update operation we attempt to insert an ID into an ID
list. There are two code-paths:

* the key (+ ID list) already exists: read the record with a RMW lock,
compute the new ID list, and replace the record
* the key does not exist: compute the new ID list (single ID) and put it
in the index.

In the second case there is risk that two threads race to create the new
record with the second one over writing the first. This is because it is
not possible to use a RMW lock on an non-existent record. The fix is to
insert the new record using "putIfAbsent" semantics, and retry the
entire process if the put fails.
1 files modified
31 ■■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java 31 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/jeb/JEStorage.java
@@ -464,24 +464,33 @@
    {
      try
      {
        Database tree = getOrOpenTree(treeName);
        DatabaseEntry dbKey = db(key);
        DatabaseEntry dbValue = new DatabaseEntry();
        boolean isDefined = tree.get(txn, dbKey, dbValue, RMW) == SUCCESS;
        final ByteSequence oldValue = valueToBytes(dbValue, isDefined);
        final ByteSequence newValue = f.computeNewValue(oldValue);
        if (!Objects.equals(newValue, oldValue))
        final Database tree = getOrOpenTree(treeName);
        final DatabaseEntry dbKey = db(key);
        final DatabaseEntry dbValue = new DatabaseEntry();
        for (;;)
        {
          final boolean isDefined = tree.get(txn, dbKey, dbValue, RMW) == SUCCESS;
          final ByteSequence oldValue = valueToBytes(dbValue, isDefined);
          final ByteSequence newValue = f.computeNewValue(oldValue);
          if (Objects.equals(newValue, oldValue))
          {
            return false;
          }
          if (newValue == null)
          {
            return tree.delete(txn, dbKey) == SUCCESS;
          }
          setData(dbValue, newValue);
          return tree.put(txn, dbKey, dbValue) == SUCCESS;
          if (isDefined)
          {
            return tree.put(txn, dbKey, dbValue) == SUCCESS;
          }
          else if (tree.putNoOverwrite(txn, dbKey, dbValue) == SUCCESS)
          {
            return true;
          }
          // else retry due to phantom read: another thread inserted a record
        }
        return false;
      }
      catch (DatabaseException e)
      {