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

Valery Kharseko
16 hours ago a1b8537e1e0cd10e4d614dd9c6bdda541a614572
opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/JDBCStorage.java
@@ -43,9 +43,12 @@
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import static org.opends.server.backends.pluggable.spi.StorageUtils.addErrorMessage;
@@ -600,12 +603,15 @@
    * The socket read timeout of one connection, and the statements running on it. This second
    * layer of the bound is a property of the socket rather than of a statement, so it cannot be
    * armed and put back per statement wherever a connection carries more than one at a time: an
    * {@code ImporterImpl} holds a single connection for the whole of an import and writes to it
    * from every phase-one worker and every phase-two task, and there the first statement to finish
    * would take the backstop away from every statement still in flight - while a statement whose
    * class carries no bound at all would run under whatever value a concurrent one happened to
    * arm, dying at it with nothing to say which property cut it, since such a statement never
    * reaches {@link #timedOut}.
    * {@code ImporterImpl} held a single connection for the whole of an import and wrote to it from
    * every phase-one worker and every phase-two task until the trees of an import were given
    * connections of their own (#891), and there the first statement to finish would take the
    * backstop away from every statement still in flight - while a statement whose class carries no
    * bound at all would run under whatever value a concurrent one happened to arm, dying at it
    * with nothing to say which property cut it, since such a statement never reaches
    * {@link #timedOut}. The arbitration stays now that no path of this class puts two threads on
    * one connection: what it holds is a property of the socket, so a connection that carries two
    * statements again must not have either of them cut by the bound of the other.
    * <p>
    * So the value armed is the loosest of the bounds of the statements in flight, and a statement
    * with no bound of its own takes it off for as long as it runs: this backstop exists to end a
@@ -949,7 +955,19 @@
   // unregistered pool is drained the moment another backend that did register with it closes,
   // with this one still borrowing from it (issue #878).
   Connection getConnection(boolean trusted) throws Exception {
      return CachedConnection.getConnection(poolKey(), trusted);
      return getConnection(trusted, 0);
   }
   /**
    * The borrow every path of this class makes, and the seam a test stands in for the pool at.
    *
    * @param maxWaitSeconds the longest this borrow may wait at the bound of the pool, whatever the
    * deployment asked for - 0 to wait as it says. Only the connections an import takes after its
    * first pass a number here: they are held until the import ends, so a pool full of them has
    * nothing to return to the thread waiting for one (#891).
    */
   Connection getConnection(boolean trusted, long maxWaitSeconds) throws Exception {
      return CachedConnection.getConnection(poolKey(), trusted, maxWaitSeconds);
   }
@@ -4886,10 +4904,168 @@
      return true;
   }
   /**
    * How many connections one import may write through; unset for the default of
    * {@link #importConnections()}, and 0 or 1 for the single connection an import had before #891 -
    * serialized, and the least a database sees of one.
    */
   static final String IMPORT_CONNECTIONS_PROPERTY = "org.openidentityplatform.opendj.jdbc.import.connections";
   /**
    * How many connections one import writes through, which is how many of its threads can write
    * at the same time.
    * <p>
    * More than one because {@code Importer} is thread-safe by contract and a
    * {@code java.sql.Connection} is not: phase two of {@code OnDiskMergeImporter} runs a thread
    * per tree and phase one clears the trees of a container while another thread writes id2entry,
    * so a shared connection has two threads issuing statements on it at once. The drivers differ
    * in what they make of that - pgjdbc and Connector/J serialize the work of a connection behind
    * a lock of their own - but the sql server driver keeps the reconnect listeners of a
    * connection in a plain {@code ArrayList} that every {@code prepareStatement()} and every
    * statement {@code close()} mutates, and two import threads walked it past the end of its
    * array (issue #891).
    * <p>
    * Bounded because an import holds what it takes for its whole duration, out of a pool that is
    * bounded since #878 and shared with every other backend on that database: a default backend
    * has trees enough - one per index, three per attribute index - for a connection per tree to
    * empty a default pool and leave the LDAP traffic of an online rebuild waiting at its bound.
    * <p>
    * Half the bound of the pool by default, and no more than half of what the default bound would
    * be: a deployment that turned the bound off ({@code pool.max=0}) asked for its operations not
    * to queue behind one another, not for an import to open a connection per tree.
    * <p>
    * A number the operator set is taken as given, up to the bound of the pool: an import-ldif of a
    * backend that is offline has no traffic of its own to leave room for, and whoever raises this
    * on a server that is answering is making that trade knowingly - the default is where the
    * caution belongs.
    * <p>
    * What this bounds is how many connections one import holds, which is not the same as staying
    * inside the bound of the pool: a thread that already holds one connection of a pool is exempt
    * from waiting at its bound (see {@code CachedConnection.Pool}), and every thread of an import
    * takes a connection per tree it touches - so the borrows after the first go unmetered where the
    * pool stands at its bound, and are destroyed rather than pooled when they come back. That
    * exemption is what keeps the threads of an import from waiting on each other: they hold their
    * connections until the import ends, so a pool full of them has nothing left to return, and a
    * bound they had to wait at would be a deadlock rather than a queue.
    */
   int importConnections() {
      final int poolMax=poolMax();
      final long byDefault=Math.max(1, Math.min(poolMax, CachedConnection.DEFAULT_POOL_MAX)/2);
      // read the way every other bound of this backend is: a value that is not a non-negative
      // number is reported to the operator rather than quietly replaced. The default is handed to
      // it rather than a zero, so that the number the warning names is the number the import goes
      // on to use.
      final long configured=CachedConnection.getNonNegativeProperty(IMPORT_CONNECTIONS_PROPERTY, byDefault, "connections");
      // clamped to the pool: connections past its bound are not there to be had, so a number above
      // it buys nothing and costs the borrow deadline of every tree over the bound
      // (CachedConnection.POOL_TIMEOUT_PROPERTY) before the fallback of connectionOf() takes over
      return (int) Math.max(1, Math.min(configured, poolMax));
   }
   /**
    * The bound of the pool this storage borrows from. A seam of its own, like
    * {@link #getConnection(boolean)}: a test that stands in for the pool must not have this reach
    * past it into the static registry, which would intern a pool for its connection string.
    */
   int poolMax() {
      return CachedConnection.poolOf(poolKey()).max();
   }
   final class ImporterImpl implements Importer {
      final Connection con;
      final ReadableTransactionImpl txr;
      final WriteableTransactionTransactionImpl txw;
      /**
       * One connection of an import, the two transactions over it and the monitor that keeps one
       * thread at a time on it. Every statement of an import is issued under that monitor: what
       * the threads of an import must not do is share a connection, and the trees of an import
       * outnumber the connections it may take.
       */
      final class ImportConnection {
         final Connection con;
         final ReadableTransactionImpl txr;
         final WriteableTransactionTransactionImpl txw;
         /**
          * When this connection was last written, taken from {@link ImporterImpl#writes}, and
          * zero while it has nothing to commit. Written and read under the monitor of this
          * object, like the statements it counts: read anywhere else it says what the connection
          * held rather than what it holds, because it is set once the write it stands for has
          * come back - and a write still in flight is exactly the one a commit point must not
          * pass over.
          */
         long lastWrite;
         ImportConnection(Connection con) {
            this.con=con;
            this.txr=new ReadableTransactionImpl(con, StatementBound.BULK);
            this.txw=new WriteableTransactionTransactionImpl(con, StatementBound.BULK);
            // the mode this import was started with rather than the one the storage carries now:
            // the transaction reads that mutable field as it is built, and these are built as the
            // trees of an import are first touched - so a storage reopened read-only under a
            // running import would have the connections it opened before that keep writing while
            // every one after it refused, halfway through and with the clears already committed
            this.txw.isReadOnly=false;
         }
         /** Called under this monitor by whoever writes through this connection. */
         void written() {
            lastWrite=writes.incrementAndGet();
         }
      }
      /** The connections this import has taken, by the index the trees are handed out against. */
      private final ConcurrentMap<Integer,ImportConnection> connections = new ConcurrentHashMap<>();
      /** The connection index each tree is written through: a tree keeps the one it was first given. */
      private final ConcurrentMap<TreeName,Integer> connectionOfTree = new ConcurrentHashMap<>();
      /** Handed out round-robin, so that the first trees of an import get connections of their own. */
      private final AtomicInteger nextConnection = new AtomicInteger();
      /** One per index, so that the connection of an index is borrowed once however many trees want it. */
      private final ConcurrentMap<Integer,Object> borrowing = new ConcurrentHashMap<>();
      /**
       * The indexes no connection could be borrowed for, whose trees write through the first
       * connection of this import instead. Remembered rather than asked again per tree: there are
       * at most {@link #maxConnections} of them, and a tree that lands on one would otherwise pay
       * the borrow deadline of the pool over again for the answer the tree before it already got.
       */
      private final Set<Integer> sharedIndexes = ConcurrentHashMap.newKeySet();
      /**
       * Read once rather than per statement: this is a property of the whole import, and reading
       * it per record would be a property lookup per entry of an import-ldif.
       */
      final int maxConnections;
      /**
       * The connection the constructor borrows. Also the one a borrow that cannot be made falls
       * back to, so it is the one connection of an import that is always there.
       */
      private static final int FIRST_CONNECTION = 0;
      /**
       * Set by {@code close()} before it commits, after which this import has no transaction left
       * for a write to belong to. Phase two gives its threads five seconds to answer an interrupt
       * and closes the importer whether they answered or not
       * ({@code OnDiskMergeImporter.invokeParallel}), so a write can arrive with the connections
       * of the import already committed and back in the pool.
       */
      private volatile boolean closed;
      /**
       * Numbers the writes of this import, so that {@code close()} can commit its connections in
       * the order they were last written to.
       * <p>
       * The connections of an import are transactions of their own, so {@code close()} cannot make
       * them durable as one - and the last thing an import writes is the flag that says the rest of
       * it is good: {@code afterPhaseTwo} sets the trust flag of every index of a container once
       * phase two has written them all, one write per base DN. Committed in that order, those flags
       * go last, and an earlier commit that fails takes them down with it: a failed import cannot
       * leave an index marked trusted over data that never got there.
       * <p>
       * That the flags are the last writes is the caller's doing rather than this class's:
       * {@code OnDiskMergeImporter} waits for every phase-two task before it runs
       * {@code afterPhaseTwo}, and nothing here refuses a write that arrives after the flags while
       * the importer is still open. An importer whose writes did not end there would order its
       * commits by what its last writes actually were, which is what this counter says and all it
       * says.
       */
      private final AtomicLong writes = new AtomicLong();
      // The trees this import wrote: close() refreshes the statistics of these and only these,
      // so rebuilding a single index does not gather statistics for the whole backend. A full
      // import legitimately covers every tree - AbstractTwoPhaseImportStrategy.beforePhaseOne
@@ -4943,10 +5119,21 @@
            if (!accessMode.isWriteable()) {
               throw new ReadOnlyStorageException();
            }
            // Inside the try like the refusal above: the pool this asks about is the one the open
            // just registered with, so a failure here has the storage this constructor opened to
            // give back as well.
            maxConnections=importConnections();
            // What an import takes out of the pool is worth reading when a borrow of one fails at
            // the bound: the pool names the property that bounds it, and this names the one that
            // bounds the demand.
            logger.debug(LocalizableMessage.raw("jdbc: import writes through up to %d connections (%s)",
               maxConnections, IMPORT_CONNECTIONS_PROPERTY));
            borrowed=getValidatedConnection();
            txr =new ReadableTransactionImpl(borrowed, StatementBound.BULK);
            txw =new WriteableTransactionTransactionImpl(borrowed, StatementBound.BULK);
            con = borrowed;
            // The first connection is taken here rather than on the first tree, as it was before
            // the connections of an import became several (#891): an import of a database that
            // takes no connection is refused where it is started, and a storage this constructor
            // opened is given back by the catch below rather than by a put() far from it.
            connections.put(FIRST_CONNECTION, new ImportConnection(borrowed));
            borrowed=null;
         }catch (Throwable e){
            // Throwable rather than Exception, the way close() below catches it and for the same
@@ -4983,73 +5170,518 @@
      }
      /**
       * Hands the connection back to the pool and closes the sessions the transaction opened
       * beside it - the stamp one and the catalog one (#888), both outside the pool and neither
       * outliving the import that opened it - whatever went before. Returns the failure the
       * caller is to report: the return rolls back, and the rollback fails on exactly the
       * connection whose commit just did, so the commit stays the exception the caller sees and
       * this one rides along with it instead of replacing it.
       * The connection the given tree is written through, borrowed from the pool the first time
       * this import touches the tree.
       * <p>
       * Bound to the tree rather than to the thread, although it is the threads of an import that
       * must not share one: the connections of an import are transactions of their own, so two of
       * them writing one row would have the second wait for the first to commit - and an import
       * commits at {@code close()}, when every thread of it is long done. That is not a shape to
       * leave lying about: {@code setTrust()} writes the state tree of a container from
       * {@code beforePhaseOne} on an import thread and again from {@code afterPhaseTwo} on the
       * thread that closes the importer, and bound to the thread those two writes would be two
       * transactions waiting for each other with nothing left to break the wait - the bulk class
       * carries no bound, and the default lock wait is forever on three of the four engines.
       * Bound to the tree they are one transaction that waits for nothing, and phase two - which
       * runs a thread per tree - still gets the connection per thread this is all about.
       */
      private SQLException releaseConnection(SQLException failure) {
      ImportConnection connectionOf(TreeName treeName) {
         // The lookup before the assignment, not for want of a computeIfAbsent: this runs once per
         // record of an import-ldif, and the mapping function below allocates a capture of this
         // importer on every call however long the tree has had a connection.
         final Integer assigned=connectionOfTree.get(treeName);
         if (assigned!=null) {
            final ImportConnection open=connections.get(assigned);
            if (open!=null) {
               return open;
            }
         }
         checkOpen();
         // floorMod rather than %: the counter is shared by every thread of the import and an
         // index of its own is all a tree needs, so it is never reset - and a negative index
         // would be one no connection is ever opened for
         final Integer index=connectionOfTree.computeIfAbsent(treeName,
            tree -> Math.floorMod(nextConnection.getAndIncrement(), maxConnections));
         final ImportConnection open=connections.get(index);
         if (open!=null) {
            return open;
         }
         final ImportConnection taken=sharedIndexes.contains(index) ? sharedConnection() : openConnection(index);
         if (connections.get(index)!=taken) {
            // this tree was given a connection of another index, the pool having none to spare:
            // pointed at it, every record of the tree takes the lookup at the top of this method
            // rather than this path, which allocates a capture of this importer per call
            connectionOfTree.put(treeName, FIRST_CONNECTION);
         }
         return taken;
      }
      /**
       * Takes the connection of an index, or the one this import already has where the pool has
       * none left to give.
       * <p>
       * The borrow is made outside the map rather than in a {@code computeIfAbsent}: it waits for a
       * connection to be returned where the pool stands at its bound - up to
       * {@code CachedConnection.POOL_TIMEOUT_PROPERTY} - and a wait of that length inside a mapping
       * function holds the bin of that key against every other index that hashes to it, which the
       * contract of {@code ConcurrentHashMap} says not to do. One borrow per index all the same:
       * two threads first touching trees of one index would otherwise each take a connection, and
       * the loser's would be a borrow of the pool made and given back for nothing.
       * <p>
       * A connection that does not end up in the map is given back here rather than left behind:
       * only its {@code close()} returns the permit it took, and a pool is never removed from the
       * map, so one lost here would be lost for the life of the server (#878).
       */
      private ImportConnection openConnection(Integer index) {
         synchronized (borrowing.computeIfAbsent(index, i -> new Object())) {
            final ImportConnection opened=connections.get(index);
            if (opened!=null) {
               return opened; // another thread of this import got here first
            }
            if (sharedIndexes.contains(index)) {
               // ... and found the pool with nothing to spare: this thread has the same answer
               // waiting for it, and would pay the borrow deadline of the pool again to get it
               return sharedConnection();
            }
            return openConnectionOnce(index);
         }
      }
      private ImportConnection openConnectionOnce(Integer index) {
         final Connection borrowed=borrowedOrShared(index);
         if (borrowed==null) { // shared, see borrowedOrShared()
            return sharedConnection();
         }
         final ImportConnection built;
         try {
            con.close();
         } catch (Throwable e) {
            // Throwable rather than SQLException: this close() is the return to the pool, whose
            // rollback a driver is free to fail unchecked. Reported rather than thrown, since a
            // throw out of here would leave with the failure the caller actually came for - the
            // commit above, and in the Throwable branch of close() the Error that branch exists
            // to preserve - dropped on the floor (issue #878).
            final SQLException reported=e instanceof SQLException ? (SQLException) e
               : new SQLException("the connection of the import could not be returned to the pool", e);
            if (failure==null) {
               failure=reported;
            }else {
               failure.addSuppressed(reported);
            }
         } finally {
            // nothing holds the borrow until this returns: new WriteableTransactionTransactionImpl
            // runs a StampSession in a field initializer, and an Error out of a bulk import - an
            // OutOfMemoryError is the one to expect - would otherwise leave it out of the pool
            built=new ImportConnection(borrowed);
         }catch (Throwable e) {
            try {
               txw.stampSession.close();
            } finally {
               txw.catalogSession.close();
               borrowed.close();
            }catch (Throwable e2) {
               // suppressed rather than logged, the way the constructor of this importer joins the
               // same pair: the failure being unwound is the one the caller asked about, and a
               // return that failed on top of it is worth reading
               e.addSuppressed(e2);
            }
            throw e;
         }
         // Entered under the monitor of the map, which close() takes to mark this import closed and
         // to take its connections away: without it a borrow in flight could be put back after
         // close() had walked the map, leaving a connection nothing would commit or return - or,
         // worse, be released twice, the second time onto a connection the pool had already handed
         // to somebody else.
         boolean tooLate=false;
         synchronized (connections) {
            if (closed) {
               tooLate=true;
            }else {
               connections.put(index, built);
            }
         }
         if (tooLate) {
            // outside the monitor: a return is a round trip, and close() must not wait behind it
            releaseUnwatched(built);
            throw importIsClosed();
         }
         return built;
      }
      /**
       * A connection of the pool for the given index, or null for a tree that is to share the
       * connection this import already has.
       * <p>
       * The pool having none left is not a reason to fail an import: what an import must not do is
       * put two threads on one connection, and the monitor of an {@link ImportConnection} sees to
       * that whether one tree writes through it or five. So a tree that cannot be given a
       * connection of its own is given the first one instead - the import runs with less of the
       * parallelism it asked for, rather than stopping halfway through with its clears already
       * committed. Before #891 an import held one connection for all of its trees and this is what
       * that looked like.
       * <p>
       * Every answer of the pool and of the database is taken this way, not only the one that ran
       * out of time. The pool at its bound, and a database that {@code CachedConnection} waited out
       * for the whole deadline, are reported as a {@code SQLTimeoutException} - but a limit of the
       * database that its dialect table does not recognize is raised at once instead: a mysql
       * account with a {@code MAX_USER_CONNECTIONS} of its own answers 1226 on SQLState 42000, and
       * a driver of no known dialect has no vendor code read at all (issue #1011). The type is no
       * rule either: a failure whose chain names the credentials of the backend is rebuilt as a
       * plain {@code SQLException} whatever the driver threw. Sorting them here would be that
       * classification written out a second time, with the failure of a multi-hour import as the
       * cost of getting it wrong (issue #1013).
       * <p>
       * Nothing is hidden by taking them all. The credentials, the driver and the database were
       * proved by the borrow this importer was built on, so a later one fails for a reason of the
       * database or of the network - and where the database really is gone, the connection this
       * import falls back to is gone with it and the next statement fails with what actually
       * happened, which is a better report than the failure of a borrow.
       * <p>
       * An interrupt is not one of those answers: it is how phase two stops an import
       * ({@code OnDiskMergeImporter.invokeParallel} gives its threads five seconds to answer one),
       * and a thread that met it by writing the tree through another connection would be carrying
       * on with the work it was told to drop. It goes back on the thread as well - the wait of a
       * borrow clears it - since what reads it next is the executor of phase two.
       */
      private Connection borrowedOrShared(Integer index) {
         try {
            // bounded, and by the default wait of a borrow however much longer the deployment
            // made that wait: the connections this one waits for are held by this import until
            // it ends, so an unbounded wait here is a thread waiting for itself - and a long one
            // is paid over again for every index the pool has no connection to spare for
            return getConnection(false, CachedConnection.DEFAULT_POOL_TIMEOUT_SECONDS);
         }catch (SQLException e) {
            sharedIndexes.add(index);
            if (e instanceof SQLTimeoutException) {
               logger.debug(LocalizableMessage.raw("jdbc: the pool has no connection to spare for a tree of this import,"
                  + " which writes it through one it already holds: %s", stackTraceToSingleLineString(e)));
            }else {
               // louder than the wait above: that one is the deployment's own bound being
               // reached, while this is a database refusing a connection outright - the import
               // goes on with less parallelism than it asked for, and an operator reading a
               // long import has nothing else to tell them why
               logger.warn(LocalizableMessage.raw("jdbc: no further connection is given to this import, which"
                  + " writes the tree through one it already holds: %s", stackTraceToSingleLineString(e)));
            }
            return null;
         }catch (InterruptedException e) {
            // put back where the borrow found it, before the wrapper leaves this class
            Thread.currentThread().interrupt();
            throw new StorageRuntimeException(e);
         }catch (Exception e) {
            throw e instanceof StorageRuntimeException ? (StorageRuntimeException) e : new StorageRuntimeException(e);
         }
      }
      /**
       * The connection every tree of this import falls back to, which is the one its constructor
       * borrowed - the only connection an import is sure to have.
       */
      private ImportConnection sharedConnection() {
         final ImportConnection shared=connections.get(FIRST_CONNECTION);
         if (shared==null) {
            throw importIsClosed(); // close() took the connections away
         }
         return shared;
      }
      /**
       * Refuses what arrives after {@code close()}: there is no transaction of this import left to
       * join, and the connection a write would go to is committed and back in the pool, serving
       * whoever borrowed it next.
       * <p>
       * Asked again under the monitor of the connection by everything that issues a statement. The
       * flag alone is a moment in time: a thread that read it before {@code close()} raised it, and
       * reached the connection after, would write on a connection of another borrower. Read under
       * the monitor it cannot: {@code close()} takes that same monitor to commit and to return the
       * connection, so either this thread is in front of the commit and part of the import, or it
       * is behind the return and refused.
       * <p>
       * Reasoned rather than pinned by a test: what a test would have to do is park a thread
       * between the read of the flag and the monitor, and the monitor is the only boundary there
       * is to park at. That a write after {@code close()} is refused at all is asserted, on one
       * thread, by {@code ImportConnectionsTestCase}.
       */
      private void checkOpen() {
         if (closed) {
            throw importIsClosed();
         }
      }
      private StorageRuntimeException importIsClosed() {
         return new StorageRuntimeException(new IllegalStateException(
            "this import is closed: its connections are committed and back in the pool"));
      }
      /**
       * Hands one connection back to the pool and closes the sessions its transaction opened
       * beside it - the stamp one and the catalog one (#888), both outside the pool and neither
       * outliving the import that opened it - under the monitor of the connection: the return
       * rolls back and hands the connection to the next borrower, so a statement of a straggling
       * import thread must not still be in flight on it - that is the
       * two-threads-on-one-connection of #891 with another borrower's operation on the other side
       * of it.
       * <p>
       * The failure of a return is returned rather than thrown: a throw out of here would leave
       * with the failure the caller actually came for - the commit of {@code close()}, and in its
       * {@code Throwable} branch the {@code Error} that branch exists to preserve - dropped on the
       * floor, and the connections after this one unreturned (#878).
       * <p>
       * By the time {@code close()} reaches this, no statement of the import can be in flight on
       * the connection anyway: every connection of that list has been through {@code commit()}
       * under this same monitor, and a write arriving after {@code close()} raised its flag is
       * refused under it by {@code checkOpen()}. So this monitor is not what the guarantee rests
       * on today and no test can tell it apart from the commit in front of it - it is here so that
       * the guarantee does not depend on that ordering, and it is all there is on the paths that
       * reach here with no commit in front of them ({@link #releaseUnwatched(ImportConnection)}).
       *
       * @return what went wrong on the way back, or null
       */
      private SQLException release(ImportConnection connection) {
         synchronized (connection) {
            try {
               connection.con.close();
               return null;
            } catch (Throwable e) {
               // Throwable rather than SQLException: this close() is the return to the pool, whose
               // rollback a driver is free to fail unchecked.
               return e instanceof SQLException ? (SQLException) e
                  : new SQLException("a connection of the import could not be returned to the pool", e);
            } finally {
               // under the monitor with the return itself: each of these sessions holds a connection
               // of its own in a plain field that its close() reads and nulls without one
               try {
                  connection.txw.stampSession.close();
               } finally {
                  connection.txw.catalogSession.close();
               }
            }
         }
      }
      /**
       * The return of a connection no caller is waiting on - the loser of a race to open one, and
       * one borrowed into an import that closed underneath it - which has no failure of an
       * operation for a failure of the return to ride along with.
       */
      private void releaseUnwatched(ImportConnection connection) {
         final SQLException failure=release(connection);
         if (failure!=null) {
            logger.trace(LocalizableMessage.raw("jdbc: unable to return a connection of the import: %s",
               stackTraceToSingleLineString(failure)));
         }
      }
      /** The return of one connection, joined to the failure the caller is going to report. */
      private SQLException release(ImportConnection connection, SQLException failure) {
         final SQLException reported=release(connection);
         if (reported==null) {
            return failure;
         }
         if (failure==null) {
            return reported;
         }
         failure.addSuppressed(reported);
         return failure;
      }
      /**
       * Commits one connection of this import under its monitor, which is what keeps the commit
       * from being the second statement in flight on it: an import thread that did not answer the
       * interrupt of phase two can still be inside a statement while {@code close()} runs
       * ({@code OnDiskMergeImporter.invokeParallel} waits five seconds for its threads and closes
       * the importer whether they stopped or not), and two threads on one connection is the whole
       * of #891.
       * <p>
       * So a close waits for a statement of a straggler to finish, and a bulk statement carries no
       * bound of its own. That wait is not new: pgjdbc and Connector/J serialize the work of a
       * connection behind a lock of their own, so a commit issued beside a statement in flight
       * already waited there - what is new is that it waits on every dialect, sql server included,
       * rather than corrupting the driver's state on the one that does not lock.
       * <p>
       * A connection with nothing written since its last commit is left alone: the clears of
       * {@code beforePhaseOne} pass through here for every tree of a container, and a commit of an
       * empty transaction is a round trip to the database for nothing. Asked here rather than by
       * the caller, and under this monitor: read in front of it, {@code lastWrite} says what the
       * connection held rather than what it holds - a write in flight has not counted itself yet.
       * <p>
       * On both paths that reach this the wait is already paid in front of it: {@code close()}
       * reads {@code lastWrite} of every connection of the import under this same monitor before
       * it commits any of them, and {@code commitPeersOf()} holds it over this call - so no test
       * can tell this monitor from the ones ahead of it, and it is here so that what keeps two
       * threads off one connection does not rest on the order another method happens to work in.
       */
      private void commit(ImportConnection connection) throws SQLException {
         synchronized (connection) {
            if (connection.lastWrite==0) {
               return;
            }
            connection.con.commit();
            connection.lastWrite=0;
         }
      }
      /**
       * Commits the other connections of this import, which is what the one connection an import
       * used to hold did of its own accord: {@code clearTree()} ends in a commit, and that commit
       * made durable every write the import had made so far. Split over several connections and
       * left to {@code close()}, those writes would stay uncommitted while the tables they are
       * about were emptied and committed one after another.
       * <p>
       * What that is worth depends on the order the caller writes in, and the two strategies of
       * {@code OnDiskMergeImporter} differ. A {@code rebuild-index} writes the
       * {@code setTrust(false)} of {@code RebuildIndexStrategy.beforePhaseOne} before it empties
       * the trees that flag describes, so this makes the flag durable in front of the clear it
       * belongs to: a server that stops in between comes back to an index that is empty and says
       * so. An {@code import-ldif} takes {@code AbstractTwoPhaseImportStrategy.beforePhaseOne},
       * which empties every tree of the container first and writes the flags after - so there this
       * bounds how much of an import stays uncommitted (the flags of one container are made
       * durable by the clears of the next), rather than closing that window. Not a regression of
       * the connections an import now takes: the one connection it held before #891 committed in
       * exactly the same places, because the caller writes in exactly the same order.
       * <p>
       * Run in front of the clear rather than after it, so that a peer whose commit fails leaves
       * the import with the tree not yet emptied: the destructive half of a clear is the one thing
       * that must not be durable while a write it is meant to invalidate is not.
       * <p>
       * Which peers hold something to commit is decided under the monitor of each of them - by
       * {@link #commit(ImportConnection)}, which passes over a connection with nothing written
       * since its last commit - rather than by a read of {@code lastWrite} taken in front of that
       * monitor. A peer whose first write is still in flight has not set it yet ({@code put()}
       * counts a write once the statement has come back), and that is precisely the write a
       * cheaper test would pass over: {@code beforePhaseOne} runs on the import threads, one
       * container at a time per thread and several containers at once, so the flags of a container
       * being written are a peer of the clears of the next. The one connection an import held
       * before #891 carried that write into the commit of the clear - it was the same transaction,
       * and a clear issued beside it waited for it in the driver - so passing over it here would
       * be a commit point the single connection did not have.
       * <p>
       * What that costs is the wait: a clear now waits for a statement in flight on each peer, as
       * every write of an import waited for the one connection it all went through.
       * <p>
       * One connection is held at a time, so no thread of an import ever holds two of these
       * monitors and two threads clearing at once cannot wait for each other.
       */
      private void commitPeersOf(ImportConnection cleared) {
         for (final ImportConnection peer : connections.values()) {
            if (peer==cleared) {
               continue;
            }
            try {
               synchronized (peer) {
                  // under the monitor, like every other statement of an import: a clear that
                  // arrives once close() has committed and returned this connection would
                  // otherwise commit the transaction of whoever borrowed it next
                  checkOpen();
                  commit(peer);
               }
            }catch (SQLException e) {
               // reported the way the commit of the clear itself is: what these make durable is
               // the work the clear is the commit point for
               throw new StorageRuntimeException(e);
            }
         }
      }
      /**
       * Commits the given connections in the order they were last written to - see {@link #writes}.
       * <p>
       * The order is taken from a copy of what each connection carries rather than read as the sort
       * goes: a straggling import thread that got in front of {@code close()} can raise the number
       * of the connection it holds while this runs, and a sort whose keys move under it is reported
       * by {@code TimSort} as a comparator that violates its contract rather than as the race it is.
       */
      private void commitAll(List<ImportConnection> taken) throws SQLException {
         final Map<ImportConnection,Long> lastWrites=new IdentityHashMap<>();
         for (final ImportConnection connection : taken) {
            synchronized (connection) {
               lastWrites.put(connection, connection.lastWrite);
            }
         }
         final List<ImportConnection> byLastWrite=new ArrayList<>(taken);
         byLastWrite.sort(Comparator.comparingLong(lastWrites::get));
         for (final ImportConnection connection : byLastWrite) {
            commit(connection);
         }
      }
      /**
       * Hands every connection this import took back to the pool, whatever went before. Returns
       * the failure the caller is to report: the return rolls back, and the rollback fails on
       * exactly the connection whose commit just did, so the commit stays the exception the caller
       * sees and this one rides along with it instead of replacing it.
       * <p>
       * Every connection is released even when one of them fails on the way: what a connection
       * left behind holds is a permit of the pool, and a pool is never removed from the map.
       */
      private SQLException releaseConnections(List<ImportConnection> taken, SQLException failure) {
         for (final ImportConnection connection : taken) {
            failure=release(connection, failure);
         }
         return failure;
      }
      // The connection goes back whatever the commit does, and the storage this importer opened
      // is closed whatever the connection does: an importer is closed on the way out of a failed
      // import as readily as a finished one - a clearTree() that reaches the bulk bound is one
      // way there - and a commit that throws on the way would otherwise leave the connection
      // out of the pool for good, holding the transaction and the locks of that import.
      // The connections go back whatever the commit does, and the storage this importer opened
      // is closed whatever they do: an importer is closed on the way out of a failed import as
      // readily as a finished one - a clearTree() that reaches the bulk bound is one way there -
      // and a commit that throws on the way would otherwise leave the connections out of the
      // pool for good, holding the transactions and the locks of that import.
      //
      // Every connection is committed, not only the one the constructor borrowed: each is a
      // transaction of its own, and what one of them holds uncommitted is the work of every tree
      // it was given (#891).
      @Override
      public void close() {
         try {
            // Taken out of the map rather than walked in it, under the monitor that guards it:
            // from here this import has no transaction left for a write to belong to, a borrow
            // still in flight is refused rather than left behind, and a second close() finds
            // nothing to commit or return - one that walked the map again would roll back and
            // re-pool connections the pool had already handed to somebody else.
            final List<ImportConnection> taken;
            final ImportConnection describing;
            synchronized (connections) {
               closed=true;
               describing=connections.get(FIRST_CONNECTION);
               taken=new ArrayList<>(connections.values());
               connections.clear();
            }
            SQLException failure=null;
            try {
               con.commit();
               if (aborted) {
                  logger.debug(LocalizableMessage.raw("jdbc: import aborted: statistics of the trees it wrote are left alone"));
               }else {
                  updateTableStatistics(con, writtenTrees);
               }
               commitAll(taken);
            } catch (SQLException e) {
               failure=e;
            } catch (Throwable t) {
               // Back to the pool whatever came out of the commit, not only on the SQLException
               // a driver is supposed to throw: nothing else holds this connection, and only
               // its close() gives back the permit it took. A pool is never removed from the
               // map, so a permit lost to an Error out of a bulk import - or to a driver
               // a driver is supposed to throw: nothing else holds these connections, and only
               // their close() gives back the permits they took. A pool is never removed from
               // the map, so a permit lost to an Error out of a bulk import - or to a driver
               // failing unchecked - is lost for the life of the server, and enough of them
               // walk the bound down to nothing (issue #878).
               final SQLException onTheWayOut=releaseConnection(null);
               final SQLException onTheWayOut=releaseConnections(taken, null);
               if (onTheWayOut!=null) {
                  t.addSuppressed(onTheWayOut);
               }
               throw t;
            }
            // Back to the pool even when the commit failed: nothing else holds this connection,
            // Everything but the connection that describes goes back before the statistics are
            // gathered: that is one statement per tree the import wrote, a full scan of the table
            // on oracle and bounded by a property of its own, and the connections of an import are
            // the pool's to hand to the operations of the server as soon as they are committed.
            for (final ImportConnection connection : taken) {
               if (connection!=describing) {
                  failure=release(connection, failure);
               }
            }
            try {
               if (aborted) {
                  logger.debug(LocalizableMessage.raw("jdbc: import aborted: statistics of the trees it wrote are left alone"));
               }else if (describing!=null && failure==null) {
                  // On the connection the constructor borrowed, under its monitor like every
                  // other statement of an import: the statements below describe a table to the
                  // optimizer rather than read one, and they run once every connection of this
                  // import is committed - so there is no work of another one left for them to
                  // miss.
                  synchronized (describing) {
                     updateTableStatistics(describing.con, writtenTrees);
                  }
               }
            } catch (Throwable t) {
               if (describing!=null) {
                  final SQLException onTheWayOut=release(describing, null);
                  if (onTheWayOut!=null) {
                     t.addSuppressed(onTheWayOut);
                  }
               }
               throw t;
            }
            // Back to the pool even when a commit failed: nothing else holds this connection,
            // so leaving it behind would leak it along with the failure.
            failure=releaseConnection(failure);
            if (describing!=null) {
               failure=release(describing, failure);
            }
            if (failure!=null) {
               throw new StorageRuntimeException(failure);
            }
@@ -5062,21 +5694,36 @@
      @Override
      public void clearTree(TreeName name) {
         txw.clearTree(name);
         final ImportConnection connection=connectionOf(name);
         commitPeersOf(connection); // in front of the clear, see there
         synchronized (connection) {
            checkOpen();
            connection.txw.clearTree(name);
            connection.lastWrite=0; // the clear ends in a commit of this connection
         }
         writtenTrees.add(name);
      }
      @Override
      public void put(TreeName treeName, ByteSequence key, ByteSequence value) {
         txw.put(treeName, key, value);
         final ImportConnection connection=connectionOf(treeName);
         synchronized (connection) {
            checkOpen();
            connection.txw.put(treeName, key, value);
            connection.written();
         }
         writtenTrees.add(treeName);
      }
      @Override
      public ByteString read(TreeName treeName, ByteSequence key) {
         return txr.read(treeName, key);
         final ImportConnection connection=connectionOf(treeName);
         synchronized (connection) {
            checkOpen();
            return connection.txr.read(treeName, key);
         }
      }
      // Bulk like every other statement of an import, by the class of the transaction it comes
      // from: this walks a whole tree with no client waiting on it - phase one of a rebuild-index
      // reads every record of id2entry through this cursor (OnDiskMergeImporter.ID2EntrySource) -
@@ -5084,7 +5731,88 @@
      // rather than a step along an index.
      @Override
      public SequentialCursor<ByteString, ByteString> openCursor(TreeName treeName) {
         return txr.openCursor(treeName);
         final ImportConnection connection=connectionOf(treeName);
         synchronized (connection) {
            checkOpen();
            return new ImportCursor(connection, connection.txr.openCursor(treeName));
         }
      }
      /**
       * A cursor of an import, every method of which runs under the monitor of the connection it
       * walks. An import has more trees than connections, so the tree this cursor walks shares
       * its connection with the trees written through it, and a batch of this cursor must not be
       * in flight there beside a statement of one of them.
       * <p>
       * The methods that issue no statement take the monitor as well, rather than being excused
       * on the ground that the thread which opened the cursor is the only one to call them:
       * nothing here enforces that, and what they read - the batch the last {@code next()} left
       * in {@link CursorImpl}, an {@code ArrayDeque} and four plain fields - is written under
       * that monitor and carries no memory barrier of its own. An uncontended monitor is what
       * that costs.
       */
      private final class ImportCursor implements SequentialCursor<ByteString, ByteString> {
         private final ImportConnection connection;
         private final SequentialCursor<ByteString, ByteString> cursor;
         ImportCursor(ImportConnection connection, SequentialCursor<ByteString, ByteString> cursor) {
            this.connection=connection;
            this.cursor=cursor;
         }
         @Override
         public boolean next() {
            synchronized (connection) {
               checkOpen();
               return cursor.next();
            }
         }
         // A cursor of an import is opened on the read transaction of its connection, whose
         // isReadOnly CursorImpl carries, so this forwards the refusal that transaction answers
         // with rather than a delete. Nothing is recorded against the connection for that reason:
         // a mark here would be a write number this cursor never made, and the highest one at
         // that - it would move its connection to the end of the order close() commits in, which
         // is where the trust flags of afterPhaseTwo belong. An importer that ever opens a
         // writeable cursor has to record the write there, next to the delete that made it.
         @Override
         public void delete() {
            synchronized (connection) {
               checkOpen();
               cursor.delete();
            }
         }
         @Override
         public boolean isDefined() {
            synchronized (connection) {
               return cursor.isDefined();
            }
         }
         @Override
         public ByteString getKey() {
            synchronized (connection) {
               return cursor.getKey();
            }
         }
         @Override
         public ByteString getValue() {
            synchronized (connection) {
               return cursor.getValue();
            }
         }
         // Not refused after close() the way a read or a write of the importer is: this frees what
         // the last batch left in the cursor and reaches no connection, and the try-with-resources
         // of a cancelled phase-two task closes its cursor after the importer it walked.
         @Override
         public void close() {
            synchronized (connection) {
               cursor.close();
            }
         }
      }
   }
   
@@ -5092,9 +5820,9 @@
   @Override
   public Importer startImport() throws ConfigException, StorageRuntimeException {
      // Everything this used to do before building the importer - opening a closed storage, and
      // borrowing the connection an import keeps for its whole duration - is the importer's own now
      // (#878). Split between the two, a failure in between had to be given back by whichever of them
      // had taken what, and the constructor's own throw was covered by neither.
      // borrowing the first of the connections an import keeps for its whole duration - is the
      // importer's own now (#878). Split between the two, a failure in between had to be given back
      // by whichever of them had taken what, and the constructor's own throw was covered by neither.
      return new ImporterImpl();
   }