| /*- |
| * 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; |
| } |
| } |