blob: 66d966c0831a82b55c17291e0207ec9934508209 [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.persist.impl;
/**
* Thrown and handled internally when metadata must be refreshed on a Replica.
*
* There are several scenarios for refreshing DPL metadata:
*
* 1. Read entity record on Replica that has stale in-memory metadata.
*
* When an entity record that references new metadata (for example, a never
* before encountered class) is written on the Master, the metadata is
* written and replicated prior to writing and replicating the entity
* record. However, the Replica's in-memory cache of metadata (the
* PersistCatalog object) is not synchronously updated when the metadata is
* replicated. When the entity record that references the newly replicated
* metadata is read on the Replica, the DPL must refresh the in-memory
* metadata cache by reading it from the catalog database.
*
* Note that this scenario occurs even without class evolution/upgrade, for
* two reasons. First, the Master does not write all metadata at once;
* metadata is added to the catalog incrementally as new persistent classes
* are encountered. Second, even when all metadata is written intially by
* the Master, the Replica may read the catalog before the Master has
* completed metadata updates.
*
* Implementation:
* + PersistCatalog.getFormat(int) throws RefreshException when the given
* format ID is not in the in-memory catalog.
* + The binding method that is calling getFormat catches RefreshException,
* calls RefreshException.refresh to read the updated metadata, and
* retries the operation.
*
* Tests:
* c.s.je.rep.persist.test.UpgradeTest.testIncrementalMetadataChanges
* c.s.je.rep.persist.test.UpgradeTest.testUpgrade
*
* 2. Write entity record on Master that is in Replica Upgrade Mode.
*
* When a Replica is upgraded with new persistent classes (see
* evolve/package.html doc) the DPL will evolve the existing metadata and
* update the in-memory metadata cache (PersistCatalog object), but will not
* write the metadata; instead, it will enter Replica Upgrade Mode. In this
* mode, the Replica will convert old format data to new format data as
* records are read, using the in-memory evolved metadata. This allows the
* Replica application to perform entity read operations with the new
* persistent classes, during the upgrade process.
*
* When this Replica is elected Master, the application will begin writing
* entity records. Note that the new metadata has not yet been written to
* the catalog database. In Replica Upgrade Mode, the current in-memory
* metadata cannot be written to disk, since the catalog database may be
* stale, i.e., it have been updated by the Master after the Replica's
* in-memory metadata was evolved. Therefore, before the first entity
* record is written, the newly elected Master must read the latest
* metadata, perform evolution of the metadata again, write the metadata,
* and then write the entity record.
*
* Implementation:
* + The catalog enters Replica Upgrade Mode when a new or evolved format is
* added to the catalog, and a ReplicaWriteException occurs when
* attempting to write the metadata. Replica Upgrade Mode is defined as
* when the number of in-memory formats is greater than the number of
* stored formats.
* + Before an entity record is written, PersistEntityBinding.objectToData
* is called to convert the entity object to record data.
* + objectToData calls PersistCatalog.checkWriteInReplicaUpgradeMode,
* which throws RefreshExeption in Replica Upgrade Mode.
* + objectToData catches RefreshException, calls RefreshException.refresh
* to read the updated metadata, and retries the operation.
*
* Tests:
* c.s.je.rep.persist.test.UpgradeTest.testElectedMasterWithStaleMetadata
* c.s.je.rep.persist.test.UpgradeTest.testRefreshAfterFirstWrite
* c.s.je.rep.persist.test.UpgradeTest.testUpgrade
*
* 3. Write metadata on Master that is in Replica Upgrade Mode.
*
* This third scenario is more unusual than the first two. It occurs when
* a Replica with stale metadata is elected Master, but is not in Replica
* Upgrade Mode. The new Master must refresh metadata before writing
* metadata. See the test case for more information.
*
* Implementation:
* + On a Master with stale metadata, the application tries to write an
* entity record that refers to a class that has not been encountered
* before.
* + Before the entity record is written, PersistEntityBinding.objectToData
* is called to convert the entity object to record data.
* + objectToData calls PersistCatalog.addNewFormat during serialization,
* which attempts to write metadata by by calling writeDataCheckStale.
* + writeDataCheckStale reads the existing metadata and detects that it
* has changed since metadata was last read, and throws RefreshExeption.
* + objectToData catches RefreshException, calls RefreshException.refresh
* to read the updated metadata, and retries the operation.
*
* Tests:
* c.s.je.rep.persist.test.UpgradeTest.testRefreshBeforeWrite
*/
public class RefreshException extends Exception {
private final Store store;
private final PersistCatalog catalog;
private final int formatId;
RefreshException(final Store store,
final PersistCatalog catalog,
final int formatId) {
this.store = store;
this.catalog = catalog;
this.formatId = formatId;
}
/**
* This method must be called to handle this exception in the binding
* methods, after the stack has unwound. The binding methods should retry
* the operation once after calling this method. If the operation fails
* again, then corruption rather than stale metadata is the likely cause
* of the problem, and an exception will be thrown to that effect.
* [#16655]
*/
public PersistCatalog refresh() {
return store.refresh(catalog, formatId, this);
}
@Override
public String getMessage() {
return "formatId=" + formatId;
}
}