1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.dbi;
9 
10 import static com.sleepycat.je.log.entry.DbOperationType.CREATE;
11 import static com.sleepycat.je.log.entry.DbOperationType.REMOVE;
12 import static com.sleepycat.je.log.entry.DbOperationType.RENAME;
13 import static com.sleepycat.je.log.entry.DbOperationType.TRUNCATE;
14 import static com.sleepycat.je.log.entry.DbOperationType.UPDATE_CONFIG;
15 
16 import java.io.PrintStream;
17 import java.nio.ByteBuffer;
18 import java.util.ArrayList;
19 import java.util.EnumSet;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.concurrent.atomic.AtomicLong;
24 
25 import com.sleepycat.je.Database;
26 import com.sleepycat.je.DatabaseConfig;
27 import com.sleepycat.je.DatabaseEntry;
28 import com.sleepycat.je.DatabaseException;
29 import com.sleepycat.je.DatabaseNotFoundException;
30 import com.sleepycat.je.DbInternal;
31 import com.sleepycat.je.EnvironmentFailureException;
32 import com.sleepycat.je.LockConflictException;
33 import com.sleepycat.je.VerifyConfig;
34 import com.sleepycat.je.log.DbOpReplicationContext;
35 import com.sleepycat.je.log.LogUtils;
36 import com.sleepycat.je.log.Loggable;
37 import com.sleepycat.je.log.ReplicationContext;
38 import com.sleepycat.je.tree.ChildReference;
39 import com.sleepycat.je.tree.IN;
40 import com.sleepycat.je.tree.LN;
41 import com.sleepycat.je.tree.MapLN;
42 import com.sleepycat.je.tree.NameLN;
43 import com.sleepycat.je.tree.Tree;
44 import com.sleepycat.je.tree.TreeUtils;
45 import com.sleepycat.je.tree.WithRootLatched;
46 import com.sleepycat.je.txn.BasicLocker;
47 import com.sleepycat.je.txn.HandleLocker;
48 import com.sleepycat.je.txn.LockGrantType;
49 import com.sleepycat.je.txn.LockResult;
50 import com.sleepycat.je.txn.LockType;
51 import com.sleepycat.je.txn.Locker;
52 import com.sleepycat.je.utilint.DbLsn;
53 import com.sleepycat.utilint.StringUtils;
54 
55 /**
56  * DbTree represents the database directory for this environment. DbTree is
57  * itself implemented through two databases. The nameDatabase maps
58  * databaseName-> an internal databaseId. The idDatabase maps
59  * databaseId->DatabaseImpl.
60  *
61  * For example, suppose we have two databases, foo and bar. We have the
62  * following structure:
63  *
64  *           nameDatabase                          idDatabase
65  *               IN                                    IN
66  *                |                                     |
67  *               BIN                                   BIN
68  *    +-------------+--------+            +---------------+--------+
69  *  .               |        |            .               |        |
70  * NameLNs         NameLN    NameLN      MapLNs for   MapLN        MapLN
71  * for internal    key=bar   key=foo     internal dbs key=53       key=79
72  * dbs             data=     data=                    data=        data=
73  *                 dbId79    dbId53                   DatabaseImpl DatabaseImpl
74  *                                                        |            |
75  *                                                   Tree for foo  Tree for bar
76  *                                                        |            |
77  *                                                     root IN       root IN
78  *
79  * Databases, Cursors, the cleaner, compressor, and other entities have
80  * references to DatabaseImpls. It's important that object identity is properly
81  * maintained, and that all constituents reference the same DatabaseImpl for
82  * the same db, lest they develop disparate views of the in-memory database;
83  * corruption would ensue. To ensure that, all entities must obtain their
84  * DatabaseImpl by going through the idDatabase.
85  *
86  * DDL type operations such as create, rename, remove and truncate get their
87  * transactional semantics by transactionally locking the NameLN appropriately.
88  * A read-lock on the NameLN, called a handle lock, is maintained for all DBs
89  * opened via the public API (openDatabase).  This prevents them from being
90  * renamed or removed while open.  See HandleLocker for details.
91  *
92  * However, for internal database operations, no handle lock on the NameLN is
93  * acquired and MapLNs are locked with short-lived non-transactional Lockers.
94  * An entity that is trying to get a reference to the DatabaseImpl gets a short
95  * lived read lock just for the fetch of the MapLN, and a DatabaseImpl usage
96  * count is incremented to prevent eviction; see getDb and releaseDb. A write
97  * lock on the MapLN is taken when the database is created, deleted, or when
98  * the MapLN is evicted. (see DatabaseImpl.isInUse())
99  *
100  * The nameDatabase operates pretty much as a regular application database in
101  * terms of eviction and recovery. The idDatabase requires special treatment
102  * for both eviction and recovery.
103  *
104  * The issues around eviction of the idDatabase center on the need to ensure
105  * that there are no other current references to the DatabaseImpl other than
106  * that held by the mapLN. The presence of a current reference would both make
107  * the DatabaseImpl not GC'able, and more importantly, would lead to object
108  * identity confusion later on. For example, if the MapLN is evicted while
109  * there is a current reference to its DatabaseImpl, and then refetched, there
110  * will be two in-memory versions of the DatabaseImpl. Since locks on the
111  * idDatabase are short lived, DatabaseImpl.useCount acts as a reference count
112  * of active current references. DatabaseImpl.useCount must be modified and
113  * read in conjunction with appropriate locking on the MapLN. See
114  * DatabaseImpl.isInUse() for details.
115  *
116  * This reference count checking is only needed when the entire MapLN is
117  * evicted. It's possible to evict only the root IN of the database in
118  * question, since that doesn't interfere with the DatabaseImpl object
119  * identity.
120  *
121  * Another dependency on usage counts was introduced to prevent MapLN deletion
122  * during cleaner and checkpointer operations that are processing entries for a
123  * DB.  (Without usage counts, this problem would have occurred even if DB
124  * eviction were never implemented.)  When the usage count is non-zero it
125  * prohibits deleteMapLN from running. The deleted state of the MapLN must not
126  * change during a reader operation (operation by a thread that has called
127  * getDb and not yet called releaseDb).
128  *
129  * Why not just hold a MapLN read lock during a reader operation?
130  * --------------------------------------------------------------
131  * Originally this was not done because of cleaner performance.  We afraid that  * either of the following solutions would not perform well:
132  *   + If we get (and release) a MapLN lock for every entry in a log file, this
133  *     adds a lot of per-entry overhead.
134  *   + If we hold the MapLN read lock for the duration of a log file cleaning
135  *     (the assumption is that many entries are for the same DB), then we block
136  *     checkpoints during that period, when they call modifyDbRoot.
137  * Therefore, the usage count is incremented once per DB encountered during log
138  * cleaning, and the count is decremented at the end.  This caching approach is
139  * also used by the HA replayer.  In both cases, we do not want to lock the
140  * MapLN every entry/operation, and we do not want to block checkpoints or
141  * other callers of modifyDbRoot.  It is acceptable, however, to block DB
142  * naming operations.
143  *
144  * In addition we allow modifyDbRoot to run when even the usage count is
145  * non-zero, which would not be possible using a read-write locking strategy.
146  * I'm not sure why this was done originally, perhaps to avoid blocking.  But
147  * currently, it is necessary to prevent a self-deadlock.  All callers of
148  * modifyDbRoot first call getDb, which increments the usage count.  So if
149  * modifyDbRoot was to check the usage count and retry if non-zero (like
150  * deleteMapLN), then it would loop forever.
151  *
152  * Why are the retry loops necessary in the DbTree methods?
153  * --------------------------------------------------------
154  * Three methods that access the MapLN perform retries (forever) when there is
155  * a lock conflict: getDb, modifyDbRoot and deleteMapLN.  Initially the retry
156  * loops were added to compensate for certain slow operations. To solve that
157  * problem, perhaps there are alternative solutions (increasing the lock
158  * timeout).  However, the deleteMapLN retry loop is necessary to avoid
159  * deleting it when the DB is in use by reader operations.
160  *
161  * Tendency to livelock
162  * --------------------
163  * Because MapLN locks are short lived, but a reader operation may hold a
164  * MapLN/DatabaseImpl for a longer period by incrementing the usage count,
165  * there is the possibility of livelock.  One strategy for avoiding livelock is
166  * to avoid algorithms where multiple threads continuously call getDb and
167  * releaseDb, since this could prevent completion of deleteMapLN.  [#20816]
168  */
169 public class DbTree implements Loggable {
170 
171     /* The id->DatabaseImpl tree is always id 0 */
172     public static final DatabaseId ID_DB_ID = new DatabaseId(0);
173     /* The name->id tree is always id 1 */
174     public static final DatabaseId NAME_DB_ID = new DatabaseId(1);
175 
176     /** Map from internal DB name to type. */
177     private final static Map<String, DbType> INTERNAL_TYPES_BY_NAME;
178     static {
179         final EnumSet<DbType> set = EnumSet.allOf(DbType.class);
180         INTERNAL_TYPES_BY_NAME = new HashMap<String, DbType>(set.size());
181         for (DbType t : set) {
182             if (t.isInternal()) {
t.getInternalName()183                 INTERNAL_TYPES_BY_NAME.put(t.getInternalName(), t);
184             }
185         }
186     }
187 
188     /**
189      * Returns the DbType for a given DB name.
190      *
191      * Note that we allow dbName to be null, because it may be null when the
192      * 'debug database name' is not yet known to DatabaseImpl.  This works
193      * because the debug name is always known immediately for internal DBs.
194      */
typeForDbName(String dbName)195     public static DbType typeForDbName(String dbName) {
196         final DbType t = INTERNAL_TYPES_BY_NAME.get(dbName);
197         if (t != null) {
198             return t;
199         }
200         return DbType.USER;
201     }
202 
203     /*
204      * Database Ids:
205      * We need to ensure that local and replicated databases use different
206      * number spaces for their ids, so there can't be any possible conflicts.
207      * Local, non replicated databases use positive values, replicated
208      * databases use negative values.  Values -1 thru NEG_DB_ID_START are
209      * reserved for future special use.
210      */
211     public static final long NEG_DB_ID_START = -256L;
212     private final AtomicLong lastAllocatedLocalDbId;
213     private final AtomicLong lastAllocatedReplicatedDbId;
214 
215     private final DatabaseImpl idDatabase;          // map db ids -> databases
216     private final DatabaseImpl nameDatabase;        // map names -> dbIds
217 
218     /* The flags byte holds a variety of attributes. */
219     private byte flags;
220 
221     /*
222      * The replicated bit is set for environments that are opened with
223      * replication. The behavior is as follows:
224      *
225      * Env is     Env is     Persistent          Follow-on action
226      * replicated brand new  value of
227      *                       DbTree.isReplicated
228      *
229      * 0             1         n/a               replicated bit = 0;
230      * 0             0           0               none
231      * 0             0           1               true for r/o, false for r/w
232      * 1             1          n/a              replicated bit = 1
233      * 1             0           0               require config of all dbs
234      * 1             0           1               none
235      */
236     private static final byte REPLICATED_BIT = 0x1;
237 
238     /*
239      * The rep converted bit is set when an environments was originally created
240      * as a standalone (non-replicated) environment, and has been changed to a
241      * replicated environment.
242      *
243      * The behaviors are as follows:
244      *
245      * Value of      Value of the    What happens      Can open     Can open
246      * RepConfig.      DbTree        when we call       as r/o       as r/2
247      * allowConvert  replicated bit  ReplicatedEnv()  Environment  Environment
248      *                                                   later on?   later on?
249      *
250      *                           throw exception,   Yes, because  Yes, because
251      *                            complain that the    env is not   env is not
252      *  false          false         env is not        converted    converted
253      *                               replicated
254      *
255      *                                              Yes, always ok  No, this is
256      *                                                 open a         now a
257      *  true           false          do conversion   replicated     replicated
258      *                                               env with r/o       env
259      *
260      *
261      *  Ignore         true or      open as a replicated
262      * allowConvert   brand new      env the usual way       Yes         No
263      *               Environment
264      */
265     private static final byte REP_CONVERTED_BIT = 0x2;
266 
267     /*
268      * The dups converted bit is set when we have successfully converted all
269      * dups databases after recovery, to indicate that we don't need to perform
270      * this conversion again for this environment.  It is set initially for a
271      * brand new environment that uses the new dup database format.
272      */
273     private static final byte DUPS_CONVERTED_BIT = 0x4;
274 
275     /*
276      * The preserve VLSN bit is set in a replicated environment only, and may
277      * never be changed after initial environment creation.  See
278      * RepParams.PRESERVE_RECORD_VERSION.
279      */
280     private static final byte PRESERVE_VLSN_BIT = 0x8;
281 
282     /**
283      * Number of LNs in the naming DB considered to be fairly small, and
284      * therefore to result in fairly fast execution of getDbName.
285      */
286     private static final long FAST_NAME_LOOKUP_MAX_LNS = 100;
287 
288     private EnvironmentImpl envImpl;
289 
290     /**
291      * Create a dbTree from the log.
292      */
DbTree()293     public DbTree() {
294         this.envImpl = null;
295         idDatabase = new DatabaseImpl();
296         idDatabase.setDebugDatabaseName(DbType.ID.getInternalName());
297 
298         /*
299          * The default is false, but just in case we ever turn it on globally
300          * for testing this forces it off.
301          */
302         idDatabase.clearKeyPrefixing();
303         nameDatabase = new DatabaseImpl();
304         nameDatabase.clearKeyPrefixing();
305         nameDatabase.setDebugDatabaseName(DbType.NAME.getInternalName());
306 
307         /* These sequences are initialized by readFromLog. */
308         lastAllocatedLocalDbId = new AtomicLong();
309         lastAllocatedReplicatedDbId = new AtomicLong();
310     }
311 
312     /**
313      * Create a new dbTree for a new environment.
314      */
DbTree(EnvironmentImpl env, boolean replicationIntended, boolean preserveVLSN)315     public DbTree(EnvironmentImpl env,
316                   boolean replicationIntended,
317                   boolean preserveVLSN)
318         throws DatabaseException {
319 
320         this.envImpl = env;
321 
322         /*
323          * Sequences must be initialized before any databases are created.  0
324          * and 1 are reserved, so we start at 2. We've put -1 to
325          * NEG_DB_ID_START asided for the future.
326          */
327         lastAllocatedLocalDbId = new AtomicLong(1);
328         lastAllocatedReplicatedDbId = new AtomicLong(NEG_DB_ID_START);
329 
330         /* The id database is local */
331         DatabaseConfig idConfig = new DatabaseConfig();
332         idConfig.setReplicated(false /* replicated */);
333 
334         /*
335          * The default is false, but just in case we ever turn it on globally
336          * for testing this forces it off.
337          */
338         idConfig.setKeyPrefixing(false);
339         idDatabase = new DatabaseImpl(null,
340                                       DbType.ID.getInternalName(),
341                                       new DatabaseId(0),
342                                       env,
343                                       idConfig);
344         /* Force a reset if enabled globally. */
345         idDatabase.clearKeyPrefixing();
346 
347         DatabaseConfig nameConfig = new DatabaseConfig();
348         nameConfig.setKeyPrefixing(false);
349         nameDatabase = new DatabaseImpl(null,
350                                         DbType.NAME.getInternalName(),
351                                         new DatabaseId(1),
352                                         env,
353                                         nameConfig);
354         /* Force a reset if enabled globally. */
355         nameDatabase.clearKeyPrefixing();
356 
357         if (replicationIntended) {
358             setIsReplicated();
359         }
360 
361         if (preserveVLSN) {
362             setPreserveVLSN();
363         }
364 
365         /* New environments don't need dup conversion. */
366         setDupsConverted();
367     }
368 
369     /**
370      * The last allocated local and replicated db ids are used for ckpts.
371      */
getLastLocalDbId()372     public long getLastLocalDbId() {
373         return lastAllocatedLocalDbId.get();
374     }
375 
getLastReplicatedDbId()376     public long getLastReplicatedDbId() {
377         return lastAllocatedReplicatedDbId.get();
378     }
379 
380     /**
381      * We get a new database id of the appropriate kind when creating a new
382      * database.
383      */
getNextLocalDbId()384     private long getNextLocalDbId() {
385         return lastAllocatedLocalDbId.incrementAndGet();
386     }
387 
getNextReplicatedDbId()388     private long getNextReplicatedDbId() {
389         return lastAllocatedReplicatedDbId.decrementAndGet();
390     }
391 
392     /**
393      * Initialize the db ids, from recovery.
394      */
setLastDbId(long lastReplicatedDbId, long lastLocalDbId)395     public void setLastDbId(long lastReplicatedDbId, long lastLocalDbId) {
396         lastAllocatedReplicatedDbId.set(lastReplicatedDbId);
397         lastAllocatedLocalDbId.set(lastLocalDbId);
398     }
399 
400     /**
401      * @return true if this id is for a replicated db.
402      */
isReplicatedId(long id)403     private boolean isReplicatedId(long id) {
404         return id < NEG_DB_ID_START;
405     }
406 
407     /*
408      * Tracks the lowest replicated database id used during a replay of the
409      * replication stream, so that it's available as the starting point if this
410      * replica transitions to being the master.
411      */
updateFromReplay(DatabaseId replayDbId)412     public void updateFromReplay(DatabaseId replayDbId) {
413         assert !envImpl.isMaster();
414 
415         final long replayVal = replayDbId.getId();
416 
417         if (replayVal > 0 && !envImpl.isRepConverted()) {
418             throw EnvironmentFailureException.unexpectedState
419                 ("replay database id is unexpectedly positive " + replayDbId);
420         }
421 
422         if (replayVal < lastAllocatedReplicatedDbId.get()) {
423             lastAllocatedReplicatedDbId.set(replayVal);
424         }
425     }
426 
427     /**
428      * Initialize the db tree during recovery, after instantiating the tree
429      * from the log.
430      * a. set up references to the environment impl
431      * b. check for replication rules.
432      */
initExistingEnvironment(EnvironmentImpl eImpl)433     void initExistingEnvironment(EnvironmentImpl eImpl)
434         throws DatabaseException {
435 
436         eImpl.checkRulesForExistingEnv(isReplicated(), getPreserveVLSN());
437         this.envImpl = eImpl;
438         idDatabase.setEnvironmentImpl(eImpl);
439         nameDatabase.setEnvironmentImpl(eImpl);
440     }
441 
442     /**
443      * Creates a new database object given a database name.
444      *
445      * Increments the use count of the new DB to prevent it from being evicted.
446      * releaseDb should be called when the returned object is no longer used,
447      * to allow it to be evicted.  See DatabaseImpl.isInUse.  [#13415]
448      */
createDb(Locker locker, String databaseName, DatabaseConfig dbConfig, HandleLocker handleLocker)449     public DatabaseImpl createDb(Locker locker,
450                                  String databaseName,
451                                  DatabaseConfig dbConfig,
452                                  HandleLocker handleLocker)
453         throws DatabaseException {
454 
455         return doCreateDb(locker,
456                           databaseName,
457                           dbConfig,
458                           handleLocker,
459                           null,  // replicatedLN
460                           null); // repContext, to be decided by new db
461     }
462 
463     /**
464      * Create a database for internal use. It may or may not be replicated.
465      * Since DatabaseConfig.replicated is true by default, be sure to
466      * set it to false if this is a internal, not replicated database.
467      */
createInternalDb(Locker locker, String databaseName, DatabaseConfig dbConfig)468     public DatabaseImpl createInternalDb(Locker locker,
469                                          String databaseName,
470                                          DatabaseConfig dbConfig)
471         throws DatabaseException {
472 
473         /* Force all internal databases to not use key prefixing. */
474         dbConfig.setKeyPrefixing(false);
475         DatabaseImpl ret =
476             doCreateDb(locker,
477                        databaseName,
478                        dbConfig,
479                        null,  // handleLocker,
480                        null,  // replicatedLN
481                        null); // repContext, to be decided by new db
482         /* Force a reset if enabled globally. */
483         ret.clearKeyPrefixing();
484         return ret;
485     }
486 
487     /**
488      * Create a replicated database on this client node.
489      */
createReplicaDb(Locker locker, String databaseName, DatabaseConfig dbConfig, NameLN replicatedLN, ReplicationContext repContext)490     public DatabaseImpl createReplicaDb(Locker locker,
491                                         String databaseName,
492                                         DatabaseConfig dbConfig,
493                                         NameLN replicatedLN,
494                                         ReplicationContext repContext)
495         throws DatabaseException {
496 
497         return doCreateDb(locker,
498                           databaseName,
499                           dbConfig,
500                           null, // databaseHndle
501                           replicatedLN,
502                           repContext);
503     }
504 
505     /**
506      * Create a database.
507      *
508      * Increments the use count of the new DB to prevent it from being evicted.
509      * releaseDb should be called when the returned object is no longer used,
510      * to allow it to be evicted.  See DatabaseImpl.isInUse.  [#13415]
511      *
512      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
513      * level DbTree operation. [#15176]
514      */
doCreateDb(Locker nameLocker, String databaseName, DatabaseConfig dbConfig, HandleLocker handleLocker, NameLN replicatedLN, ReplicationContext repContext)515     private DatabaseImpl doCreateDb(Locker nameLocker,
516                                     String databaseName,
517                                     DatabaseConfig dbConfig,
518                                     HandleLocker handleLocker,
519                                     NameLN replicatedLN,
520                                     ReplicationContext repContext)
521         throws DatabaseException {
522 
523         /* Create a new database object. */
524         DatabaseId newId = null;
525         long allocatedLocalDbId = 0;
526         long allocatedRepDbId = 0;
527         if (replicatedLN != null) {
528 
529             /*
530              * This database was created on a master node and is being
531              * propagated to this client node.
532              */
533             newId = replicatedLN.getId();
534         } else {
535 
536             /*
537              * This database has been created locally, either because this is
538              * a non-replicated node or this is the replicated group master.
539              */
540             if (envImpl.isReplicated() &&
541                 dbConfig.getReplicated()) {
542                 newId = new DatabaseId(getNextReplicatedDbId());
543                 allocatedRepDbId = newId.getId();
544             } else {
545                 newId = new DatabaseId(getNextLocalDbId());
546                 allocatedLocalDbId = newId.getId();
547             }
548         }
549 
550         DatabaseImpl newDb = null;
551         CursorImpl idCursor = null;
552         CursorImpl nameCursor = null;
553         boolean operationOk = false;
554         Locker idDbLocker = null;
555         try {
556             newDb = new DatabaseImpl(nameLocker,
557                                      databaseName, newId, envImpl, dbConfig);
558 
559             /* Get effective rep context and check for replica write. */
560             ReplicationContext useRepContext = repContext;
561             if (repContext == null) {
562                 useRepContext = newDb.getOperationRepContext(CREATE);
563             }
564             checkReplicaWrite(nameLocker, useRepContext);
565 
566             /* Insert it into name -> id db. */
567             nameCursor = new CursorImpl(nameDatabase, nameLocker);
568             LN nameLN = null;
569             if (replicatedLN != null) {
570                 nameLN = replicatedLN;
571             } else {
572                 nameLN = new NameLN(newId);
573             }
574 
575             nameCursor.insertRecord(
576                 StringUtils.toUTF8(databaseName), // key
577                 nameLN, false /*blindInsertion*/, useRepContext);
578 
579             /* Record handle lock. */
580             if (handleLocker != null) {
581                 acquireHandleLock(nameCursor, handleLocker);
582             }
583 
584             /* Insert it into id -> name db, in auto commit mode. */
585             idDbLocker = BasicLocker.createBasicLocker(envImpl);
586             idCursor = new CursorImpl(idDatabase, idDbLocker);
587 
588             idCursor.insertRecord(
589                 newId.getBytes() /*key*/, new MapLN(newDb) /*ln*/,
590                 false /*blindInsertion*/, ReplicationContext.NO_REPLICATE);
591 
592             /* Increment DB use count with lock held. */
593             newDb.incrementUseCount();
594             operationOk = true;
595         } finally {
596             if (idCursor != null) {
597                 idCursor.close();
598             }
599 
600             if (nameCursor != null) {
601                 nameCursor.close();
602             }
603 
604             if (idDbLocker != null) {
605                 idDbLocker.operationEnd(operationOk);
606             }
607 
608             /*
609              * Undo the allocation of the database ID if DB creation fails.  We
610              * use compareAndSet so that we don't undo the assignment of the ID
611              * by another concurrent operation, for example, truncation.
612              *
613              * Note that IDs are not conserved in doTruncateDb when a failure
614              * occurs.  This inconsistency is historical and may or may not be
615              * the best approach.
616              *
617              * [#18642]
618              */
619             if (!operationOk) {
620                 if (allocatedRepDbId != 0) {
621                     lastAllocatedReplicatedDbId.compareAndSet
622                         (allocatedRepDbId, allocatedRepDbId + 1);
623                 }
624                 if (allocatedLocalDbId != 0) {
625                     lastAllocatedLocalDbId.compareAndSet
626                         (allocatedLocalDbId, allocatedLocalDbId - 1);
627                 }
628             }
629         }
630 
631         return newDb;
632     }
633 
634     /**
635      * Called after locking a NameLN with nameCursor when opening a database.
636      * The NameLN may be locked for read or write, depending on whether the
637      * database existed when openDatabase was called.  Here we additionally
638      * lock the NameLN for read on behalf of the handleLocker, which is kept
639      * by the Database handle.
640      *
641      * The lock must be acquired while the BIN is latched, so the locker will
642      * be updated if the LSN changes.  There is no lock contention possible
643      * because the HandleLocker shares locks with the nameCursor locker, and
644      * jumpAheadOfWaiters=true is passed in case another locker is waiting on a
645      * write lock.
646      *
647      * If the lock is denied, checkPreempted is called on the nameCursor
648      * locker, in case the lock is denied because the nameCursor's lock was
649      * preempted. If so, DatabasePreemptedException will be thrown.
650      *
651      * @see CursorImpl#lockLN
652      * @see HandleLocker
653      */
acquireHandleLock(CursorImpl nameCursor, HandleLocker handleLocker)654     private void acquireHandleLock(CursorImpl nameCursor,
655                                    HandleLocker handleLocker) {
656         nameCursor.latchBIN();
657         try {
658             final long lsn = nameCursor.getCurrentLsn();
659 
660             final LockResult lockResult = handleLocker.nonBlockingLock
661                 (lsn, LockType.READ, true /*jumpAheadOfWaiters*/,
662                  nameDatabase);
663 
664             if (lockResult.getLockGrant() == LockGrantType.DENIED) {
665                 nameCursor.getLocker().checkPreempted(null);
666                 throw EnvironmentFailureException.unexpectedState
667                     ("No contention is possible with HandleLocker: " +
668                      DbLsn.getNoFormatString(lsn));
669             }
670         } finally {
671             nameCursor.releaseBIN();
672         }
673     }
674 
675     /**
676      * Check deferred write settings before writing the MapLN.
677      * @param db the database represented by this MapLN
678      */
optionalModifyDbRoot(DatabaseImpl db)679     public void optionalModifyDbRoot(DatabaseImpl db)
680         throws DatabaseException {
681 
682         if (db.isDeferredWriteMode()) {
683             return;
684         }
685 
686         modifyDbRoot(db);
687     }
688 
689     /**
690      * Write the MapLN to disk.
691      * @param db the database represented by this MapLN
692      */
modifyDbRoot(DatabaseImpl db)693     public void modifyDbRoot(DatabaseImpl db)
694         throws DatabaseException {
695 
696         modifyDbRoot(db, DbLsn.NULL_LSN /*ifBeforeLsn*/, true /*mustExist*/);
697     }
698 
699     /**
700      * Write a MapLN to the log in order to:
701      *  - propagate a root change
702      *  - save per-db utilization information
703      *  - save database config information.
704      * Any MapLN writes must be done through this method, in order to ensure
705      * that the root latch is taken, and updates to the rootIN are properly
706      * safeguarded. See MapN.java for more detail.
707      *
708      * @param db the database whose root is held by this MapLN
709      *
710      * @param ifBeforeLsn if argument is not NULL_LSN, only do the write if
711      * this MapLN's current LSN is before isBeforeLSN.
712      *
713      * @param mustExist if true, throw DatabaseException if the DB does not
714      * exist; if false, silently do nothing.
715      */
modifyDbRoot( DatabaseImpl db, long ifBeforeLsn, boolean mustExist)716     public void modifyDbRoot(
717         DatabaseImpl db,
718         long ifBeforeLsn,
719         boolean mustExist)
720         throws DatabaseException {
721 
722         /*
723          * Do not write LNs in read-only env.  This method is called when
724          * recovery causes a root split. [#21493]
725          */
726         if (envImpl.isReadOnly() && envImpl.isInInit()) {
727             return;
728         }
729 
730         if (db.getId().equals(ID_DB_ID) ||
731             db.getId().equals(NAME_DB_ID)) {
732             envImpl.logMapTreeRoot();
733         } else {
734             DatabaseEntry keyDbt = new DatabaseEntry(db.getId().getBytes());
735 
736             /*
737              * Retry indefinitely in the face of lock timeouts since the
738              * lock on the MapLN is only supposed to be held for short
739              * periods.
740              */
741             while (true) {
742                 Locker idDbLocker = null;
743                 CursorImpl cursor = null;
744                 boolean operationOk = false;
745                 try {
746                     idDbLocker = BasicLocker.createBasicLocker(envImpl);
747                     cursor = new CursorImpl(idDatabase, idDbLocker);
748 
749                     boolean found = cursor.searchExact(keyDbt, LockType.WRITE);
750 
751                     if (!found) {
752                         if (mustExist) {
753                             throw new EnvironmentFailureException(
754                                 envImpl,
755                                 EnvironmentFailureReason.LOG_INTEGRITY,
756                                 "Can't find database ID: " + db.getId());
757                         }
758                         /* Do nothing silently. */
759                         break;
760                     }
761 
762                     /* Check BIN LSN while latched. */
763                     if (ifBeforeLsn == DbLsn.NULL_LSN ||
764                         DbLsn.compareTo(
765                             cursor.getCurrentLsn(), ifBeforeLsn) < 0) {
766 
767                         MapLN mapLN = (MapLN) cursor.getCurrentLN(
768                             true, /*isLatched*/ true/*unlatch*/);
769 
770                         assert mapLN != null; /* Should be locked. */
771 
772                         /* Perform rewrite. */
773                         RewriteMapLN writeMapLN = new RewriteMapLN(cursor);
774                         mapLN.getDatabase().getTree().withRootLatchedExclusive(
775                             writeMapLN);
776 
777                         operationOk = true;
778                     }
779                     break;
780                 } catch (LockConflictException e) {
781                     /* Continue loop and retry. */
782                 } finally {
783                     if (cursor != null) {
784                         cursor.releaseBIN();
785                         cursor.close();
786                     }
787                     if (idDbLocker != null) {
788                         idDbLocker.operationEnd(operationOk);
789                     }
790                 }
791             }
792         }
793     }
794 
795     private static class RewriteMapLN implements WithRootLatched {
796         private final CursorImpl cursor;
797 
RewriteMapLN(CursorImpl cursor)798         RewriteMapLN(CursorImpl cursor) {
799             this.cursor = cursor;
800         }
801 
doWork(@uppressWarningsR) ChildReference root)802         public IN doWork(@SuppressWarnings("unused") ChildReference root)
803             throws DatabaseException {
804 
805             DatabaseEntry dataDbt = new DatabaseEntry(new byte[0]);
806             cursor.updateCurrentRecord(null,  // replaceKey
807                                        dataDbt,
808                                        null,  // foundData
809                                        null,  // returnNewData
810                                        ReplicationContext.NO_REPLICATE,
811                                        null/*opStats*/);
812             return null;
813         }
814     }
815 
816     /**
817      * In other places (e.g., when write locking a record in ReadOnlyTxn) we
818      * allow writes to the naming DB on a replica, since we allow both
819      * replicated and non-replicated DBs and therefore some NameLNs are
820      * replicated and some are not.  Below is the sole check to prevent a
821      * creation, removal, truncation, or configuration update of a replicated
822      * DB on a replica.  It will throw ReplicaWriteException on a replica if
823      * this operation would assign a new VLSN. [#20543]
824      */
checkReplicaWrite(Locker locker, ReplicationContext repContext)825     private void checkReplicaWrite(Locker locker,
826                                    ReplicationContext repContext) {
827         if (repContext != null && repContext.mustGenerateVLSN()) {
828             locker.disallowReplicaWrite();
829         }
830     }
831 
832     /**
833      * Used by lockNameLN to get the rep context, which is needed for calling
834      * checkReplicaWrite.
835      */
836     interface GetRepContext {
get(DatabaseImpl dbImpl)837         ReplicationContext get(DatabaseImpl dbImpl);
838     }
839 
840     /**
841      * Thrown by lockNameLN when an incorrect locker was used via auto-commit.
842      * See Environment.DbNameOperation.  A checked exception is used to ensure
843      * that it is always handled internally and never propagated to the app.
844      */
845     public static class NeedRepLockerException extends Exception {}
846 
847     /**
848      * Helper for database operations. This method positions a cursor
849      * on the NameLN that represents this database and write locks it.
850      *
851      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
852      * level DbTree operation. [#15176]
853      *
854      * @throws IllegalStateException via
855      * Environment.remove/rename/truncateDatabase
856      */
lockNameLN(Locker locker, String databaseName, String action, GetRepContext getRepContext)857     private NameLockResult lockNameLN(Locker locker,
858                                       String databaseName,
859                                       String action,
860                                       GetRepContext getRepContext)
861         throws DatabaseNotFoundException, NeedRepLockerException {
862 
863         /*
864          * We have to return both a cursor on the naming tree and a
865          * reference to the found DatabaseImpl.
866          */
867         NameLockResult result = new NameLockResult();
868 
869         /* Find the existing DatabaseImpl and establish a cursor. */
870         result.dbImpl = getDb(locker, databaseName, null);
871         if (result.dbImpl == null) {
872             throw new DatabaseNotFoundException
873                 ("Attempted to " + action + " non-existent database " +
874                  databaseName);
875         }
876 
877         boolean success = false;
878         try {
879             /* Get effective rep context and check for replica write. */
880             result.repContext = getRepContext.get(result.dbImpl);
881             checkReplicaWrite(locker, result.repContext);
882 
883             /*
884              * Check for an incorrect locker created via auto-commit.  This
885              * check is made after we have the DatabaseImpl and can check
886              * whether it is replicated.  See Environment.DbNameOperation.
887              */
888             if (envImpl.isReplicated() &&
889                 result.dbImpl.isReplicated() &&
890                 locker.getTxnLocker() != null &&
891                 locker.getTxnLocker().isAutoTxn() &&
892                 !locker.isReplicated()) {
893                 throw new NeedRepLockerException();
894             }
895 
896             result.nameCursor = new CursorImpl(nameDatabase, locker);
897 
898             /* Position the cursor at the specified NameLN. */
899             DatabaseEntry key =
900                 new DatabaseEntry(StringUtils.toUTF8(databaseName));
901             /* See [#16210]. */
902             boolean found = result.nameCursor.searchExact(key, LockType.WRITE);
903 
904             if (!found) {
905                 throw new DatabaseNotFoundException(
906                     "Attempted to " + action + " non-existent database " +
907                     databaseName);
908             }
909 
910             /* Call lockAndGetCurrentLN to write lock the nameLN. */
911             result.nameLN = (NameLN) result.nameCursor.getCurrentLN(
912                 true, /*isLatched*/ true/*unlatch*/);
913             assert result.nameLN != null; /* Should be locked. */
914 
915             /*
916              * Check for open handles after we have the write lock and no other
917              * transactions can open a handle.  After obtaining the write lock,
918              * other handles may be open only if (1) we preempted their locks,
919              * or (2) a handle was opened with the same transaction as used for
920              * this operation.  For (1), we mark the handles as preempted to
921              * cause a DatabasePreemptedException the next time they are
922              * accessed.  For (2), we throw IllegalStateException.
923              */
924             if (locker.getImportunate()) {
925                 /* We preempted the lock of all open DB handles. [#17015] */
926                 final String msg =
927                     "Database " + databaseName +
928                     " has been forcibly closed in order to apply a" +
929                     " replicated " + action + " operation.  This Database" +
930                     " and all associated Cursors must be closed.  All" +
931                     " associated Transactions must be aborted.";
932                 for (Database db : result.dbImpl.getReferringHandles()) {
933                     DbInternal.setPreempted(db, databaseName, msg);
934                 }
935             } else {
936                 /* Disallow open handles for the same transaction. */
937                 int handleCount = result.dbImpl.getReferringHandleCount();
938                 if (handleCount > 0) {
939                     throw new IllegalStateException
940                         ("Can't " + action + " database " + databaseName +
941                          ", " + handleCount + " open Database handles exist");
942                 }
943             }
944             success = true;
945         } finally {
946             if (!success) {
947                 releaseDb(result.dbImpl);
948                 if (result.nameCursor != null) {
949                     result.nameCursor.releaseBIN();
950                     result.nameCursor.close();
951                 }
952             }
953         }
954 
955         return result;
956     }
957 
958     private static class NameLockResult {
959         CursorImpl nameCursor;
960         DatabaseImpl dbImpl;
961         NameLN nameLN;
962         ReplicationContext repContext;
963     }
964 
965     /**
966      * Update the NameLN for the DatabaseImpl when the DatabaseConfig changes.
967      *
968      * JE MapLN actually includes the DatabaseImpl information, but it is not
969      * transactional, so the DatabaseConfig information is stored in
970      * NameLNLogEntry and replicated.
971      *
972      * So when there is a DatabaseConfig changes, we'll update the NameLN for
973      * the database, which will log a new NameLNLogEntry so that the rep stream
974      * will transfer it to the replicas and it will be replayed.
975      *
976      * @param locker the locker used to update the NameLN
977      * @param dbName the name of the database whose corresponding NameLN needs
978      * to be updated
979      * @param repContext information used while replaying a NameLNLogEntry on
980      * the replicas, it's null on master
981      */
updateNameLN(Locker locker, String dbName, final DbOpReplicationContext repContext)982     public void updateNameLN(Locker locker,
983                              String dbName,
984                              final DbOpReplicationContext repContext)
985         throws LockConflictException {
986 
987         assert dbName != null;
988 
989         /* Find and write lock on the NameLN. */
990         final NameLockResult result;
991         try {
992             result = lockNameLN
993                 (locker, dbName, "updateConfig", new GetRepContext() {
994 
995                 public ReplicationContext get(DatabaseImpl dbImpl) {
996                     return (repContext != null) ?
997                         repContext :
998                         dbImpl.getOperationRepContext(UPDATE_CONFIG, null);
999                 }
1000             });
1001         } catch (NeedRepLockerException e) {
1002             /* Should never happen; db is known when locker is created. */
1003             throw EnvironmentFailureException.unexpectedException(envImpl, e);
1004         }
1005 
1006         final CursorImpl nameCursor = result.nameCursor;
1007         final DatabaseImpl dbImpl = result.dbImpl;
1008         final ReplicationContext useRepContext = result.repContext;
1009         try {
1010 
1011             /* Log a NameLN. */
1012             DatabaseEntry dataDbt = new DatabaseEntry(new byte[0]);
1013             nameCursor.updateCurrentRecord(null, // replaceKey
1014                                            dataDbt,
1015                                            null, // foundData
1016                                            null, // returnNewData
1017                                            useRepContext,
1018                                            null/*opStats*/);
1019         } finally {
1020             releaseDb(dbImpl);
1021             nameCursor.releaseBIN();
1022             nameCursor.close();
1023         }
1024     }
1025 
1026     /**
1027      * Rename the database by creating a new NameLN and deleting the old one.
1028      *
1029      * @return the database handle of the impacted database
1030      *
1031      * @throws DatabaseNotFoundException if the operation fails because the
1032      * given DB name is not found.
1033      */
doRenameDb(Locker locker, String databaseName, String newName, NameLN replicatedLN, final DbOpReplicationContext repContext)1034     private DatabaseImpl doRenameDb(Locker locker,
1035                                     String databaseName,
1036                                     String newName,
1037                                     NameLN replicatedLN,
1038                                     final DbOpReplicationContext repContext)
1039         throws DatabaseNotFoundException, NeedRepLockerException {
1040 
1041         final NameLockResult result = lockNameLN
1042             (locker, databaseName, "rename", new GetRepContext() {
1043 
1044             public ReplicationContext get(DatabaseImpl dbImpl) {
1045                 return (repContext != null) ?
1046                     repContext :
1047                     dbImpl.getOperationRepContext(RENAME);
1048             }
1049         });
1050 
1051         final CursorImpl nameCursor = result.nameCursor;
1052         final DatabaseImpl dbImpl = result.dbImpl;
1053         final ReplicationContext useRepContext = result.repContext;
1054         try {
1055 
1056             /*
1057              * Rename simply deletes the one entry in the naming tree and
1058              * replaces it with a new one. Remove the oldName->dbId entry and
1059              * insert newName->dbId.
1060              */
1061             nameCursor.deleteCurrentRecord(ReplicationContext.NO_REPLICATE);
1062             final NameLN useLN =
1063                 (replicatedLN != null) ?
1064                  replicatedLN :
1065                  new NameLN(dbImpl.getId());
1066             /*
1067              * Reset cursor to remove old BIN before calling insertRecord.
1068              * [#16280]
1069              */
1070             nameCursor.reset();
1071 
1072             nameCursor.insertRecord(
1073                 StringUtils.toUTF8(newName), useLN,
1074                 false /*blindInsertion*/, useRepContext);
1075 
1076             dbImpl.setDebugDatabaseName(newName);
1077             return dbImpl;
1078         } finally {
1079             releaseDb(dbImpl);
1080             nameCursor.close();
1081         }
1082     }
1083 
1084     /**
1085      * Stand alone and Master invocations.
1086      *
1087      * @see #doRenameDb
1088      */
dbRename(Locker locker, String databaseName, String newName)1089     public DatabaseImpl dbRename(Locker locker,
1090                                  String databaseName,
1091                                  String newName)
1092         throws DatabaseNotFoundException, NeedRepLockerException {
1093 
1094         return doRenameDb(locker, databaseName, newName, null, null);
1095     }
1096 
1097     /**
1098      * Replica invocations.
1099      *
1100      * @see #doRenameDb
1101      */
renameReplicaDb(Locker locker, String databaseName, String newName, NameLN replicatedLN, DbOpReplicationContext repContext)1102     public DatabaseImpl renameReplicaDb(Locker locker,
1103                                         String databaseName,
1104                                         String newName,
1105                                         NameLN replicatedLN,
1106                                         DbOpReplicationContext repContext)
1107         throws DatabaseNotFoundException {
1108 
1109         try {
1110             return doRenameDb(locker, databaseName, newName, replicatedLN,
1111                               repContext);
1112         } catch (NeedRepLockerException e) {
1113             /* Should never happen; db is known when locker is created. */
1114             throw EnvironmentFailureException.unexpectedException(envImpl, e);
1115         }
1116     }
1117 
1118     /**
1119      * Remove the database by deleting the nameLN.
1120      *
1121      * @return a handle to the renamed database
1122      *
1123      * @throws DatabaseNotFoundException if the operation fails because the
1124      * given DB name is not found, or the non-null checkId argument does not
1125      * match the database identified by databaseName.
1126      */
doRemoveDb(Locker locker, String databaseName, DatabaseId checkId, final DbOpReplicationContext repContext)1127     private DatabaseImpl doRemoveDb(Locker locker,
1128                                     String databaseName,
1129                                     DatabaseId checkId,
1130                                     final DbOpReplicationContext repContext)
1131         throws DatabaseNotFoundException, NeedRepLockerException {
1132 
1133         CursorImpl nameCursor = null;
1134 
1135         final NameLockResult result = lockNameLN
1136             (locker, databaseName, "remove", new GetRepContext() {
1137 
1138             public ReplicationContext get(DatabaseImpl dbImpl) {
1139                 return (repContext != null) ?
1140                     repContext :
1141                     dbImpl.getOperationRepContext(REMOVE);
1142             }
1143         });
1144 
1145         final ReplicationContext useRepContext = result.repContext;
1146         try {
1147             nameCursor = result.nameCursor;
1148             if (checkId != null && !checkId.equals(result.nameLN.getId())) {
1149                 throw new DatabaseNotFoundException
1150                     ("ID mismatch: " + databaseName);
1151             }
1152 
1153             /*
1154              * Delete the NameLN. There's no need to mark any Database
1155              * handle invalid, because the handle must be closed when we
1156              * take action and any further use of the handle will re-look
1157              * up the database.
1158              */
1159             nameCursor.deleteCurrentRecord(useRepContext);
1160 
1161             /*
1162              * Schedule database for final deletion during commit. This
1163              * should be the last action taken, since this will take
1164              * effect immediately for non-txnal lockers.
1165              *
1166              * Do not call releaseDb here on result.dbImpl, since that is
1167              * taken care of by markDeleteAtTxnEnd.
1168              */
1169             locker.markDeleteAtTxnEnd(result.dbImpl, true);
1170             return result.dbImpl;
1171         } finally {
1172             if (nameCursor != null) {
1173                 nameCursor.close();
1174             }
1175         }
1176     }
1177 
1178     /**
1179      * Stand alone and Master invocations.
1180      *
1181      * @see #doRemoveDb
1182      */
dbRemove(Locker locker, String databaseName, DatabaseId checkId)1183     public DatabaseImpl dbRemove(Locker locker,
1184                          String databaseName,
1185                          DatabaseId checkId)
1186         throws DatabaseNotFoundException, NeedRepLockerException {
1187 
1188         return doRemoveDb(locker, databaseName, checkId, null);
1189     }
1190 
1191     /**
1192      * Replica invocations.
1193      *
1194      * @see #doRemoveDb
1195      */
removeReplicaDb(Locker locker, String databaseName, DatabaseId checkId, DbOpReplicationContext repContext)1196     public void removeReplicaDb(Locker locker,
1197                                 String databaseName,
1198                                 DatabaseId checkId,
1199                                 DbOpReplicationContext repContext)
1200         throws DatabaseNotFoundException {
1201 
1202         try {
1203             doRemoveDb(locker, databaseName, checkId, repContext);
1204         } catch (NeedRepLockerException e) {
1205             /* Should never happen; db is known when locker is created. */
1206             throw EnvironmentFailureException.unexpectedException(envImpl, e);
1207         }
1208     }
1209 
1210     /**
1211      * To truncate, remove the database named by databaseName and
1212      * create a new database in its place.
1213      *
1214      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
1215      * level DbTree operation. [#15176]
1216      *
1217      * @param returnCount if true, must return the count of records in the
1218      * database, which can be an expensive option.
1219      *
1220      * @return the record count, oldDb and newDb packaged in a TruncateDbResult
1221      *
1222      * @throws DatabaseNotFoundException if the operation fails because the
1223      * given DB name is not found.
1224      */
1225     public TruncateDbResult
doTruncateDb(Locker locker, String databaseName, boolean returnCount, NameLN replicatedLN, final DbOpReplicationContext repContext)1226         doTruncateDb(Locker locker,
1227                      String databaseName,
1228                      boolean returnCount,
1229                      NameLN replicatedLN,
1230                      final DbOpReplicationContext repContext)
1231         throws DatabaseNotFoundException, NeedRepLockerException {
1232 
1233         assert((replicatedLN != null) ? (repContext != null) : true);
1234 
1235         final NameLockResult result = lockNameLN
1236             (locker, databaseName, "truncate", new GetRepContext() {
1237 
1238             public ReplicationContext get(DatabaseImpl dbImpl) {
1239                 return (repContext != null) ?
1240                     repContext :
1241                     dbImpl.getOperationRepContext(TRUNCATE, dbImpl.getId());
1242             }
1243         });
1244 
1245         final CursorImpl nameCursor = result.nameCursor;
1246         final ReplicationContext useRepContext = result.repContext;
1247         try {
1248             /*
1249              * Make a new database with an empty tree. Make the nameLN refer to
1250              * the id of the new database. If this database is replicated, the
1251              * new one should also be replicated, and vice versa.
1252              */
1253             DatabaseImpl oldDb = result.dbImpl;
1254             final DatabaseId newId =
1255                 (replicatedLN != null) ?
1256                  replicatedLN.getId() :
1257                  new DatabaseId(isReplicatedId(oldDb.getId().getId()) ?
1258                                 getNextReplicatedDbId() :
1259                                 getNextLocalDbId());
1260 
1261             DatabaseImpl newDb = oldDb.cloneDatabase();
1262             newDb.incrementUseCount();
1263             newDb.setId(newId);
1264             newDb.setTree(new Tree(newDb));
1265 
1266             /*
1267              * Insert the new MapLN into the id tree. Do not use a transaction
1268              * on the id database, because we can not hold long term locks on
1269              * the mapLN.
1270              */
1271             Locker idDbLocker = null;
1272             CursorImpl idCursor = null;
1273             boolean operationOk = false;
1274             try {
1275                 idDbLocker = BasicLocker.createBasicLocker(envImpl);
1276                 idCursor = new CursorImpl(idDatabase, idDbLocker);
1277 
1278                 idCursor.insertRecord(
1279                     newId.getBytes() /*key*/, new MapLN(newDb),
1280                     false /*blindInsertion*/, ReplicationContext.NO_REPLICATE);
1281 
1282                 operationOk = true;
1283             } finally {
1284                 if (idCursor != null) {
1285                     idCursor.close();
1286                 }
1287 
1288                 if (idDbLocker != null) {
1289                     idDbLocker.operationEnd(operationOk);
1290                 }
1291             }
1292             result.nameLN.setId(newDb.getId());
1293 
1294             /* If required, count the number of records in the database. */
1295             final long recordCount = (returnCount ? oldDb.count(0) : 0);
1296 
1297             /* log the nameLN. */
1298             DatabaseEntry dataDbt = new DatabaseEntry(new byte[0]);
1299 
1300             nameCursor.updateCurrentRecord(null,  // replaceKey
1301                                            dataDbt,
1302                                            null,  // foundData
1303                                            null,  // returnNewData
1304                                            useRepContext,
1305                                            null/*opStats*/);
1306             /*
1307              * Marking the lockers should be the last action, since it
1308              * takes effect immediately for non-txnal lockers.
1309              *
1310              * Do not call releaseDb here on oldDb or newDb, since that is
1311              * taken care of by markDeleteAtTxnEnd.
1312              */
1313 
1314             /* Schedule old database for deletion if txn commits. */
1315             locker.markDeleteAtTxnEnd(oldDb, true);
1316 
1317             /* Schedule new database for deletion if txn aborts. */
1318             locker.markDeleteAtTxnEnd(newDb, false);
1319 
1320             return new TruncateDbResult(oldDb, newDb, recordCount);
1321         } finally {
1322             nameCursor.releaseBIN();
1323             nameCursor.close();
1324         }
1325     }
1326 
1327     /*
1328      * Effectively a struct used to return multiple values of interest.
1329      */
1330     public static class TruncateDbResult {
1331         public final DatabaseImpl oldDB;
1332         public final DatabaseImpl newDb;
1333         public final long recordCount;
1334 
TruncateDbResult(DatabaseImpl oldDB, DatabaseImpl newDb, long recordCount)1335         public TruncateDbResult(DatabaseImpl oldDB,
1336                                 DatabaseImpl newDb,
1337                                 long recordCount) {
1338             this.oldDB = oldDB;
1339             this.newDb = newDb;
1340             this.recordCount = recordCount;
1341         }
1342     }
1343 
1344     /**
1345      * @see #doTruncateDb
1346      */
truncate(Locker locker, String databaseName, boolean returnCount)1347     public TruncateDbResult truncate(Locker locker,
1348                                      String databaseName,
1349                                      boolean returnCount)
1350         throws DatabaseNotFoundException, NeedRepLockerException {
1351 
1352         return doTruncateDb(locker, databaseName, returnCount, null, null);
1353     }
1354 
1355     /**
1356      * @see #doTruncateDb
1357      */
truncateReplicaDb(Locker locker, String databaseName, boolean returnCount, NameLN replicatedLN, DbOpReplicationContext repContext)1358     public TruncateDbResult truncateReplicaDb(Locker locker,
1359                                               String databaseName,
1360                                               boolean returnCount,
1361                                               NameLN replicatedLN,
1362                                               DbOpReplicationContext repContext)
1363         throws DatabaseNotFoundException {
1364 
1365         try {
1366             return doTruncateDb(locker, databaseName, returnCount,
1367                                 replicatedLN, repContext);
1368         } catch (NeedRepLockerException e) {
1369             /* Should never happen; db is known when locker is created. */
1370             throw EnvironmentFailureException.unexpectedException(envImpl, e);
1371         }
1372     }
1373 
1374     /*
1375      * Remove the mapLN that refers to this database.
1376      *
1377      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
1378      * level DbTree operation. [#15176]
1379      */
deleteMapLN(DatabaseId id)1380     void deleteMapLN(DatabaseId id)
1381         throws DatabaseException {
1382 
1383         /*
1384          * Retry indefinitely in the face of lock timeouts since the lock on
1385          * the MapLN is only supposed to be held for short periods.
1386          */
1387         boolean done = false;
1388         while (!done) {
1389             Locker idDbLocker = null;
1390             CursorImpl idCursor = null;
1391             boolean operationOk = false;
1392             try {
1393                 idDbLocker = BasicLocker.createBasicLocker(envImpl);
1394                 idCursor = new CursorImpl(idDatabase, idDbLocker);
1395 
1396                 boolean found = idCursor.searchExact(
1397                     new DatabaseEntry(id.getBytes()), LockType.WRITE);
1398 
1399                 if (found) {
1400 
1401                     /*
1402                      * If the database is in use by an internal JE operation
1403                      * (checkpointing, cleaning, etc), release the lock (done
1404                      * in the finally block) and retry.  [#15805]
1405                      */
1406                     MapLN mapLN = (MapLN) idCursor.getCurrentLN(
1407                         true, /*isLatched*/ true/*unlatch*/);
1408 
1409                     assert mapLN != null;
1410                     DatabaseImpl dbImpl = mapLN.getDatabase();
1411 
1412                     if (!dbImpl.isInUseDuringDbRemove()) {
1413                         idCursor.deleteCurrentRecord(
1414                             ReplicationContext.NO_REPLICATE);
1415                         done = true;
1416                     }
1417                 } else {
1418                     /* MapLN does not exist. */
1419                     done = true;
1420                 }
1421                 operationOk = true;
1422             } catch (LockConflictException e) {
1423                 /* Continue loop and retry. */
1424             } finally {
1425                 if (idCursor != null) {
1426                     /* searchExact leaves BIN latched. */
1427                     idCursor.releaseBIN();
1428                     idCursor.close();
1429                 }
1430                 if (idDbLocker != null) {
1431                     idDbLocker.operationEnd(operationOk);
1432                 }
1433             }
1434         }
1435     }
1436 
1437     /**
1438      * Get a database object given a database name.  Increments the use count
1439      * of the given DB to prevent it from being evicted.  releaseDb should be
1440      * called when the returned object is no longer used, to allow it to be
1441      * evicted.  See DatabaseImpl.isInUse.
1442      * [#13415]
1443      *
1444      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
1445      * level DbTree operation. [#15176]
1446      *
1447      * @param nameLocker is used to access the NameLN. As always, a NullTxn
1448      *  is used to access the MapLN.
1449      * @param databaseName target database
1450      * @return null if database doesn't exist
1451      */
getDb(Locker nameLocker, String databaseName, HandleLocker handleLocker)1452     public DatabaseImpl getDb(Locker nameLocker,
1453                               String databaseName,
1454                               HandleLocker handleLocker)
1455         throws DatabaseException {
1456 
1457         /* Use count is not incremented for idDatabase and nameDatabase. */
1458         if (databaseName.equals(DbType.ID.getInternalName())) {
1459             return idDatabase;
1460         } else if (databaseName.equals(DbType.NAME.getInternalName())) {
1461             return nameDatabase;
1462         }
1463 
1464         /*
1465          * Search the nameDatabase tree for the NameLn for this name.
1466          */
1467         CursorImpl nameCursor = null;
1468         DatabaseId id = null;
1469         try {
1470             nameCursor = new CursorImpl(nameDatabase, nameLocker);
1471             DatabaseEntry keyDbt =
1472                 new DatabaseEntry(StringUtils.toUTF8(databaseName));
1473 
1474             boolean found = nameCursor.searchExact(keyDbt, LockType.READ);
1475 
1476             if (found) {
1477                 NameLN nameLN = (NameLN) nameCursor.getCurrentLN(
1478                     true, /*isLatched*/ true/*unlatch*/);
1479                 assert nameLN != null; /* Should be locked. */
1480                 id = nameLN.getId();
1481 
1482                 /* Record handle lock. */
1483                 if (handleLocker != null) {
1484                     acquireHandleLock(nameCursor, handleLocker);
1485                 }
1486             }
1487         } finally {
1488             if (nameCursor != null) {
1489                 nameCursor.releaseBIN();
1490                 nameCursor.close();
1491             }
1492         }
1493 
1494         /*
1495          * Now search the id tree.
1496          */
1497         if (id == null) {
1498             return null;
1499         }
1500         return getDb(id, -1, databaseName);
1501     }
1502 
1503     /**
1504      * Get a database object based on an id only.  Used by recovery, cleaning
1505      * and other clients who have an id in hand, and don't have a resident
1506      * node, to find the matching database for a given log entry.
1507      */
getDb(DatabaseId dbId)1508     public DatabaseImpl getDb(DatabaseId dbId)
1509         throws DatabaseException {
1510 
1511         return getDb(dbId, -1);
1512     }
1513 
1514     /**
1515      * Get a database object based on an id only. Specify the lock timeout to
1516      * use, or -1 to use the default timeout.  A timeout should normally only
1517      * be specified by daemons with their own timeout configuration.  public
1518      * for unit tests.
1519      */
getDb(DatabaseId dbId, long lockTimeout)1520     public DatabaseImpl getDb(DatabaseId dbId, long lockTimeout)
1521         throws DatabaseException {
1522 
1523         return getDb(dbId, lockTimeout, (String) null);
1524     }
1525 
1526     /**
1527      * Get a database object based on an id only, caching the id-db mapping in
1528      * the given map.
1529      */
getDb(DatabaseId dbId, long lockTimeout, Map<DatabaseId, DatabaseImpl> dbCache)1530     public DatabaseImpl getDb(DatabaseId dbId,
1531                               long lockTimeout,
1532                               Map<DatabaseId, DatabaseImpl> dbCache)
1533         throws DatabaseException {
1534 
1535         if (dbCache.containsKey(dbId)) {
1536             return dbCache.get(dbId);
1537         }
1538         DatabaseImpl db = getDb(dbId, lockTimeout, (String) null);
1539         dbCache.put(dbId, db);
1540         return db;
1541     }
1542 
1543     /**
1544      * Get a database object based on an id only. Specify the lock timeout to
1545      * use, or -1 to use the default timeout.  A timeout should normally only
1546      * be specified by daemons with their own timeout configuration.  public
1547      * for unit tests.
1548      *
1549      * Increments the use count of the given DB to prevent it from being
1550      * evicted.  releaseDb should be called when the returned object is no
1551      * longer used, to allow it to be evicted.  See DatabaseImpl.isInUse.
1552      * [#13415]
1553      *
1554      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
1555      * level DbTree operation. [#15176]
1556      */
getDb(DatabaseId dbId, long lockTimeout, String dbNameIfAvailable)1557     public DatabaseImpl getDb(DatabaseId dbId,
1558                               long lockTimeout,
1559                               String dbNameIfAvailable)
1560         throws DatabaseException {
1561 
1562         if (dbId.equals(idDatabase.getId())) {
1563             /* We're looking for the id database itself. */
1564             return idDatabase;
1565         } else if (dbId.equals(nameDatabase.getId())) {
1566             /* We're looking for the name database itself. */
1567             return nameDatabase;
1568         } else {
1569             /* Scan the tree for this db. */
1570             DatabaseImpl foundDbImpl = null;
1571 
1572             /*
1573              * Retry indefinitely in the face of lock timeouts.  Deadlocks may
1574              * be due to conflicts with modifyDbRoot.
1575              */
1576             while (true) {
1577                 Locker locker = null;
1578                 CursorImpl idCursor = null;
1579                 boolean operationOk = false;
1580                 try {
1581                     locker = BasicLocker.createBasicLocker(envImpl);
1582                     if (lockTimeout != -1) {
1583                         locker.setLockTimeout(lockTimeout);
1584                     }
1585                     idCursor = new CursorImpl(idDatabase, locker);
1586                     DatabaseEntry keyDbt = new DatabaseEntry(dbId.getBytes());
1587 
1588                     boolean found = idCursor.searchExact(keyDbt, LockType.READ);
1589 
1590                     if (found) {
1591                         MapLN mapLN = (MapLN) idCursor.getCurrentLN(
1592                             true, /*isLatched*/ true /*unlatch*/);
1593                         assert mapLN != null; /* Should be locked. */
1594                         foundDbImpl =  mapLN.getDatabase();
1595                         /* Increment DB use count with lock held. */
1596                         foundDbImpl.incrementUseCount();
1597                     }
1598                     operationOk = true;
1599                     break;
1600                 } catch (LockConflictException e) {
1601                     /* Continue loop and retry. */
1602                 } finally {
1603                     if (idCursor != null) {
1604                         idCursor.releaseBIN();
1605                         idCursor.close();
1606                     }
1607                     if (locker != null) {
1608                         locker.operationEnd(operationOk);
1609                     }
1610                 }
1611             }
1612 
1613             /*
1614              * Set the debugging name in the databaseImpl.
1615              */
1616             setDebugNameForDatabaseImpl(foundDbImpl, dbNameIfAvailable);
1617 
1618             return foundDbImpl;
1619         }
1620     }
1621 
1622     /**
1623      * Decrements the use count of the given DB, allowing it to be evicted if
1624      * the use count reaches zero.  Must be called to release a DatabaseImpl
1625      * that was returned by a method in this class.  See DatabaseImpl.isInUse.
1626      * [#13415]
1627      */
releaseDb(DatabaseImpl db)1628     public void releaseDb(DatabaseImpl db) {
1629         /* Use count is not incremented for idDatabase and nameDatabase. */
1630         if (db != null &&
1631             db != idDatabase &&
1632             db != nameDatabase) {
1633             db.decrementUseCount();
1634         }
1635     }
1636 
1637     /**
1638      * Calls releaseDb for all DBs in the given map of DatabaseId to
1639      * DatabaseImpl.  See getDb(DatabaseId, long, Map). [#13415]
1640      */
releaseDbs(Map<DatabaseId,DatabaseImpl> dbCache)1641     public void releaseDbs(Map<DatabaseId,DatabaseImpl> dbCache) {
1642         if (dbCache != null) {
1643             for (DatabaseImpl databaseImpl : dbCache.values()) {
1644                 releaseDb(databaseImpl);
1645             }
1646         }
1647     }
1648 
1649     /*
1650      * We need to cache a database name in the dbImpl for later use in error
1651      * messages, when it may be unsafe to walk the mapping tree.  Finding a
1652      * name by id is slow, so minimize the number of times we must set the
1653      * debug name.  The debug name will only be uninitialized when an existing
1654      * databaseImpl is faulted in.
1655      */
setDebugNameForDatabaseImpl(DatabaseImpl dbImpl, String dbName)1656     private void setDebugNameForDatabaseImpl(DatabaseImpl dbImpl,
1657                                              String dbName)
1658         throws DatabaseException {
1659 
1660         if (dbImpl != null) {
1661             if (dbName != null) {
1662                 /* If a name was provided, use that. */
1663                 dbImpl.setDebugDatabaseName(dbName);
1664             } else {
1665 
1666                 /*
1667                  * Only worry about searching for a name if the name is
1668                  * uninitialized.  Only search after recovery had finished
1669                  * setting up the tree.
1670                  *
1671                  * Only do name lookup if it will be fairly fast.  Debugging
1672                  * info isn't important enough to cause long lookups during log
1673                  * cleaning, for example.  [#21015]
1674                  */
1675                 if (envImpl.isValid() &&
1676                     !dbImpl.isDebugNameAvailable() &&
1677                     getFastNameLookup()) {
1678                     dbImpl.setDebugDatabaseName(getDbName(dbImpl.getId()));
1679                 }
1680             }
1681         }
1682     }
1683 
1684     /**
1685      * Rebuild the IN list after recovery.
1686      */
rebuildINListMapDb()1687     public void rebuildINListMapDb()
1688         throws DatabaseException {
1689 
1690         idDatabase.getTree().rebuildINList();
1691     }
1692 
1693     /*
1694      * Verification, must be run while system is quiescent.
1695      */
verify(final VerifyConfig config, @SuppressWarnings(R) PrintStream out)1696     public boolean verify(final VerifyConfig config,
1697                           @SuppressWarnings("unused") PrintStream out)
1698         throws DatabaseException {
1699 
1700         boolean ret = true;
1701         try {
1702             /* For now, verify all databases. */
1703             boolean ok = idDatabase.verify(config,
1704                                            idDatabase.getEmptyStats());
1705             if (!ok) {
1706                 ret = false;
1707             }
1708 
1709             ok = nameDatabase.verify(config,
1710                                      nameDatabase.getEmptyStats());
1711             if (!ok) {
1712                 ret = false;
1713             }
1714         } catch (DatabaseException DE) {
1715             ret = false;
1716         }
1717 
1718         synchronized (envImpl.getINCompressor()) {
1719 
1720             /*
1721              * Get a cursor on the id tree. Use objects at the dbi layer rather
1722              * than at the public api, in order to retrieve objects rather than
1723              * Dbts. Note that we don't do cursor cloning here, so any failures
1724              * from each db verify invalidate the cursor.  Use dirty read
1725              * (LockMode.NONE) because locks on the MapLN should never be held
1726              * for long, as that will cause deadlocks with splits and
1727              * checkpointing.
1728              */
1729             final LockType lockType = LockType.NONE;
1730             class Traversal implements CursorImpl.WithCursor {
1731                 boolean allOk = true;
1732 
1733                 public boolean withCursor(CursorImpl cursor,
1734                                           @SuppressWarnings("unused")
1735                                           DatabaseEntry key,
1736                                           @SuppressWarnings("unused")
1737                                           DatabaseEntry data)
1738                     throws DatabaseException {
1739 
1740                     MapLN mapLN = (MapLN) cursor.lockAndGetCurrentLN(lockType);
1741                     if (mapLN != null && !mapLN.isDeleted()) {
1742                         DatabaseImpl dbImpl = mapLN.getDatabase();
1743                         boolean ok = dbImpl.verify(config,
1744                                                    dbImpl.getEmptyStats());
1745                         if (!ok) {
1746                             allOk = false;
1747                         }
1748                     }
1749                     return true;
1750                 }
1751             }
1752             Traversal traversal = new Traversal();
1753             CursorImpl.traverseDbWithCursor
1754                 (idDatabase, lockType, true /*allowEviction*/, traversal);
1755             if (!traversal.allOk) {
1756                 ret = false;
1757             }
1758         }
1759 
1760         return ret;
1761     }
1762 
1763     /**
1764      * Returns true if the naming DB has a fairly small number of names, and
1765      * therefore execution of getDbName will be fairly fast.
1766      */
getFastNameLookup()1767     private boolean getFastNameLookup() {
1768         return nameDatabase.getTree().getMaxLNs() <= FAST_NAME_LOOKUP_MAX_LNS;
1769     }
1770 
1771     /**
1772      * Return the database name for a given db. Slow, must traverse. Called by
1773      * Database.getName.
1774      *
1775      * Do not evict (do not call CursorImpl.setAllowEviction(true)) during low
1776      * level DbTree operation. [#15176]
1777      */
getDbName(final DatabaseId id)1778     public String getDbName(final DatabaseId id)
1779         throws DatabaseException {
1780 
1781         if (id.equals(ID_DB_ID)) {
1782             return DbType.ID.getInternalName();
1783         } else if (id.equals(NAME_DB_ID)) {
1784             return DbType.NAME.getInternalName();
1785         }
1786 
1787         class Traversal implements CursorImpl.WithCursor {
1788             String name = null;
1789 
1790             public boolean withCursor(CursorImpl cursor,
1791                                       DatabaseEntry key,
1792                                       @SuppressWarnings("unused")
1793                                       DatabaseEntry data)
1794                 throws DatabaseException {
1795 
1796                 NameLN nameLN = (NameLN) cursor.lockAndGetCurrentLN(
1797                     LockType.NONE);
1798 
1799                 if (nameLN != null && nameLN.getId().equals(id)) {
1800                     name = StringUtils.fromUTF8(key.getData());
1801                     return false;
1802                 }
1803                 return true;
1804             }
1805         }
1806 
1807         Traversal traversal = new Traversal();
1808 
1809         CursorImpl.traverseDbWithCursor(
1810             nameDatabase, LockType.NONE, false /*allowEviction*/, traversal);
1811 
1812         return traversal.name;
1813     }
1814 
1815     /**
1816      * @return a map of database ids to database names (Strings).
1817      */
getDbNamesAndIds()1818     public Map<DatabaseId,String> getDbNamesAndIds()
1819         throws DatabaseException {
1820 
1821         final Map<DatabaseId,String> nameMap =
1822             new HashMap<DatabaseId,String>();
1823 
1824         class Traversal implements CursorImpl.WithCursor {
1825             public boolean withCursor(CursorImpl cursor,
1826                                       DatabaseEntry key,
1827                                       @SuppressWarnings("unused")
1828                                       DatabaseEntry data)
1829                 throws DatabaseException {
1830 
1831                 NameLN nameLN = (NameLN) cursor.lockAndGetCurrentLN(
1832                     LockType.NONE);
1833                 DatabaseId id = nameLN.getId();
1834                 nameMap.put(id, StringUtils.fromUTF8(key.getData()));
1835                 return true;
1836             }
1837         }
1838         Traversal traversal = new Traversal();
1839         CursorImpl.traverseDbWithCursor
1840             (nameDatabase, LockType.NONE, false /*allowEviction*/, traversal);
1841         return nameMap;
1842     }
1843 
1844     /**
1845      * @return a list of database names held in the environment, as strings.
1846      */
getDbNames()1847     public List<String> getDbNames()
1848         throws DatabaseException {
1849 
1850         final List<String> nameList = new ArrayList<String>();
1851 
1852         CursorImpl.traverseDbWithCursor(nameDatabase,
1853                                         LockType.NONE,
1854                                         true /*allowEviction*/,
1855                                         new CursorImpl.WithCursor() {
1856             public boolean withCursor(@SuppressWarnings("unused")
1857                                       CursorImpl cursor,
1858                                       DatabaseEntry key,
1859                                       @SuppressWarnings("unused")
1860                                       DatabaseEntry data)
1861                 throws DatabaseException {
1862 
1863                 String name = StringUtils.fromUTF8(key.getData());
1864                 if (!isReservedDbName(name)) {
1865                     nameList.add(name);
1866                 }
1867                 return true;
1868             }
1869         });
1870 
1871         return nameList;
1872     }
1873 
1874     /**
1875      * Return a list of the names of internally used databases that
1876      * don't get looked up through the naming tree.
1877      */
getInternalNoLookupDbNames()1878     public List<String> getInternalNoLookupDbNames() {
1879         List<String> names = new ArrayList<String>();
1880         names.add(DbType.ID.getInternalName());
1881         names.add(DbType.NAME.getInternalName());
1882         return names;
1883     }
1884 
1885     /**
1886      * Return a list of the names of internally used databases for all
1887      * environment types.
1888      */
getInternalNoRepDbNames()1889     public List<String> getInternalNoRepDbNames() {
1890         List<String> names = new ArrayList<String>();
1891         names.add(DbType.UTILIZATION.getInternalName());
1892         return names;
1893     }
1894 
1895     /**
1896      * Return a list of the names of internally used databases for replication
1897      * only.
1898      */
getInternalRepDbNames()1899     public List<String> getInternalRepDbNames() {
1900         List<String> names = new ArrayList<String>();
1901         names.add(DbType.REP_GROUP.getInternalName());
1902         names.add(DbType.VLSN_MAP.getInternalName());
1903         return names;
1904     }
1905 
1906     /**
1907      * Returns true if the name is a reserved JE database name.
1908      */
isReservedDbName(String name)1909     public static boolean isReservedDbName(String name) {
1910         return typeForDbName(name).isInternal();
1911     }
1912 
1913     /**
1914      * @return the higest level node in the environment.
1915      */
getHighestLevel()1916     public int getHighestLevel()
1917         throws DatabaseException {
1918 
1919         /* The highest level in the map side */
1920         int idHighLevel = getHighestLevel(idDatabase);
1921 
1922         /* The highest level in the name side */
1923         int nameHighLevel = getHighestLevel(nameDatabase);
1924 
1925         return (nameHighLevel > idHighLevel) ? nameHighLevel : idHighLevel;
1926     }
1927 
1928     /**
1929      * @return the higest level node for this database.
1930      */
getHighestLevel(DatabaseImpl dbImpl)1931     public int getHighestLevel(DatabaseImpl dbImpl)
1932         throws DatabaseException {
1933 
1934         /* The highest level in the map side */
1935         RootLevel getLevel = new RootLevel(dbImpl);
1936         dbImpl.getTree().withRootLatchedShared(getLevel);
1937         return getLevel.getRootLevel();
1938     }
1939 
isReplicated()1940     boolean isReplicated() {
1941         return (flags & REPLICATED_BIT) != 0;
1942     }
1943 
setIsReplicated()1944     void setIsReplicated() {
1945         flags |= REPLICATED_BIT;
1946     }
1947 
1948     /*
1949      * Return true if this environment is converted from standalone to
1950      * replicated.
1951      */
isRepConverted()1952     boolean isRepConverted() {
1953         return (flags & REP_CONVERTED_BIT) != 0;
1954     }
1955 
setIsRepConverted()1956     void setIsRepConverted() {
1957         flags |= REP_CONVERTED_BIT;
1958     }
1959 
getIdDatabaseImpl()1960     public DatabaseImpl getIdDatabaseImpl() {
1961         return idDatabase;
1962     }
1963 
getDupsConverted()1964     boolean getDupsConverted() {
1965         return (flags & DUPS_CONVERTED_BIT) != 0;
1966     }
1967 
setDupsConverted()1968     void setDupsConverted() {
1969         flags |= DUPS_CONVERTED_BIT;
1970     }
1971 
getPreserveVLSN()1972     private boolean getPreserveVLSN() {
1973         return (flags & PRESERVE_VLSN_BIT) != 0;
1974     }
1975 
setPreserveVLSN()1976     private void setPreserveVLSN() {
1977         flags |= PRESERVE_VLSN_BIT;
1978     }
1979 
1980     /**
1981      * Release resources and update memory budget. Should only be called
1982      * when this dbtree is closed and will never be accessed again.
1983      */
close()1984     public void close() {
1985         idDatabase.releaseTreeAdminMemory();
1986         nameDatabase.releaseTreeAdminMemory();
1987     }
1988 
getTreeAdminMemory()1989     public long getTreeAdminMemory() {
1990         return idDatabase.getTreeAdminMemory() +
1991             nameDatabase.getTreeAdminMemory();
1992     }
1993 
1994     /*
1995      * RootLevel lets us fetch the root IN within the root latch.
1996      */
1997     private static class RootLevel implements WithRootLatched {
1998         private final DatabaseImpl db;
1999         private int rootLevel;
2000 
RootLevel(DatabaseImpl db)2001         RootLevel(DatabaseImpl db) {
2002             this.db = db;
2003             rootLevel = 0;
2004         }
2005 
doWork(ChildReference root)2006         public IN doWork(ChildReference root)
2007             throws DatabaseException {
2008 
2009             if (root == null) {
2010                 return null;
2011             }
2012             IN rootIN = (IN) root.fetchTarget(db, null);
2013             rootLevel = rootIN.getLevel();
2014             return null;
2015         }
2016 
getRootLevel()2017         int getRootLevel() {
2018             return rootLevel;
2019         }
2020     }
2021 
2022     /*
2023      * Logging support
2024      */
2025 
2026     /**
2027      * @see Loggable#getLogSize
2028      */
getLogSize()2029     public int getLogSize() {
2030         return
2031             LogUtils.getLongLogSize() + // lastAllocatedLocalDbId
2032             LogUtils.getLongLogSize() + // lastAllocatedReplicatedDbId
2033             idDatabase.getLogSize() +
2034             nameDatabase.getLogSize() +
2035             1; // 1 byte of flags
2036     }
2037 
2038     /**
2039      * This log entry type is configured to perform marshaling (getLogSize and
2040      * writeToLog) under the write log mutex.  Otherwise, the size could change
2041      * in between calls to these two methods as the result of utilizaton
2042      * tracking.
2043      *
2044      * @see Loggable#writeToLog
2045      */
writeToLog(ByteBuffer logBuffer)2046     public void writeToLog(ByteBuffer logBuffer) {
2047 
2048         /*
2049          * Long format, rather than packed long format, is used for the last
2050          * allocated DB IDs.  The IDs, and therefore their packed length, can
2051          * change between the getLogSize and writeToLog calls. Since the root
2052          * is infrequently logged, the simplest solution is to use fixed size
2053          * values. [#18540]
2054          */
2055         LogUtils.writeLong(logBuffer, lastAllocatedLocalDbId.get());
2056         LogUtils.writeLong(logBuffer, lastAllocatedReplicatedDbId.get());
2057 
2058         idDatabase.writeToLog(logBuffer);
2059         nameDatabase.writeToLog(logBuffer);
2060         logBuffer.put(flags);
2061     }
2062 
2063     /**
2064      * @see Loggable#readFromLog
2065      */
readFromLog(ByteBuffer itemBuffer, int entryVersion)2066     public void readFromLog(ByteBuffer itemBuffer, int entryVersion) {
2067 
2068         if (entryVersion >= 8) {
2069             lastAllocatedLocalDbId.set(LogUtils.readLong(itemBuffer));
2070             lastAllocatedReplicatedDbId.set(LogUtils.readLong(itemBuffer));
2071         } else {
2072             lastAllocatedLocalDbId.set(LogUtils.readInt(itemBuffer));
2073             if (entryVersion >= 6) {
2074                 lastAllocatedReplicatedDbId.set(LogUtils.readInt(itemBuffer));
2075             }
2076         }
2077 
2078         idDatabase.readFromLog(itemBuffer, entryVersion); // id db
2079         nameDatabase.readFromLog(itemBuffer, entryVersion); // name db
2080 
2081         if (entryVersion >= 6) {
2082             flags = itemBuffer.get();
2083         } else {
2084             flags = 0;
2085         }
2086     }
2087 
2088     /**
2089      * @see Loggable#dumpLog
2090      */
dumpLog(StringBuilder sb, boolean verbose)2091     public void dumpLog(StringBuilder sb, boolean verbose) {
2092         sb.append("<dbtree lastLocalDbId = \"");
2093         sb.append(lastAllocatedLocalDbId);
2094         sb.append("\" lastReplicatedDbId = \"");
2095         sb.append(lastAllocatedReplicatedDbId);
2096         sb.append("\">");
2097         sb.append("<idDb>");
2098         idDatabase.dumpLog(sb, verbose);
2099         sb.append("</idDb><nameDb>");
2100         nameDatabase.dumpLog(sb, verbose);
2101         sb.append("</nameDb>");
2102         sb.append("</dbtree>");
2103     }
2104 
2105     /**
2106      * @see Loggable#getTransactionId
2107      */
getTransactionId()2108     public long getTransactionId() {
2109         return 0;
2110     }
2111 
2112     /**
2113      * @see Loggable#logicalEquals
2114      * Always return false, this item should never be compared.
2115      */
logicalEquals(@uppressWarningsR) Loggable other)2116     public boolean logicalEquals(@SuppressWarnings("unused") Loggable other) {
2117         return false;
2118     }
2119 
2120     /*
2121      * For unit test support
2122      */
2123 
dumpString(int nSpaces)2124     String dumpString(int nSpaces) {
2125         StringBuilder self = new StringBuilder();
2126         self.append(TreeUtils.indent(nSpaces));
2127         self.append("<dbTree lastDbId =\"");
2128         self.append(lastAllocatedLocalDbId);
2129         self.append("\">");
2130         self.append('\n');
2131         self.append(idDatabase.dumpString(nSpaces + 1));
2132         self.append('\n');
2133         self.append(nameDatabase.dumpString(nSpaces + 1));
2134         self.append('\n');
2135         self.append("</dbtree>");
2136         return self.toString();
2137     }
2138 
2139     @Override
toString()2140     public String toString() {
2141         return dumpString(0);
2142     }
2143 
2144     /**
2145      * For debugging.
2146      */
dump()2147     public void dump() {
2148         idDatabase.getTree().dump();
2149         nameDatabase.getTree().dump();
2150     }
2151 }
2152