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