Blame source/vdo/base/hashLock.c

Packit Service 310c69
/*
Packit Service 310c69
 * Copyright (c) 2020 Red Hat, Inc.
Packit Service 310c69
 *
Packit Service 310c69
 * This program is free software; you can redistribute it and/or
Packit Service 310c69
 * modify it under the terms of the GNU General Public License
Packit Service 310c69
 * as published by the Free Software Foundation; either version 2
Packit Service 310c69
 * of the License, or (at your option) any later version.
Packit Service 310c69
 * 
Packit Service 310c69
 * This program is distributed in the hope that it will be useful,
Packit Service 310c69
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 310c69
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 310c69
 * GNU General Public License for more details.
Packit Service 310c69
 * 
Packit Service 310c69
 * You should have received a copy of the GNU General Public License
Packit Service 310c69
 * along with this program; if not, write to the Free Software
Packit Service 310c69
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service 310c69
 * 02110-1301, USA. 
Packit Service 310c69
 *
Packit Service 310c69
 * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/hashLock.c#5 $
Packit Service 310c69
 */
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * HashLock controls and coordinates writing, index access, and dedupe among
Packit Service 310c69
 * groups of DataVIOs concurrently writing identical blocks, allowing them to
Packit Service 310c69
 * deduplicate not only against advice but also against each other. This save
Packit Service 310c69
 * on index queries and allows those DataVIOs to concurrently deduplicate
Packit Service 310c69
 * against a single block instead of being serialized through a PBN read lock.
Packit Service 310c69
 * Only one index query is needed for each HashLock, instead of one for every
Packit Service 310c69
 * DataVIO.
Packit Service 310c69
 *
Packit Service 310c69
 * A HashLock acts like a state machine perhaps more than as a lock. Other
Packit Service 310c69
 * than the starting and ending states INITIALIZING and DESTROYING, every
Packit Service 310c69
 * state represents and is held for the duration of an asynchronous operation.
Packit Service 310c69
 * All state transitions are performed on the thread of the HashZone
Packit Service 310c69
 * containing the lock. An asynchronous operation is almost always performed
Packit Service 310c69
 * upon entering a state, and the callback from that operation triggers
Packit Service 310c69
 * exiting the state and entering a new state.
Packit Service 310c69
 *
Packit Service 310c69
 * In all states except DEDUPING, there is a single DataVIO, called the lock
Packit Service 310c69
 * agent, performing the asynchronous operations on behalf of the lock. The
Packit Service 310c69
 * agent will change during the lifetime of the lock if the lock is shared by
Packit Service 310c69
 * more than one DataVIO. DataVIOs waiting to deduplicate are kept on a wait
Packit Service 310c69
 * queue. Viewed a different way, the agent holds the lock exclusively until
Packit Service 310c69
 * the lock enters the DEDUPING state, at which point it becomes a shared lock
Packit Service 310c69
 * that all the waiters (and any new DataVIOs that arrive) use to share a PBN
Packit Service 310c69
 * lock. In state DEDUPING, there is no agent. When the last DataVIO in the
Packit Service 310c69
 * lock calls back in DEDUPING, it becomes the agent and the lock becomes
Packit Service 310c69
 * exclusive again. New DataVIOs that arrive in the lock will also go on the
Packit Service 310c69
 * wait queue.
Packit Service 310c69
 *
Packit Service 310c69
 * The existence of lock waiters is a key factor controlling which state the
Packit Service 310c69
 * lock transitions to next. When the lock is new or has waiters, it will
Packit Service 310c69
 * always try to reach DEDUPING, and when it doesn't, it will try to clean up
Packit Service 310c69
 * and exit.
Packit Service 310c69
 *
Packit Service 310c69
 * Deduping requires holding a PBN lock on a block that is known to contain
Packit Service 310c69
 * data identical to the DataVIOs in the lock, so the lock will send the
Packit Service 310c69
 * agent to the duplicate zone to acquire the PBN lock (LOCKING), to the
Packit Service 310c69
 * kernel I/O threads to read and verify the data (VERIFYING), or to write a
Packit Service 310c69
 * new copy of the data to a full data block or a slot in a compressed block
Packit Service 310c69
 * (WRITING).
Packit Service 310c69
 *
Packit Service 310c69
 * Cleaning up consists of updating the index when the data location is
Packit Service 310c69
 * different from the initial index query (UPDATING, triggered by stale
Packit Service 310c69
 * advice, compression, and rollover), releasing the PBN lock on the duplicate
Packit Service 310c69
 * block (UNLOCKING), and releasing the HashLock itself back to the hash zone
Packit Service 310c69
 * (DESTROYING).
Packit Service 310c69
 *
Packit Service 310c69
 * The shortest sequence of states is for non-concurrent writes of new data:
Packit Service 310c69
 *   INITIALIZING -> QUERYING -> WRITING -> DESTROYING
Packit Service 310c69
 * This sequence is short because no PBN read lock or index update is needed.
Packit Service 310c69
 *
Packit Service 310c69
 * Non-concurrent, finding valid advice looks like this (endpoints elided):
Packit Service 310c69
 *   -> QUERYING -> LOCKING -> VERIFYING -> DEDUPING -> UNLOCKING ->
Packit Service 310c69
 * Or with stale advice (endpoints elided):
Packit Service 310c69
 *   -> QUERYING -> LOCKING -> VERIFYING -> UNLOCKING -> WRITING -> UPDATING ->
Packit Service 310c69
 *
Packit Service 310c69
 * When there are not enough available reference count increments available on
Packit Service 310c69
 * a PBN for a DataVIO to deduplicate, a new lock is forked and the excess
Packit Service 310c69
 * waiters roll over to the new lock (which goes directly to WRITING). The new
Packit Service 310c69
 * lock takes the place of the old lock in the lock map so new DataVIOs will
Packit Service 310c69
 * be directed to it. The two locks will proceed independently, but only the
Packit Service 310c69
 * new lock will have the right to update the index (unless it also forks).
Packit Service 310c69
 *
Packit Service 310c69
 * Since rollover happens in a lock instance, once a valid data location has
Packit Service 310c69
 * been selected, it will not change. QUERYING and WRITING are only performed
Packit Service 310c69
 * once per lock lifetime. All other non-endpoint states can be re-entered.
Packit Service 310c69
 *
Packit Service 310c69
 * XXX still need doc on BYPASSING
Packit Service 310c69
 *
Packit Service 310c69
 * The function names in this module follow a convention referencing the
Packit Service 310c69
 * states and transitions in the state machine diagram for VDOSTORY-190.
Packit Service 310c69
 * [XXX link or repository path to it?]
Packit Service 310c69
 * For example, for the LOCKING state, there are startLocking() and
Packit Service 310c69
 * finishLocking() functions. startLocking() is invoked by the finish function
Packit Service 310c69
 * of the state (or states) that transition to LOCKING. It performs the actual
Packit Service 310c69
 * lock state change and must be invoked on the hash zone thread.
Packit Service 310c69
 * finishLocking() is called by (or continued via callback from) the code
Packit Service 310c69
 * actually obtaining the lock. It does any bookkeeping or decision-making
Packit Service 310c69
 * required and invokes the appropriate start function of the state being
Packit Service 310c69
 * transitioned to after LOCKING.
Packit Service 310c69
 **/
Packit Service 310c69
Packit Service 310c69
#include "hashLock.h"
Packit Service 310c69
#include "hashLockInternals.h"
Packit Service 310c69
Packit Service 310c69
#include "logger.h"
Packit Service 310c69
#include "permassert.h"
Packit Service 310c69
Packit Service 310c69
#include "compressionState.h"
Packit Service 310c69
#include "constants.h"
Packit Service 310c69
#include "dataVIO.h"
Packit Service 310c69
#include "hashZone.h"
Packit Service 310c69
#include "packer.h"
Packit Service 310c69
#include "pbnLock.h"
Packit Service 310c69
#include "physicalZone.h"
Packit Service 310c69
#include "ringNode.h"
Packit Service 310c69
#include "slab.h"
Packit Service 310c69
#include "slabDepot.h"
Packit Service 310c69
#include "trace.h"
Packit Service 310c69
#include "types.h"
Packit Service 310c69
#include "vdoInternal.h"
Packit Service 310c69
#include "vioWrite.h"
Packit Service 310c69
#include "waitQueue.h"
Packit Service 310c69
Packit Service 310c69
static const char *LOCK_STATE_NAMES[] = {
Packit Service 310c69
  [HASH_LOCK_BYPASSING]    = "BYPASSING",
Packit Service 310c69
  [HASH_LOCK_DEDUPING]     = "DEDUPING",
Packit Service 310c69
  [HASH_LOCK_DESTROYING]   = "DESTROYING",
Packit Service 310c69
  [HASH_LOCK_INITIALIZING] = "INITIALIZING",
Packit Service 310c69
  [HASH_LOCK_LOCKING]      = "LOCKING",
Packit Service 310c69
  [HASH_LOCK_QUERYING]     = "QUERYING",
Packit Service 310c69
  [HASH_LOCK_UNLOCKING]    = "UNLOCKING",
Packit Service 310c69
  [HASH_LOCK_UPDATING]     = "UPDATING",
Packit Service 310c69
  [HASH_LOCK_VERIFYING]    = "VERIFYING",
Packit Service 310c69
  [HASH_LOCK_WRITING]      = "WRITING",
Packit Service 310c69
};
Packit Service 310c69
Packit Service 310c69
// There are loops in the state diagram, so some forward decl's are needed.
Packit Service 310c69
static void startDeduping(HashLock *lock, DataVIO *agent, bool agentIsDone);
Packit Service 310c69
static void startLocking(HashLock *lock, DataVIO *agent);
Packit Service 310c69
static void startWriting(HashLock *lock, DataVIO *agent);
Packit Service 310c69
static void unlockDuplicatePBN(VDOCompletion *completion);
Packit Service 310c69
static void transferAllocationLock(DataVIO *dataVIO);
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
PBNLock *getDuplicateLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  if (dataVIO->hashLock == NULL) {
Packit Service 310c69
    return NULL;
Packit Service 310c69
  }
Packit Service 310c69
  return dataVIO->hashLock->duplicateLock;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
const char *getHashLockStateName(HashLockState state)
Packit Service 310c69
{
Packit Service 310c69
  // Catch if a state has been added without updating the name array.
Packit Service 310c69
  STATIC_ASSERT((HASH_LOCK_DESTROYING + 1) == COUNT_OF(LOCK_STATE_NAMES));
Packit Service 310c69
  return (state < COUNT_OF(LOCK_STATE_NAMES)) ? LOCK_STATE_NAMES[state] : NULL;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Set the current state of a hash lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock      The lock to update
Packit Service 310c69
 * @param newState  The new state
Packit Service 310c69
 **/
Packit Service 310c69
static void setHashLockState(HashLock *lock, HashLockState newState)
Packit Service 310c69
{
Packit Service 310c69
  if (false) {
Packit Service 310c69
    logWarning("XXX %" PRIptr " %s -> %s", (void *) lock,
Packit Service 310c69
               getHashLockStateName(lock->state),
Packit Service 310c69
               getHashLockStateName(newState));
Packit Service 310c69
  }
Packit Service 310c69
  lock->state = newState;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Assert that a DataVIO is the agent of its hash lock, and that this is being
Packit Service 310c69
 * called in the hash zone.
Packit Service 310c69
 *
Packit Service 310c69
 * @param dataVIO  The DataVIO expected to be the lock agent
Packit Service 310c69
 * @param where    A string describing the function making the assertion
Packit Service 310c69
 **/
Packit Service 310c69
static void assertHashLockAgent(DataVIO *dataVIO, const char *where)
Packit Service 310c69
{
Packit Service 310c69
  // Not safe to access the agent field except from the hash zone.
Packit Service 310c69
  assertInHashZone(dataVIO);
Packit Service 310c69
  ASSERT_LOG_ONLY(dataVIO == dataVIO->hashLock->agent,
Packit Service 310c69
                  "%s must be for the hash lock agent", where);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Set or clear the lock agent.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock      The hash lock to update
Packit Service 310c69
 * @param newAgent  The new lock agent (may be NULL to clear the agent)
Packit Service 310c69
 **/
Packit Service 310c69
static void setAgent(HashLock *lock, DataVIO *newAgent)
Packit Service 310c69
{
Packit Service 310c69
  lock->agent = newAgent;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Set the duplicate lock held by a hash lock. May only be called in the
Packit Service 310c69
 * physical zone of the PBN lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param hashLock  The hash lock to update
Packit Service 310c69
 * @param pbnLock   The PBN read lock to use as the duplicate lock
Packit Service 310c69
 **/
Packit Service 310c69
static void setDuplicateLock(HashLock *hashLock, PBNLock *pbnLock)
Packit Service 310c69
{
Packit Service 310c69
  ASSERT_LOG_ONLY((hashLock->duplicateLock == NULL),
Packit Service 310c69
                  "hash lock must not already hold a duplicate lock");
Packit Service 310c69
Packit Service 310c69
  pbnLock->holderCount += 1;
Packit Service 310c69
  hashLock->duplicateLock = pbnLock;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Convert a pointer to the hashLockNode field in a DataVIO to the enclosing
Packit Service 310c69
 * DataVIO.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lockNode The RingNode to convert
Packit Service 310c69
 *
Packit Service 310c69
 * @return A pointer to the DataVIO containing the RingNode
Packit Service 310c69
 **/
Packit Service 310c69
static inline DataVIO *dataVIOFromLockNode(RingNode *lockNode)
Packit Service 310c69
{
Packit Service 310c69
  return (DataVIO *) ((byte *) lockNode - offsetof(DataVIO, hashLockNode));
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Remove the first DataVIO from the lock's wait queue and return it.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock  The lock containing the wait queue
Packit Service 310c69
 *
Packit Service 310c69
 * @return The first (oldest) waiter in the queue, or NULL if
Packit Service 310c69
 *         the queue is empty
Packit Service 310c69
 **/
Packit Service 310c69
static inline DataVIO *dequeueLockWaiter(HashLock *lock)
Packit Service 310c69
{
Packit Service 310c69
  return waiterAsDataVIO(dequeueNextWaiter(&lock->waiters));
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Continue processing a DataVIO that has been waiting for an event, setting
Packit Service 310c69
 * the result from the event, and continuing in a specified callback function.
Packit Service 310c69
 *
Packit Service 310c69
 * @param dataVIO   The DataVIO to continue
Packit Service 310c69
 * @param result    The current result (will not mask older errors)
Packit Service 310c69
 * @param callback  The function in which to continue processing
Packit Service 310c69
 **/
Packit Service 310c69
static void continueDataVIOIn(DataVIO   *dataVIO,
Packit Service 310c69
                              int        result,
Packit Service 310c69
                              VDOAction *callback)
Packit Service 310c69
{
Packit Service 310c69
  dataVIOAsCompletion(dataVIO)->callback = callback;
Packit Service 310c69
  continueDataVIO(dataVIO, result);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Set, change, or clear the hash lock a DataVIO is using. Updates the hash
Packit Service 310c69
 * lock (or locks) to reflect the change in membership.
Packit Service 310c69
 *
Packit Service 310c69
 * @param dataVIO  The DataVIO to update
Packit Service 310c69
 * @param newLock  The hash lock the DataVIO is joining
Packit Service 310c69
 **/
Packit Service 310c69
static void setHashLock(DataVIO *dataVIO, HashLock *newLock)
Packit Service 310c69
{
Packit Service 310c69
  HashLock *oldLock = dataVIO->hashLock;
Packit Service 310c69
  if (oldLock != NULL) {
Packit Service 310c69
    ASSERT_LOG_ONLY(dataVIO->hashZone != NULL,
Packit Service 310c69
                    "must have a hash zone when halding a hash lock");
Packit Service 310c69
    ASSERT_LOG_ONLY(!isRingEmpty(&dataVIO->hashLockNode),
Packit Service 310c69
                    "must be on a hash lock ring when holding a hash lock");
Packit Service 310c69
    ASSERT_LOG_ONLY(oldLock->referenceCount > 0,
Packit Service 310c69
                    "hash lock reference must be counted");
Packit Service 310c69
Packit Service 310c69
    if ((oldLock->state != HASH_LOCK_BYPASSING)
Packit Service 310c69
        && (oldLock->state != HASH_LOCK_UNLOCKING)) {
Packit Service 310c69
      // If the reference count goes to zero in a non-terminal state, we're
Packit Service 310c69
      // most likely leaking this lock.
Packit Service 310c69
      ASSERT_LOG_ONLY(oldLock->referenceCount > 1,
Packit Service 310c69
                      "hash locks should only become unreferenced in"
Packit Service 310c69
                      " a terminal state, not state %s",
Packit Service 310c69
                      getHashLockStateName(oldLock->state));
Packit Service 310c69
    }
Packit Service 310c69
Packit Service 310c69
    unspliceRingNode(&dataVIO->hashLockNode);
Packit Service 310c69
    oldLock->referenceCount -= 1;
Packit Service 310c69
Packit Service 310c69
    dataVIO->hashLock = NULL;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (newLock != NULL) {
Packit Service 310c69
    // Keep all DataVIOs sharing the lock on a ring since they can complete in
Packit Service 310c69
    // any order and we'll always need a pointer to one to compare data.
Packit Service 310c69
    pushRingNode(&newLock->duplicateRing, &dataVIO->hashLockNode);
Packit Service 310c69
    newLock->referenceCount += 1;
Packit Service 310c69
Packit Service 310c69
    // XXX Not needed for VDOSTORY-190, but useful for checking whether a test
Packit Service 310c69
    // is getting concurrent dedupe, and how much.
Packit Service 310c69
    if (newLock->maxReferences < newLock->referenceCount) {
Packit Service 310c69
      newLock->maxReferences = newLock->referenceCount;
Packit Service 310c69
    }
Packit Service 310c69
Packit Service 310c69
    dataVIO->hashLock = newLock;
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Bottleneck for DataVIOs that have written or deduplicated and that are no
Packit Service 310c69
 * longer needed to be an agent for the hash lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param dataVIO  The DataVIO to complete and send to be cleaned up
Packit Service 310c69
 **/
Packit Service 310c69
static void exitHashLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  // XXX trace record?
Packit Service 310c69
Packit Service 310c69
  // Release the hash lock now, saving a thread transition in cleanup.
Packit Service 310c69
  releaseHashLock(dataVIO);
Packit Service 310c69
Packit Service 310c69
  // Complete the DataVIO and start the clean-up path in vioWrite to release
Packit Service 310c69
  // any locks it still holds.
Packit Service 310c69
  finishDataVIO(dataVIO, VDO_SUCCESS);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Retire the active lock agent, replacing it with the first lock waiter, and
Packit Service 310c69
 * make the retired agent exit the hash lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock  The hash lock to update
Packit Service 310c69
 *
Packit Service 310c69
 * @return The new lock agent (which will be NULL if there was no waiter)
Packit Service 310c69
 **/
Packit Service 310c69
static DataVIO *retireLockAgent(HashLock *lock)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO *oldAgent = lock->agent;
Packit Service 310c69
  DataVIO *newAgent = dequeueLockWaiter(lock);
Packit Service 310c69
  setAgent(lock, newAgent);
Packit Service 310c69
  exitHashLock(oldAgent);
Packit Service 310c69
  if (newAgent != NULL) {
Packit Service 310c69
    setDuplicateLocation(newAgent, lock->duplicate);
Packit Service 310c69
  }
Packit Service 310c69
  return newAgent;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Callback to call compressData(), putting a DataVIO back on the write path.
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The DataVIO
Packit Service 310c69
 **/
Packit Service 310c69
static void compressDataCallback(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  // XXX VDOSTORY-190 need an error check since compressData doesn't have one.
Packit Service 310c69
  compressData(asDataVIO(completion));
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Add a DataVIO to the lock's queue of waiters.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock     The hash lock on which to wait
Packit Service 310c69
 * @param dataVIO  The DataVIO to add to the queue
Packit Service 310c69
 **/
Packit Service 310c69
static void waitOnHashLock(HashLock *lock, DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  int result = enqueueDataVIO(&lock->waiters, dataVIO, THIS_LOCATION(NULL));
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    // This should be impossible, but if it somehow happens, give up on trying
Packit Service 310c69
    // to dedupe the data.
Packit Service 310c69
    setHashLock(dataVIO, NULL);
Packit Service 310c69
    continueDataVIOIn(dataVIO, result, compressDataCallback);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // Make sure the agent doesn't block indefinitely in the packer since it now
Packit Service 310c69
  // has at least one other DataVIO waiting on it.
Packit Service 310c69
  if ((lock->state == HASH_LOCK_WRITING) && cancelCompression(lock->agent)) {
Packit Service 310c69
    /*
Packit Service 310c69
     * Even though we're waiting, we also have to send ourselves as a one-way
Packit Service 310c69
     * message to the packer to ensure the agent continues executing. This is
Packit Service 310c69
     * safe because cancelCompression() guarantees the agent won't continue
Packit Service 310c69
     * executing until this message arrives in the packer, and because the
Packit Service 310c69
     * wait queue link isn't used for sending the message.
Packit Service 310c69
     */
Packit Service 310c69
    dataVIO->compression.lockHolder = lock->agent;
Packit Service 310c69
    launchPackerCallback(dataVIO, removeLockHolderFromPacker,
Packit Service 310c69
                         THIS_LOCATION("$F;cb=removeLockHolderFromPacker"));
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * WaiterCallback function that calls compressData on the DataVIO waiter.
Packit Service 310c69
 *
Packit Service 310c69
 * @param waiter   The DataVIO's waiter link
Packit Service 310c69
 * @param context  Not used
Packit Service 310c69
 **/
Packit Service 310c69
static void compressWaiter(Waiter *waiter,
Packit Service 310c69
                           void   *context __attribute__((unused)))
Packit Service 310c69
{
Packit Service 310c69
  DataVIO *dataVIO     = waiterAsDataVIO(waiter);
Packit Service 310c69
  dataVIO->isDuplicate = false;
Packit Service 310c69
  compressData(dataVIO);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Handle the result of the agent for the lock releasing a read lock on
Packit Service 310c69
 * duplicate candidate due to aborting the hash lock. This continuation is
Packit Service 310c69
 * registered in unlockDuplicatePBN().
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO acting as the lock's agent
Packit Service 310c69
 **/
Packit Service 310c69
static void finishBypassing(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *agent = asDataVIO(completion);
Packit Service 310c69
  assertHashLockAgent(agent, __func__);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->duplicateLock == NULL,
Packit Service 310c69
                  "must have released the duplicate lock for the hash lock");
Packit Service 310c69
  exitHashLock(agent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Stop using the hash lock, resuming the old write path for the lock agent
Packit Service 310c69
 * and any DataVIOs waiting on it, and put it in a state where DataVIOs
Packit Service 310c69
 * entering the lock will use the old dedupe path instead of waiting.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock
Packit Service 310c69
 * @param agent  The DataVIO acting as the agent for the lock
Packit Service 310c69
 **/
Packit Service 310c69
static void startBypassing(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_BYPASSING);
Packit Service 310c69
Packit Service 310c69
  // Ensure we don't attempt to update advice when cleaning up.
Packit Service 310c69
  lock->updateAdvice = false;
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(((agent != NULL) || !hasWaiters(&lock->waiters)),
Packit Service 310c69
                  "should not have waiters without an agent");
Packit Service 310c69
  notifyAllWaiters(&lock->waiters, compressWaiter, NULL);
Packit Service 310c69
Packit Service 310c69
  if (lock->duplicateLock != NULL) {
Packit Service 310c69
    if (agent != NULL) {
Packit Service 310c69
      // The agent must reference the duplicate zone to launch it.
Packit Service 310c69
      agent->duplicate = lock->duplicate;
Packit Service 310c69
      launchDuplicateZoneCallback(agent, unlockDuplicatePBN,
Packit Service 310c69
                                  THIS_LOCATION(NULL));
Packit Service 310c69
      return;
Packit Service 310c69
    }
Packit Service 310c69
    ASSERT_LOG_ONLY(false, "hash lock holding a PBN lock must have an agent");
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (agent == NULL) {
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  setAgent(lock, NULL);
Packit Service 310c69
  agent->isDuplicate = false;
Packit Service 310c69
  compressData(agent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Abort processing on this hash lock when noticing an error. Currently, this
Packit Service 310c69
 * moves the hash lock to the BYPASSING state, to release all pending DataVIOs.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock     The HashLock
Packit Service 310c69
 * @param dataVIO  The DataVIO with the error
Packit Service 310c69
 **/
Packit Service 310c69
static void abortHashLock(HashLock *lock, DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  // If we've already aborted the lock, don't try to re-abort it; just exit.
Packit Service 310c69
  if (lock->state == HASH_LOCK_BYPASSING) {
Packit Service 310c69
    exitHashLock(dataVIO);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (dataVIO != lock->agent) {
Packit Service 310c69
    if ((lock->agent != NULL) || (lock->referenceCount > 1)) {
Packit Service 310c69
      // Other DataVIOs are still sharing the lock (which should be DEDUPING),
Packit Service 310c69
      // so just kick this one out of the lock to report its error.
Packit Service 310c69
      ASSERT_LOG_ONLY(lock->agent == NULL,
Packit Service 310c69
                      "only active agent should call abortHashLock");
Packit Service 310c69
      exitHashLock(dataVIO);
Packit Service 310c69
      return;
Packit Service 310c69
    }
Packit Service 310c69
    // Make the lone DataVIO the lock agent so it can abort and clean up.
Packit Service 310c69
    setAgent(lock, dataVIO);
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  startBypassing(lock, dataVIO);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Handle the result of the agent for the lock releasing a read lock on
Packit Service 310c69
 * duplicate candidate. This continuation is registered in
Packit Service 310c69
 * unlockDuplicatePBN().
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO acting as the lock's agent
Packit Service 310c69
 **/
Packit Service 310c69
static void finishUnlocking(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *agent = asDataVIO(completion);
Packit Service 310c69
  assertHashLockAgent(agent, __func__);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->duplicateLock == NULL,
Packit Service 310c69
                  "must have released the duplicate lock for the hash lock");
Packit Service 310c69
Packit Service 310c69
  if (completion->result != VDO_SUCCESS) {
Packit Service 310c69
    abortHashLock(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (!lock->verified) {
Packit Service 310c69
    /*
Packit Service 310c69
     * UNLOCKING -> WRITING transition: The lock we released was on an
Packit Service 310c69
     * unverified block, so it must have been a lock on advice we were
Packit Service 310c69
     * verifying, not on a location that was used for deduplication. Go write
Packit Service 310c69
     * (or compress) the block to get a location to dedupe against.
Packit Service 310c69
     */
Packit Service 310c69
    startWriting(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // With the lock released, the verified duplicate block may already have
Packit Service 310c69
  // changed and will need to be re-verified if a waiter arrived.
Packit Service 310c69
  lock->verified = false;
Packit Service 310c69
Packit Service 310c69
  if (hasWaiters(&lock->waiters)) {
Packit Service 310c69
    /*
Packit Service 310c69
     * UNLOCKING -> LOCKING transition: A new DataVIO entered the hash lock
Packit Service 310c69
     * while the agent was releasing the PBN lock. The current agent exits and
Packit Service 310c69
     * the waiter has to re-lock and re-verify the duplicate location.
Packit Service 310c69
     */
Packit Service 310c69
    // XXX VDOSTORY-190 If we used the current agent to re-acquire the PBN
Packit Service 310c69
    // lock we wouldn't need to re-verify.
Packit Service 310c69
    agent = retireLockAgent(lock);
Packit Service 310c69
    startLocking(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * UNLOCKING -> DESTROYING transition: The agent is done with the lock
Packit Service 310c69
   * and no other DataVIOs reference it, so remove it from the lock map
Packit Service 310c69
   * and return it to the pool.
Packit Service 310c69
   */
Packit Service 310c69
  exitHashLock(agent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Release a read lock on the PBN of the block that may or may not have
Packit Service 310c69
 * contained duplicate data. This continuation is launched by
Packit Service 310c69
 * startUnlocking(), and calls back to finishUnlocking() on the hash zone
Packit Service 310c69
 * thread.
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO acting as the lock's agent
Packit Service 310c69
 **/
Packit Service 310c69
static void unlockDuplicatePBN(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO *agent = asDataVIO(completion);
Packit Service 310c69
  assertInDuplicateZone(agent);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->duplicateLock != NULL,
Packit Service 310c69
                  "must have a duplicate lock to release");
Packit Service 310c69
Packit Service 310c69
  releasePBNLock(agent->duplicate.zone, agent->duplicate.pbn,
Packit Service 310c69
                 &lock->duplicateLock);
Packit Service 310c69
Packit Service 310c69
  if (lock->state == HASH_LOCK_BYPASSING) {
Packit Service 310c69
    launchHashZoneCallback(agent, finishBypassing, THIS_LOCATION(NULL));
Packit Service 310c69
  } else {
Packit Service 310c69
    launchHashZoneCallback(agent, finishUnlocking, THIS_LOCATION(NULL));
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Release a read lock on the PBN of the block that may or may not have
Packit Service 310c69
 * contained duplicate data.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock
Packit Service 310c69
 * @param agent  The DataVIO currently acting as the agent for the lock
Packit Service 310c69
 **/
Packit Service 310c69
static void startUnlocking(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_UNLOCKING);
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * XXX If we arrange to continue on the duplicate zone thread when
Packit Service 310c69
   * verification fails, and don't explicitly change lock states (or use an
Packit Service 310c69
   * agent-local state, or an atomic), we can avoid a thread transition here.
Packit Service 310c69
   */
Packit Service 310c69
  launchDuplicateZoneCallback(agent, unlockDuplicatePBN, THIS_LOCATION(NULL));
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Process the result of a UDS update performed by the agent for the lock.
Packit Service 310c69
 * This continuation is registered in startQuerying().
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO that performed the update
Packit Service 310c69
 **/
Packit Service 310c69
static void finishUpdating(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *agent = asDataVIO(completion);
Packit Service 310c69
  assertHashLockAgent(agent, __func__);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  if (completion->result != VDO_SUCCESS) {
Packit Service 310c69
    abortHashLock(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // UDS was updated successfully, so don't update again unless the
Packit Service 310c69
  // duplicate location changes due to rollover.
Packit Service 310c69
  lock->updateAdvice = false;
Packit Service 310c69
Packit Service 310c69
  if (hasWaiters(&lock->waiters)) {
Packit Service 310c69
    /*
Packit Service 310c69
     * UPDATING -> DEDUPING transition: A new DataVIO arrived during the UDS
Packit Service 310c69
     * update. Send it on the verified dedupe path. The agent is done with the
Packit Service 310c69
     * lock, but the lock may still need to use it to clean up after rollover.
Packit Service 310c69
     */
Packit Service 310c69
    startDeduping(lock, agent, true);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (lock->duplicateLock != NULL) {
Packit Service 310c69
    /*
Packit Service 310c69
     * UPDATING -> UNLOCKING transition: No one is waiting to dedupe, but we
Packit Service 310c69
     * hold a duplicate PBN lock, so go release it.
Packit Service 310c69
     */
Packit Service 310c69
    startUnlocking(lock, agent);
Packit Service 310c69
  } else {
Packit Service 310c69
    /*
Packit Service 310c69
     * UPDATING -> DESTROYING transition: No one is waiting to dedupe and
Packit Service 310c69
     * there's no lock to release.
Packit Service 310c69
     */
Packit Service 310c69
    // XXX startDestroying(lock, agent);
Packit Service 310c69
    startBypassing(lock, NULL);
Packit Service 310c69
    exitHashLock(agent);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Continue deduplication with the last step, updating UDS with the location
Packit Service 310c69
 * of the duplicate that should be returned as advice in the future.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock
Packit Service 310c69
 * @param agent  The DataVIO currently acting as the agent for the lock
Packit Service 310c69
 **/
Packit Service 310c69
static void startUpdating(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_UPDATING);
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->verified, "new advice should have been verified");
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->updateAdvice, "should only update advice if needed");
Packit Service 310c69
Packit Service 310c69
  agent->lastAsyncOperation = UPDATE_INDEX;
Packit Service 310c69
  setHashZoneCallback(agent, finishUpdating, THIS_LOCATION(NULL));
Packit Service 310c69
  dataVIOAsCompletion(agent)->layer->updateAlbireo(agent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Handle a DataVIO that has finished deduplicating against the block locked
Packit Service 310c69
 * by the hash lock. If there are other DataVIOs still sharing the lock, this
Packit Service 310c69
 * will just release the DataVIO's share of the lock and finish processing the
Packit Service 310c69
 * DataVIO. If this is the last DataVIO holding the lock, this makes the
Packit Service 310c69
 * DataVIO the lock agent and uses it to advance the state of the lock so it
Packit Service 310c69
 * can eventually be released.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock     The hash lock
Packit Service 310c69
 * @param dataVIO  The lock holder that has finished deduplicating
Packit Service 310c69
 **/
Packit Service 310c69
static void finishDeduping(HashLock *lock, DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->agent == NULL, "shouldn't have an agent in DEDUPING");
Packit Service 310c69
  ASSERT_LOG_ONLY(!hasWaiters(&lock->waiters),
Packit Service 310c69
                  "shouldn't have any lock waiters in DEDUPING");
Packit Service 310c69
Packit Service 310c69
  // Just release the lock reference if other DataVIOs are still deduping.
Packit Service 310c69
  if (lock->referenceCount > 1) {
Packit Service 310c69
    exitHashLock(dataVIO);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // The hash lock must have an agent for all other lock states.
Packit Service 310c69
  DataVIO *agent = dataVIO;
Packit Service 310c69
  setAgent(lock, agent);
Packit Service 310c69
Packit Service 310c69
  if (lock->updateAdvice) {
Packit Service 310c69
    /*
Packit Service 310c69
     * DEDUPING -> UPDATING transition: The location of the duplicate block
Packit Service 310c69
     * changed since the initial UDS query because of compression, rollover,
Packit Service 310c69
     * or because the query agent didn't have an allocation. The UDS update
Packit Service 310c69
     * was delayed in case there was another change in location, but with only
Packit Service 310c69
     * this DataVIO using the hash lock, it's time to update the advice.
Packit Service 310c69
     */
Packit Service 310c69
    startUpdating(lock, agent);
Packit Service 310c69
  } else {
Packit Service 310c69
    /*
Packit Service 310c69
     * DEDUPING -> UNLOCKING transition: Release the PBN read lock on the
Packit Service 310c69
     * duplicate location so the hash lock itself can be released (contingent
Packit Service 310c69
     * on no new DataVIOs arriving in the lock before the agent returns).
Packit Service 310c69
     */
Packit Service 310c69
    startUnlocking(lock, agent);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Implements WaiterCallback. Binds the DataVIO that was waiting to a new hash
Packit Service 310c69
 * lock and waits on that lock.
Packit Service 310c69
 **/
Packit Service 310c69
static void enterForkedLock(Waiter *waiter, void *context)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *dataVIO = waiterAsDataVIO(waiter);
Packit Service 310c69
  HashLock *newLock = (HashLock *) context;
Packit Service 310c69
Packit Service 310c69
  setHashLock(dataVIO, newLock);
Packit Service 310c69
  waitOnHashLock(newLock, dataVIO);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Fork a hash lock because it has run out of increments on the duplicate PBN.
Packit Service 310c69
 * Transfers the new agent and any lock waiters to a new hash lock instance
Packit Service 310c69
 * which takes the place of the old lock in the lock map. The old lock remains
Packit Service 310c69
 * active, but will not update advice.
Packit Service 310c69
 *
Packit Service 310c69
 * @param oldLock   The hash lock to fork
Packit Service 310c69
 * @param newAgent  The DataVIO that will be the agent for the new lock
Packit Service 310c69
 **/
Packit Service 310c69
static void forkHashLock(HashLock *oldLock, DataVIO *newAgent)
Packit Service 310c69
{
Packit Service 310c69
  HashLock *newLock;
Packit Service 310c69
  int result = acquireHashLockFromZone(newAgent->hashZone,
Packit Service 310c69
                                       &newAgent->chunkName,
Packit Service 310c69
                                       oldLock, &newLock);
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    abortHashLock(oldLock, newAgent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // Only one of the two locks should update UDS. The old lock is out of
Packit Service 310c69
  // references, so it would be poor dedupe advice in the short term.
Packit Service 310c69
  oldLock->updateAdvice = false;
Packit Service 310c69
  newLock->updateAdvice = true;
Packit Service 310c69
Packit Service 310c69
  setHashLock(newAgent, newLock);
Packit Service 310c69
  setAgent(newLock, newAgent);
Packit Service 310c69
Packit Service 310c69
  notifyAllWaiters(&oldLock->waiters, enterForkedLock, newLock);
Packit Service 310c69
Packit Service 310c69
  newAgent->isDuplicate = false;
Packit Service 310c69
  startWriting(newLock, newAgent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Reserve a reference count increment for a DataVIO and launch it on the
Packit Service 310c69
 * dedupe path. If no increments are available, this will roll over to a new
Packit Service 310c69
 * hash lock and launch the DataVIO as the writing agent for that lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock      The hash lock
Packit Service 310c69
 * @param dataVIO   The DataVIO to deduplicate using the hash lock
Packit Service 310c69
 * @param hasClaim  true if the dataVIO already has claimed
Packit Service 310c69
 *                  an increment from the duplicate lock
Packit Service 310c69
 **/
Packit Service 310c69
static void launchDedupe(HashLock *lock, DataVIO *dataVIO, bool hasClaim)
Packit Service 310c69
{
Packit Service 310c69
  if (!hasClaim && !claimPBNLockIncrement(lock->duplicateLock)) {
Packit Service 310c69
    // Out of increments, so must roll over to a new lock.
Packit Service 310c69
    forkHashLock(lock, dataVIO);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // Deduplicate against the lock's verified location.
Packit Service 310c69
  setDuplicateLocation(dataVIO, lock->duplicate);
Packit Service 310c69
  launchDuplicateZoneCallback(dataVIO, shareBlock,
Packit Service 310c69
                              THIS_LOCATION("$F;cb=shareBlock"));
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Enter the hash lock state where DataVIOs deduplicate in parallel against a
Packit Service 310c69
 * true copy of their data on disk. If the agent itself needs to deduplicate,
Packit Service 310c69
 * an increment for it must already have been claimed from the duplicate lock,
Packit Service 310c69
 * ensuring the hash lock will still have a DataVIO holding it.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock         The hash lock
Packit Service 310c69
 * @param agent        The DataVIO acting as the agent for the lock
Packit Service 310c69
 * @param agentIsDone  true only if the agent has already written
Packit Service 310c69
 *                     or deduplicated against its data
Packit Service 310c69
 **/
Packit Service 310c69
static void startDeduping(HashLock *lock, DataVIO *agent, bool agentIsDone)
Packit Service 310c69
{
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_DEDUPING);
Packit Service 310c69
Packit Service 310c69
  // We don't take the downgraded allocation lock from the agent unless we
Packit Service 310c69
  // actually need to deduplicate against it.
Packit Service 310c69
  if (lock->duplicateLock == NULL) {
Packit Service 310c69
    ASSERT_LOG_ONLY(!isCompressed(agent->newMapped.state),
Packit Service 310c69
                    "compression must have shared a lock");
Packit Service 310c69
    ASSERT_LOG_ONLY(agentIsDone, "agent must have written the new duplicate");
Packit Service 310c69
    transferAllocationLock(agent);
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(isPBNReadLock(lock->duplicateLock),
Packit Service 310c69
                  "duplicateLock must be a PBN read lock");
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * This state is not like any of the other states. There is no designated
Packit Service 310c69
   * agent--the agent transitioning to this state and all the waiters will be
Packit Service 310c69
   * launched to deduplicate in parallel.
Packit Service 310c69
   */
Packit Service 310c69
  setAgent(lock, NULL);
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * Launch the agent (if not already deduplicated) and as many lock waiters
Packit Service 310c69
   * as we have available increments for on the dedupe path. If we run out of
Packit Service 310c69
   * increments, rollover will be triggered and the remaining waiters will be
Packit Service 310c69
   * transferred to the new lock.
Packit Service 310c69
   */
Packit Service 310c69
  if (!agentIsDone) {
Packit Service 310c69
    launchDedupe(lock, agent, true);
Packit Service 310c69
    agent = NULL;
Packit Service 310c69
  }
Packit Service 310c69
  while (hasWaiters(&lock->waiters)) {
Packit Service 310c69
    launchDedupe(lock, dequeueLockWaiter(lock), false);
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (agentIsDone) {
Packit Service 310c69
    /*
Packit Service 310c69
     * In the degenerate case where all the waiters rolled over to a new lock,
Packit Service 310c69
     * this will continue to use the old agent to clean up this lock, and
Packit Service 310c69
     * otherwise it just lets the agent exit the lock.
Packit Service 310c69
     */
Packit Service 310c69
    finishDeduping(lock, agent);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Handle the result of the agent for the lock comparing its data to the
Packit Service 310c69
 * duplicate candidate. This continuation is registered in startVerifying().
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO used to verify dedupe
Packit Service 310c69
 **/
Packit Service 310c69
static void finishVerifying(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *agent = asDataVIO(completion);
Packit Service 310c69
  assertHashLockAgent(agent, __func__);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  if (completion->result != VDO_SUCCESS) {
Packit Service 310c69
    // XXX VDOSTORY-190 should convert verify IO errors to verification failure
Packit Service 310c69
    abortHashLock(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  lock->verified = agent->isDuplicate;
Packit Service 310c69
Packit Service 310c69
  // Only count the result of the initial verification of the advice as valid
Packit Service 310c69
  // or stale, and not any re-verifications due to PBN lock releases.
Packit Service 310c69
  if (!lock->verifyCounted) {
Packit Service 310c69
    lock->verifyCounted = true;
Packit Service 310c69
    if (lock->verified) {
Packit Service 310c69
      bumpHashZoneValidAdviceCount(agent->hashZone);
Packit Service 310c69
    } else {
Packit Service 310c69
      bumpHashZoneStaleAdviceCount(agent->hashZone);
Packit Service 310c69
    }
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // Even if the block is a verified duplicate, we can't start to deduplicate
Packit Service 310c69
  // unless we can claim a reference count increment for the agent.
Packit Service 310c69
  if (lock->verified && !claimPBNLockIncrement(lock->duplicateLock)) {
Packit Service 310c69
    agent->isDuplicate = false;
Packit Service 310c69
    lock->verified     = false;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (lock->verified) {
Packit Service 310c69
    /*
Packit Service 310c69
     * VERIFYING -> DEDUPING transition: The advice is for a true duplicate,
Packit Service 310c69
     * so start deduplicating against it, if references are available.
Packit Service 310c69
     */
Packit Service 310c69
    startDeduping(lock, agent, false);
Packit Service 310c69
  } else {
Packit Service 310c69
    /*
Packit Service 310c69
     * VERIFYING -> UNLOCKING transition: Either the verify failed or we'd try
Packit Service 310c69
     * to dedupe and roll over immediately, which would fail because it would
Packit Service 310c69
     * leave the lock without an agent to release the PBN lock. In both cases,
Packit Service 310c69
     * the data will have to be written or compressed, but first the advice
Packit Service 310c69
     * PBN must be unlocked by the VERIFYING agent.
Packit Service 310c69
     */
Packit Service 310c69
    lock->updateAdvice = true;
Packit Service 310c69
    startUnlocking(lock, agent);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Continue the deduplication path for a hash lock by using the agent to read
Packit Service 310c69
 * (and possibly decompress) the data at the candidate duplicate location,
Packit Service 310c69
 * comparing it to the data in the agent to verify that the candidate is
Packit Service 310c69
 * identical to all the DataVIOs sharing the hash. If so, it can be
Packit Service 310c69
 * deduplicated against, otherwise a DataVIO allocation will have to be
Packit Service 310c69
 * written to and used for dedupe.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock (must be LOCKING)
Packit Service 310c69
 * @param agent  The DataVIO to use to read and compare candidate data
Packit Service 310c69
 **/
Packit Service 310c69
static void startVerifying(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_VERIFYING);
Packit Service 310c69
  ASSERT_LOG_ONLY(!lock->verified, "hash lock only verifies advice once");
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * XXX VDOSTORY-190 Optimization: This is one of those places where the zone
Packit Service 310c69
   * and continuation we want to use depends on the outcome of the comparison.
Packit Service 310c69
   * If we could choose which path in the layer thread before continuing, we
Packit Service 310c69
   * could save a thread transition in one of the two cases (assuming we're
Packit Service 310c69
   * willing to delay visibility of the the hash lock state change).
Packit Service 310c69
   */
Packit Service 310c69
  VDOCompletion *completion = dataVIOAsCompletion(agent);
Packit Service 310c69
  agent->lastAsyncOperation = VERIFY_DEDUPLICATION;
Packit Service 310c69
  setHashZoneCallback(agent, finishVerifying, THIS_LOCATION(NULL));
Packit Service 310c69
  completion->layer->verifyDuplication(agent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Handle the result of the agent for the lock attempting to obtain a PBN read
Packit Service 310c69
 * lock on the candidate duplicate block. this continuation is registered in
Packit Service 310c69
 * lockDuplicatePBN().
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO that attempted to get
Packit Service 310c69
 *                    the read lock
Packit Service 310c69
 **/
Packit Service 310c69
static void finishLocking(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *agent = asDataVIO(completion);
Packit Service 310c69
  assertHashLockAgent(agent, __func__);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  if (completion->result != VDO_SUCCESS) {
Packit Service 310c69
    // XXX clearDuplicateLocation()?
Packit Service 310c69
    agent->isDuplicate = false;
Packit Service 310c69
    abortHashLock(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (!agent->isDuplicate) {
Packit Service 310c69
    ASSERT_LOG_ONLY(lock->duplicateLock == NULL,
Packit Service 310c69
                  "must not hold duplicateLock if not flagged as a duplicate");
Packit Service 310c69
    /*
Packit Service 310c69
     * LOCKING -> WRITING transition: The advice block is being modified or
Packit Service 310c69
     * has no available references, so try to write or compress the data,
Packit Service 310c69
     * remembering to update UDS later with the new advice.
Packit Service 310c69
     */
Packit Service 310c69
    bumpHashZoneStaleAdviceCount(agent->hashZone);
Packit Service 310c69
    lock->updateAdvice = true;
Packit Service 310c69
    startWriting(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->duplicateLock != NULL,
Packit Service 310c69
                  "must hold duplicateLock if flagged as a duplicate");
Packit Service 310c69
Packit Service 310c69
  if (!lock->verified) {
Packit Service 310c69
    /*
Packit Service 310c69
     * LOCKING -> VERIFYING transition: Continue on the unverified dedupe path,
Packit Service 310c69
     * reading the candidate duplicate and comparing it to the agent's data to
Packit Service 310c69
     * decide whether it is a true duplicate or stale advice.
Packit Service 310c69
     */
Packit Service 310c69
    startVerifying(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (!claimPBNLockIncrement(lock->duplicateLock)) {
Packit Service 310c69
    /*
Packit Service 310c69
     * LOCKING -> UNLOCKING transition: The verified block was re-locked, but
Packit Service 310c69
     * has no available increments left. Must first release the useless PBN
Packit Service 310c69
     * read lock before rolling over to a new copy of the block.
Packit Service 310c69
     */
Packit Service 310c69
    agent->isDuplicate = false;
Packit Service 310c69
    lock->verified     = false;
Packit Service 310c69
    lock->updateAdvice = true;
Packit Service 310c69
    startUnlocking(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * LOCKING -> DEDUPING transition: Continue on the verified dedupe path,
Packit Service 310c69
   * deduplicating against a location that was previously verified or
Packit Service 310c69
   * written to.
Packit Service 310c69
   */
Packit Service 310c69
  startDeduping(lock, agent, false);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Acquire a read lock on the PBN of the block containing candidate duplicate
Packit Service 310c69
 * data (compressed or uncompressed). If the PBN is already locked for
Packit Service 310c69
 * writing, the lock attempt is abandoned and isDuplicate will be cleared
Packit Service 310c69
 * before calling back. this continuation is launched from startLocking(), and
Packit Service 310c69
 * calls back to finishLocking() on the hash zone thread.
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion The completion of the DataVIO attempting to acquire the
Packit Service 310c69
 *                   physical block lock on behalf of its hash lock
Packit Service 310c69
 **/
Packit Service 310c69
static void lockDuplicatePBN(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO      *agent = asDataVIO(completion);
Packit Service 310c69
  PhysicalZone *zone  = agent->duplicate.zone;
Packit Service 310c69
  assertInDuplicateZone(agent);
Packit Service 310c69
Packit Service 310c69
  setHashZoneCallback(agent, finishLocking, THIS_LOCATION(NULL));
Packit Service 310c69
Packit Service 310c69
  // While in the zone that owns it, find out how many additional references
Packit Service 310c69
  // can be made to the block if it turns out to truly be a duplicate.
Packit Service 310c69
  SlabDepot *depot = getSlabDepot(getVDOFromDataVIO(agent));
Packit Service 310c69
  unsigned int incrementLimit = getIncrementLimit(depot, agent->duplicate.pbn);
Packit Service 310c69
  if (incrementLimit == 0) {
Packit Service 310c69
    // We could deduplicate against it later if a reference happened to be
Packit Service 310c69
    // released during verification, but it's probably better to bail out now.
Packit Service 310c69
    // XXX clearDuplicateLocation()?
Packit Service 310c69
    agent->isDuplicate = false;
Packit Service 310c69
    continueDataVIO(agent, VDO_SUCCESS);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  PBNLock *lock;
Packit Service 310c69
  int result = attemptPBNLock(zone, agent->duplicate.pbn, VIO_READ_LOCK,
Packit Service 310c69
                              &lock);
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    continueDataVIO(agent, result);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (!isPBNReadLock(lock)) {
Packit Service 310c69
    /*
Packit Service 310c69
     * There are three cases of write locks: uncompressed data block writes,
Packit Service 310c69
     * compressed (packed) block writes, and block map page writes. In all
Packit Service 310c69
     * three cases, we give up on trying to verify the advice and don't bother
Packit Service 310c69
     * to try deduplicate against the data in the write lock holder.
Packit Service 310c69
     *
Packit Service 310c69
     * 1) We don't ever want to try to deduplicate against a block map page.
Packit Service 310c69
     *
Packit Service 310c69
     * 2a) It's very unlikely we'd deduplicate against an entire packed block,
Packit Service 310c69
     * both because of the chance of matching it, and because we don't record
Packit Service 310c69
     * advice for it, but for the uncompressed representation of all the
Packit Service 310c69
     * fragments it contains. The only way we'd be getting lock contention is
Packit Service 310c69
     * if we've written the same representation coincidentally before, had it
Packit Service 310c69
     * become unreferenced, and it just happened to be packed together from
Packit Service 310c69
     * compressed writes when we go to verify the lucky advice. Giving up is a
Packit Service 310c69
     * miniscule loss of potential dedupe.
Packit Service 310c69
     *
Packit Service 310c69
     * 2b) If the advice is for a slot of a compressed block, it's about to
Packit Service 310c69
     * get smashed, and the write smashing it cannot contain our data--it
Packit Service 310c69
     * would have to be writing on behalf of our hash lock, but that's
Packit Service 310c69
     * impossible since we're the lock agent.
Packit Service 310c69
     *
Packit Service 310c69
     * 3a) If the lock is held by a DataVIO with different data, the advice is
Packit Service 310c69
     * already stale or is about to become stale.
Packit Service 310c69
     *
Packit Service 310c69
     * 3b) If the lock is held by a DataVIO that matches us, we may as well
Packit Service 310c69
     * either write it ourselves (or reference the copy we already wrote)
Packit Service 310c69
     * instead of potentially having many duplicates wait for the lock holder
Packit Service 310c69
     * to write, journal, hash, and finally arrive in the hash lock. All we
Packit Service 310c69
     * lose is a chance to avoid a UDS update in the very rare case of advice
Packit Service 310c69
     * for a free block that just happened to be allocated to a DataVIO with
Packit Service 310c69
     * the same hash. In async mode, there's also a chance to save on a block
Packit Service 310c69
     * write, at the cost of a block verify. Saving on a full block compare in
Packit Service 310c69
     * all stale advice cases almost certainly outweighs saving a UDS update
Packit Service 310c69
     * in a lucky case where advice would have been saved from becoming stale.
Packit Service 310c69
     */
Packit Service 310c69
    // XXX clearDuplicateLocation()?
Packit Service 310c69
    agent->isDuplicate = false;
Packit Service 310c69
    continueDataVIO(agent, VDO_SUCCESS);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (lock->holderCount == 0) {
Packit Service 310c69
    // Ensure that the newly-locked block is referenced.
Packit Service 310c69
    Slab *slab = getSlab(depot, agent->duplicate.pbn);
Packit Service 310c69
    result = acquireProvisionalReference(slab, agent->duplicate.pbn, lock);
Packit Service 310c69
    if (result != VDO_SUCCESS) {
Packit Service 310c69
      logWarningWithStringError(result,
Packit Service 310c69
                                "Error acquiring provisional reference for "
Packit Service 310c69
                                "dedupe candidate; aborting dedupe");
Packit Service 310c69
      agent->isDuplicate = false;
Packit Service 310c69
      releasePBNLock(zone, agent->duplicate.pbn, &lock);
Packit Service 310c69
      continueDataVIO(agent, result);
Packit Service 310c69
      return;
Packit Service 310c69
    }
Packit Service 310c69
Packit Service 310c69
    /*
Packit Service 310c69
     * The increment limit we grabbed earlier is still valid. The lock now
Packit Service 310c69
     * holds the rights to acquire all those references. Those rights will be
Packit Service 310c69
     * claimed by hash locks sharing this read lock.
Packit Service 310c69
     */
Packit Service 310c69
    lock->incrementLimit = incrementLimit;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // We've successfully acquired a read lock on behalf of the hash lock,
Packit Service 310c69
  // so mark it as such.
Packit Service 310c69
  setDuplicateLock(agent->hashLock, lock);
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * XXX VDOSTORY-190 Optimization: Same as startLocking() lazily changing
Packit Service 310c69
   * state to save on having to switch back to the hash zone thread. Here we
Packit Service 310c69
   * could directly launch the block verify, then switch to a hash thread.
Packit Service 310c69
   */
Packit Service 310c69
  continueDataVIO(agent, VDO_SUCCESS);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Continue deduplication for a hash lock that has obtained valid advice
Packit Service 310c69
 * of a potential duplicate through its agent.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock (currently must be QUERYING)
Packit Service 310c69
 * @param agent  The DataVIO bearing the dedupe advice
Packit Service 310c69
 **/
Packit Service 310c69
static void startLocking(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  ASSERT_LOG_ONLY(lock->duplicateLock == NULL,
Packit Service 310c69
                  "must not acquire a duplicate lock when already holding it");
Packit Service 310c69
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_LOCKING);
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * XXX VDOSTORY-190 Optimization: If we arrange to continue on the duplicate
Packit Service 310c69
   * zone thread when accepting the advice, and don't explicitly change lock
Packit Service 310c69
   * states (or use an agent-local state, or an atomic), we can avoid a thread
Packit Service 310c69
   * transition here.
Packit Service 310c69
   */
Packit Service 310c69
  agent->lastAsyncOperation = ACQUIRE_PBN_READ_LOCK;
Packit Service 310c69
  launchDuplicateZoneCallback(agent, lockDuplicatePBN, THIS_LOCATION(NULL));
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Re-entry point for the lock agent after it has finished writing or
Packit Service 310c69
 * compressing its copy of the data block. The agent will never need to dedupe
Packit Service 310c69
 * against anything, so it's done with the lock, but the lock may not be
Packit Service 310c69
 * finished with it, as a UDS update might still be needed.
Packit Service 310c69
 *
Packit Service 310c69
 * If there are other lock holders, the agent will hand the job to one of them
Packit Service 310c69
 * and exit, leaving the lock to deduplicate against the just-written block.
Packit Service 310c69
 * If there are no other lock holders, the agent either exits (and later tears
Packit Service 310c69
 * down the hash lock), or it remains the agent and updates UDS.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock, which must be in state WRITING
Packit Service 310c69
 * @param agent  The DataVIO that wrote its data for the lock
Packit Service 310c69
 **/
Packit Service 310c69
static void finishWriting(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  // Dedupe against the data block or compressed block slot the agent wrote.
Packit Service 310c69
  // Since we know the write succeeded, there's no need to verify it.
Packit Service 310c69
  lock->duplicate = agent->newMapped;
Packit Service 310c69
  lock->verified  = true;
Packit Service 310c69
Packit Service 310c69
  if (isCompressed(lock->duplicate.state) && lock->registered) {
Packit Service 310c69
    // Compression means the location we gave in the UDS query is not the
Packit Service 310c69
    // location we're using to deduplicate.
Packit Service 310c69
    lock->updateAdvice = true;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // If there are any waiters, we need to start deduping them.
Packit Service 310c69
  if (hasWaiters(&lock->waiters)) {
Packit Service 310c69
    /*
Packit Service 310c69
     * WRITING -> DEDUPING transition: an asynchronously-written block
Packit Service 310c69
     * failed to compress, so the PBN lock on the written copy was already
Packit Service 310c69
     * transferred. The agent is done with the lock, but the lock may
Packit Service 310c69
     * still need to use it to clean up after rollover.
Packit Service 310c69
     */
Packit Service 310c69
    startDeduping(lock, agent, true);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // There are no waiters and the agent has successfully written, so take a
Packit Service 310c69
  // step towards being able to release the hash lock (or just release it).
Packit Service 310c69
  if (lock->updateAdvice) {
Packit Service 310c69
    /*
Packit Service 310c69
     * WRITING -> UPDATING transition: There's no waiter and a UDS update is
Packit Service 310c69
     * needed, so retain the WRITING agent and use it to launch the update.
Packit Service 310c69
     * The happens on compression, rollover, or the QUERYING agent not having
Packit Service 310c69
     * an allocation.
Packit Service 310c69
     */
Packit Service 310c69
    startUpdating(lock, agent);
Packit Service 310c69
  } else if (lock->duplicateLock != NULL) {
Packit Service 310c69
    /*
Packit Service 310c69
     * WRITING -> UNLOCKING transition: There's no waiter and no update
Packit Service 310c69
     * needed, but the compressed write gave us a shared duplicate lock that
Packit Service 310c69
     * we must release.
Packit Service 310c69
     */
Packit Service 310c69
    setDuplicateLocation(agent, lock->duplicate);
Packit Service 310c69
    startUnlocking(lock, agent);
Packit Service 310c69
  } else {
Packit Service 310c69
    /*
Packit Service 310c69
     * WRITING -> DESTROYING transition: There's no waiter, no update needed,
Packit Service 310c69
     * and no duplicate lock held, so both the agent and lock have no more
Packit Service 310c69
     * work to do. The agent will release its allocation lock in cleanup.
Packit Service 310c69
     */
Packit Service 310c69
    // XXX startDestroying(lock, agent);
Packit Service 310c69
    startBypassing(lock, NULL);
Packit Service 310c69
    exitHashLock(agent);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Search through the lock waiters for a DataVIO that has an allocation. If
Packit Service 310c69
 * one is found, swap agents, put the old agent at the head of the wait queue,
Packit Service 310c69
 * then return the new agent. Otherwise, just return the current agent.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock to modify
Packit Service 310c69
 **/
Packit Service 310c69
static DataVIO *selectWritingAgent(HashLock *lock)
Packit Service 310c69
{
Packit Service 310c69
  // This should-be-impossible condition is the only cause for
Packit Service 310c69
  // enqueueDataVIO() to fail later on, where it would be a pain to handle.
Packit Service 310c69
  int result = ASSERT(!isWaiting(dataVIOAsWaiter(lock->agent)),
Packit Service 310c69
                      "agent must not be waiting");
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    return lock->agent;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  WaitQueue tempQueue;
Packit Service 310c69
  initializeWaitQueue(&tempQueue);
Packit Service 310c69
Packit Service 310c69
  // Move waiters to the temp queue one-by-one until we find an allocation.
Packit Service 310c69
  // Not ideal to search, but it only happens when nearly out of space.
Packit Service 310c69
  DataVIO *dataVIO;
Packit Service 310c69
  while (((dataVIO = dequeueLockWaiter(lock)) != NULL)
Packit Service 310c69
         && !hasAllocation(dataVIO)) {
Packit Service 310c69
    // Use the lower-level enqueue since we're just moving waiters around.
Packit Service 310c69
    int result = enqueueWaiter(&tempQueue, dataVIOAsWaiter(dataVIO));
Packit Service 310c69
    // The only error is the DataVIO already being on a wait queue, and since
Packit Service 310c69
    // we just dequeued it, that could only happen due to a memory smash or
Packit Service 310c69
    // concurrent use of that DataVIO.
Packit Service 310c69
    ASSERT_LOG_ONLY(result == VDO_SUCCESS, "impossible enqueueWaiter error");
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (dataVIO != NULL) {
Packit Service 310c69
    // Move the rest of the waiters over to the temp queue, preserving the
Packit Service 310c69
    // order they arrived at the lock.
Packit Service 310c69
    transferAllWaiters(&lock->waiters, &tempQueue);
Packit Service 310c69
Packit Service 310c69
    // The current agent is being replaced and will have to wait to dedupe;
Packit Service 310c69
    // make it the first waiter since it was the first to reach the lock.
Packit Service 310c69
    int result = enqueueDataVIO(&lock->waiters, lock->agent,
Packit Service 310c69
                                THIS_LOCATION(NULL));
Packit Service 310c69
    ASSERT_LOG_ONLY(result == VDO_SUCCESS,
Packit Service 310c69
                    "impossible enqueueDataVIO error after isWaiting checked");
Packit Service 310c69
    setAgent(lock, dataVIO);
Packit Service 310c69
  } else {
Packit Service 310c69
    // No one has an allocation, so keep the current agent.
Packit Service 310c69
    dataVIO = lock->agent;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // Swap all the waiters back onto the lock's queue.
Packit Service 310c69
  transferAllWaiters(&tempQueue, &lock->waiters);
Packit Service 310c69
  return dataVIO;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Begin the non-duplicate write path for a hash lock that had no advice,
Packit Service 310c69
 * selecting a DataVIO with an allocation as a new agent, if necessary,
Packit Service 310c69
 * then resuming the agent on the DataVIO write path.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock   The hash lock (currently must be QUERYING)
Packit Service 310c69
 * @param agent  The DataVIO currently acting as the agent for the lock
Packit Service 310c69
 **/
Packit Service 310c69
static void startWriting(HashLock *lock, DataVIO *agent)
Packit Service 310c69
{
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_WRITING);
Packit Service 310c69
Packit Service 310c69
  // The agent might not have received an allocation and so can't be used for
Packit Service 310c69
  // writing, but it's entirely possible that one of the waiters did.
Packit Service 310c69
  if (!hasAllocation(agent)) {
Packit Service 310c69
    agent = selectWritingAgent(lock);
Packit Service 310c69
    // If none of the waiters had an allocation, the writes all have to fail.
Packit Service 310c69
    if (!hasAllocation(agent)) {
Packit Service 310c69
      /*
Packit Service 310c69
       * XXX VDOSTORY-190 Should we keep a variant of BYPASSING that causes
Packit Service 310c69
       * new arrivals to fail immediately if they don't have an allocation? It
Packit Service 310c69
       * might be possible that on some path there would be non-waiters still
Packit Service 310c69
       * referencing the lock, so it would remain in the map as everything is
Packit Service 310c69
       * currently spelled, even if the agent and all the waiters release.
Packit Service 310c69
       */
Packit Service 310c69
      startBypassing(lock, agent);
Packit Service 310c69
      return;
Packit Service 310c69
    }
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // If the agent compresses, it might wait indefinitely in the packer,
Packit Service 310c69
  // which would be bad if there are any other DataVIOs waiting.
Packit Service 310c69
  if (hasWaiters(&lock->waiters)) {
Packit Service 310c69
    // XXX in sync mode, transition directly to LOCKING to start dedupe?
Packit Service 310c69
    cancelCompression(agent);
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  /*
Packit Service 310c69
   * Send the agent to the compress/pack/async-write path in vioWrite. If it
Packit Service 310c69
   * succeeds, it will return to the hash lock via continueHashLock() and call
Packit Service 310c69
   * finishWriting().
Packit Service 310c69
   */
Packit Service 310c69
  compressData(agent);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Process the result of a UDS query performed by the agent for the lock. This
Packit Service 310c69
 * continuation is registered in startQuerying().
Packit Service 310c69
 *
Packit Service 310c69
 * @param completion  The completion of the DataVIO that performed the query
Packit Service 310c69
 **/
Packit Service 310c69
static void finishQuerying(VDOCompletion *completion)
Packit Service 310c69
{
Packit Service 310c69
  DataVIO  *agent = asDataVIO(completion);
Packit Service 310c69
  assertHashLockAgent(agent, __func__);
Packit Service 310c69
  HashLock *lock = agent->hashLock;
Packit Service 310c69
Packit Service 310c69
  if (completion->result != VDO_SUCCESS) {
Packit Service 310c69
    abortHashLock(lock, agent);
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (agent->isDuplicate) {
Packit Service 310c69
    lock->duplicate = agent->duplicate;
Packit Service 310c69
    /*
Packit Service 310c69
     * QUERYING -> LOCKING transition: Valid advice was obtained from UDS.
Packit Service 310c69
     * Use the QUERYING agent to start the hash lock on the unverified dedupe
Packit Service 310c69
     * path, verifying that the advice can be used.
Packit Service 310c69
     */
Packit Service 310c69
    startLocking(lock, agent);
Packit Service 310c69
  } else {
Packit Service 310c69
    // The agent will be used as the duplicate if has an allocation; if it
Packit Service 310c69
    // does, that location was posted to UDS, so no update will be needed.
Packit Service 310c69
    lock->updateAdvice = !hasAllocation(agent);
Packit Service 310c69
    /*
Packit Service 310c69
     * QUERYING -> WRITING transition: There was no advice or the advice
Packit Service 310c69
     * wasn't valid, so try to write or compress the data.
Packit Service 310c69
     */
Packit Service 310c69
    startWriting(lock, agent);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Start deduplication for a hash lock that has finished initializing by
Packit Service 310c69
 * making the DataVIO that requested it the agent, entering the QUERYING
Packit Service 310c69
 * state, and using the agent to perform the UDS query on behalf of the lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock     The initialized hash lock
Packit Service 310c69
 * @param dataVIO  The DataVIO that has just obtained the new lock
Packit Service 310c69
 **/
Packit Service 310c69
static void startQuerying(HashLock *lock, DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  setAgent(lock, dataVIO);
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_QUERYING);
Packit Service 310c69
Packit Service 310c69
  VDOCompletion *completion   = dataVIOAsCompletion(dataVIO);
Packit Service 310c69
  dataVIO->lastAsyncOperation = CHECK_FOR_DEDUPLICATION;
Packit Service 310c69
  setHashZoneCallback(dataVIO, finishQuerying, THIS_LOCATION(NULL));
Packit Service 310c69
  completion->layer->checkForDuplication(dataVIO);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Complain that a DataVIO has entered a HashLock that is in an unimplemented
Packit Service 310c69
 * or unusable state and continue the DataVIO with an error.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock     The hash lock
Packit Service 310c69
 * @param dataVIO  The DataVIO attempting to enter the lock
Packit Service 310c69
 **/
Packit Service 310c69
static void reportBogusLockState(HashLock *lock, DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  int result = ASSERT_FALSE("hash lock must not be in unimplemented state %s",
Packit Service 310c69
                            getHashLockStateName(lock->state));
Packit Service 310c69
  continueDataVIOIn(dataVIO, result, compressDataCallback);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
void enterHashLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  HashLock *lock = dataVIO->hashLock;
Packit Service 310c69
  switch (lock->state) {
Packit Service 310c69
  case HASH_LOCK_INITIALIZING:
Packit Service 310c69
    startQuerying(lock, dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_QUERYING:
Packit Service 310c69
  case HASH_LOCK_WRITING:
Packit Service 310c69
  case HASH_LOCK_UPDATING:
Packit Service 310c69
  case HASH_LOCK_LOCKING:
Packit Service 310c69
  case HASH_LOCK_VERIFYING:
Packit Service 310c69
  case HASH_LOCK_UNLOCKING:
Packit Service 310c69
    // The lock is busy, and can't be shared yet.
Packit Service 310c69
    waitOnHashLock(lock, dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_BYPASSING:
Packit Service 310c69
    // Bypass dedupe entirely.
Packit Service 310c69
    compressData(dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_DEDUPING:
Packit Service 310c69
    launchDedupe(lock, dataVIO, false);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_DESTROYING:
Packit Service 310c69
    // A lock in this state should not be acquired by new VIOs.
Packit Service 310c69
    reportBogusLockState(lock, dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  default:
Packit Service 310c69
    reportBogusLockState(lock, dataVIO);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
void continueHashLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  HashLock *lock = dataVIO->hashLock;
Packit Service 310c69
  // XXX VDOSTORY-190 Eventually we may be able to fold the error handling
Packit Service 310c69
  // in at this point instead of using a separate entry point for it.
Packit Service 310c69
Packit Service 310c69
  switch (lock->state) {
Packit Service 310c69
  case HASH_LOCK_WRITING:
Packit Service 310c69
    ASSERT_LOG_ONLY(dataVIO == lock->agent,
Packit Service 310c69
                    "only the lock agent may continue the lock");
Packit Service 310c69
    finishWriting(lock, dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_DEDUPING:
Packit Service 310c69
    finishDeduping(lock, dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_BYPASSING:
Packit Service 310c69
    // This DataVIO has finished the write path and the lock doesn't need it.
Packit Service 310c69
    // XXX This isn't going to be correct if DEDUPING ever uses BYPASSING.
Packit Service 310c69
    finishDataVIO(dataVIO, VDO_SUCCESS);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  case HASH_LOCK_INITIALIZING:
Packit Service 310c69
  case HASH_LOCK_QUERYING:
Packit Service 310c69
  case HASH_LOCK_UPDATING:
Packit Service 310c69
  case HASH_LOCK_LOCKING:
Packit Service 310c69
  case HASH_LOCK_VERIFYING:
Packit Service 310c69
  case HASH_LOCK_UNLOCKING:
Packit Service 310c69
  case HASH_LOCK_DESTROYING:
Packit Service 310c69
    // A lock in this state should never be re-entered.
Packit Service 310c69
    reportBogusLockState(lock, dataVIO);
Packit Service 310c69
    break;
Packit Service 310c69
Packit Service 310c69
  default:
Packit Service 310c69
    reportBogusLockState(lock, dataVIO);
Packit Service 310c69
  }
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
void continueHashLockOnError(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  // XXX We could simply use continueHashLock() and check for errors in that.
Packit Service 310c69
  abortHashLock(dataVIO->hashLock, dataVIO);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Check whether the data in DataVIOs sharing a lock is different than in a
Packit Service 310c69
 * DataVIO seeking to share the lock, which should only be possible in the
Packit Service 310c69
 * extremely unlikely case of a hash collision.
Packit Service 310c69
 *
Packit Service 310c69
 * @param lock       The lock to check
Packit Service 310c69
 * @param candidate  The DataVIO seeking to share the lock
Packit Service 310c69
 *
Packit Service 310c69
 * @return true if the given DataVIO must not share the lock
Packit Service 310c69
 *         because it doesn't have the same data as the lock holders
Packit Service 310c69
 **/
Packit Service 310c69
static bool isHashCollision(HashLock *lock, DataVIO *candidate)
Packit Service 310c69
{
Packit Service 310c69
  if (isRingEmpty(&lock->duplicateRing)) {
Packit Service 310c69
    return false;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  DataVIO       *lockHolder = dataVIOFromLockNode(lock->duplicateRing.next);
Packit Service 310c69
  PhysicalLayer *layer      = dataVIOAsCompletion(candidate)->layer;
Packit Service 310c69
  bool           collides   = !layer->compareDataVIOs(lockHolder, candidate);
Packit Service 310c69
Packit Service 310c69
  if (collides) {
Packit Service 310c69
    bumpHashZoneCollisionCount(candidate->hashZone);
Packit Service 310c69
  } else {
Packit Service 310c69
    bumpHashZoneDataMatchCount(candidate->hashZone);
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  return collides;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
static inline int assertHashLockPreconditions(const DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  int result = ASSERT(dataVIO->hashLock == NULL,
Packit Service 310c69
                      "must not already hold a hash lock");
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    return result;
Packit Service 310c69
  }
Packit Service 310c69
  result = ASSERT(isRingEmpty(&dataVIO->hashLockNode),
Packit Service 310c69
                  "must not already be a member of a hash lock ring");
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    return result;
Packit Service 310c69
  }
Packit Service 310c69
  return ASSERT(dataVIO->recoverySequenceNumber == 0,
Packit Service 310c69
                "must not hold a recovery lock when getting a hash lock");
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
int acquireHashLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  int result = assertHashLockPreconditions(dataVIO);
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    return result;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  HashLock *lock;
Packit Service 310c69
  result = acquireHashLockFromZone(dataVIO->hashZone, &dataVIO->chunkName,
Packit Service 310c69
                                   NULL, &lock);
Packit Service 310c69
  if (result != VDO_SUCCESS) {
Packit Service 310c69
    return result;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  if (isHashCollision(lock, dataVIO)) {
Packit Service 310c69
    // Hash collisions are extremely unlikely, but the bogus dedupe would be a
Packit Service 310c69
    // data corruption. Bypass dedupe entirely by leaving hashLock unset.
Packit Service 310c69
    // XXX clear hashZone too?
Packit Service 310c69
    return VDO_SUCCESS;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  setHashLock(dataVIO, lock);
Packit Service 310c69
  return VDO_SUCCESS;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
void releaseHashLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  HashLock *lock = dataVIO->hashLock;
Packit Service 310c69
  if (lock == NULL) {
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  setHashLock(dataVIO, NULL);
Packit Service 310c69
Packit Service 310c69
  if (lock->referenceCount > 0) {
Packit Service 310c69
    // The lock is still in use by other DataVIOs.
Packit Service 310c69
    return;
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  setHashLockState(lock, HASH_LOCK_DESTROYING);
Packit Service 310c69
  returnHashLockToZone(dataVIO->hashZone, &lock);
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**
Packit Service 310c69
 * Transfer a DataVIO's downgraded allocation PBN lock to the DataVIO's hash
Packit Service 310c69
 * lock, converting it to a duplicate PBN lock.
Packit Service 310c69
 *
Packit Service 310c69
 * @param dataVIO  The DataVIO holding the allocation lock to transfer
Packit Service 310c69
 **/
Packit Service 310c69
static void transferAllocationLock(DataVIO *dataVIO)
Packit Service 310c69
{
Packit Service 310c69
  ASSERT_LOG_ONLY(dataVIO->newMapped.pbn == getDataVIOAllocation(dataVIO),
Packit Service 310c69
                  "transferred lock must be for the block written");
Packit Service 310c69
Packit Service 310c69
  AllocatingVIO *allocatingVIO  = dataVIOAsAllocatingVIO(dataVIO);
Packit Service 310c69
  PBNLock       *pbnLock        = allocatingVIO->allocationLock;
Packit Service 310c69
  allocatingVIO->allocationLock = NULL;
Packit Service 310c69
  allocatingVIO->allocation     = ZERO_BLOCK;
Packit Service 310c69
Packit Service 310c69
  ASSERT_LOG_ONLY(isPBNReadLock(pbnLock),
Packit Service 310c69
                  "must have downgraded the allocation lock before transfer");
Packit Service 310c69
Packit Service 310c69
  HashLock *hashLock  = dataVIO->hashLock;
Packit Service 310c69
  hashLock->duplicate = dataVIO->newMapped;
Packit Service 310c69
  dataVIO->duplicate  = dataVIO->newMapped;
Packit Service 310c69
Packit Service 310c69
  // Since the lock is being transferred, the holder count doesn't change (and
Packit Service 310c69
  // isn't even safe to examine on this thread).
Packit Service 310c69
  hashLock->duplicateLock = pbnLock;
Packit Service 310c69
}
Packit Service 310c69
Packit Service 310c69
/**********************************************************************/
Packit Service 310c69
void shareCompressedWriteLock(DataVIO *dataVIO, PBNLock *pbnLock)
Packit Service 310c69
{
Packit Service 310c69
  ASSERT_LOG_ONLY(getDuplicateLock(dataVIO) == NULL,
Packit Service 310c69
                  "a duplicate PBN lock should not exist when writing");
Packit Service 310c69
  ASSERT_LOG_ONLY(isCompressed(dataVIO->newMapped.state),
Packit Service 310c69
                  "lock transfer must be for a compressed write");
Packit Service 310c69
  assertInNewMappedZone(dataVIO);
Packit Service 310c69
Packit Service 310c69
  // First sharer downgrades the lock.
Packit Service 310c69
  if (!isPBNReadLock(pbnLock)) {
Packit Service 310c69
    downgradePBNWriteLock(pbnLock);
Packit Service 310c69
  }
Packit Service 310c69
Packit Service 310c69
  // Get a share of the PBN lock, ensuring it cannot be released until
Packit Service 310c69
  // after this DataVIO has had a chance to journal a reference.
Packit Service 310c69
  dataVIO->duplicate = dataVIO->newMapped;
Packit Service 310c69
  dataVIO->hashLock->duplicate = dataVIO->newMapped;
Packit Service 310c69
  setDuplicateLock(dataVIO->hashLock, pbnLock);
Packit Service 310c69
Packit Service 310c69
  // Claim a reference for this DataVIO, which is necessary since another
Packit Service 310c69
  // HashLock might start deduplicating against it before our incRef.
Packit Service 310c69
  bool claimed = claimPBNLockIncrement(pbnLock);
Packit Service 310c69
  ASSERT_LOG_ONLY(claimed, "impossible to fail to claim an initial increment");
Packit Service 310c69
}