Blame source/vdo/base/readOnlyNotifier.c

Packit Service d40955
/*
Packit Service d40955
 * Copyright (c) 2020 Red Hat, Inc.
Packit Service d40955
 *
Packit Service d40955
 * This program is free software; you can redistribute it and/or
Packit Service d40955
 * modify it under the terms of the GNU General Public License
Packit Service d40955
 * as published by the Free Software Foundation; either version 2
Packit Service d40955
 * of the License, or (at your option) any later version.
Packit Service d40955
 * 
Packit Service d40955
 * This program is distributed in the hope that it will be useful,
Packit Service d40955
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service d40955
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service d40955
 * GNU General Public License for more details.
Packit Service d40955
 * 
Packit Service d40955
 * You should have received a copy of the GNU General Public License
Packit Service d40955
 * along with this program; if not, write to the Free Software
Packit Service d40955
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit Service d40955
 * 02110-1301, USA. 
Packit Service d40955
 *
Packit Service d40955
 * $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/readOnlyNotifier.c#5 $
Packit Service d40955
 */
Packit Service d40955
Packit Service d40955
#include "readOnlyNotifier.h"
Packit Service d40955
Packit Service d40955
#include "atomic.h"
Packit Service d40955
#include "logger.h"
Packit Service d40955
#include "memoryAlloc.h"
Packit Service d40955
#include "permassert.h"
Packit Service d40955
Packit Service d40955
#include "completion.h"
Packit Service d40955
#include "physicalLayer.h"
Packit Service d40955
#include "threadConfig.h"
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * A ReadOnlyNotifier has a single completion which is used to perform
Packit Service d40955
 * read-only notifications, however, enterReadOnlyMode() may be called from any
Packit Service d40955
 * base thread. A pair of atomic fields are used to control the read-only mode
Packit Service d40955
 * entry process. The first field holds the read-only error. The second is the
Packit Service d40955
 * state field, which may hold any of the four special values enumerated here.
Packit Service d40955
 *
Packit Service d40955
 * When enterReadOnlyMode() is called from some base thread, a compare-and-swap
Packit Service d40955
 * is done on the readOnlyError, setting it to the supplied error if the value
Packit Service d40955
 * was VDO_SUCCESS. If this fails, some other thread has already intiated
Packit Service d40955
 * read-only entry or scheduled a pending entry, so the call exits. Otherwise,
Packit Service d40955
 * a compare-and-swap is done on the state, setting it to NOTIFYING if the
Packit Service d40955
 * value was MAY_NOTIFY. If this succeeds, the caller initiates the
Packit Service d40955
 * notification. If this failed due to notifications being disallowed, the
Packit Service d40955
 * notifier will be in the MAY_NOT_NOTIFY state but readOnlyError will not be
Packit Service d40955
 * VDO_SUCCESS. This configuration will indicate to allowReadOnlyModeEntry()
Packit Service d40955
 * that there is a pending notification to perform.
Packit Service d40955
 **/
Packit Service d40955
enum {
Packit Service d40955
  /** Notifications are allowed but not in progress */
Packit Service d40955
  MAY_NOTIFY = 0,
Packit Service d40955
  /** A notification is in progress */
Packit Service d40955
  NOTIFYING,
Packit Service d40955
  /** Notifications are not allowed */
Packit Service d40955
  MAY_NOT_NOTIFY,
Packit Service d40955
  /** A notification has completed */
Packit Service d40955
  NOTIFIED,
Packit Service d40955
};
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * An object to be notified when the VDO enters read-only mode
Packit Service d40955
 **/
Packit Service d40955
typedef struct readOnlyListener ReadOnlyListener;
Packit Service d40955
Packit Service d40955
struct readOnlyListener {
Packit Service d40955
  /** The listener */
Packit Service d40955
  void                 *listener;
Packit Service d40955
  /** The method to call to notifiy the listener */
Packit Service d40955
  ReadOnlyNotification *notify;
Packit Service d40955
  /** A pointer to the next listener */
Packit Service d40955
  ReadOnlyListener     *next;
Packit Service d40955
};
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * Data associated with each base code thread.
Packit Service d40955
 **/
Packit Service d40955
typedef struct threadData {
Packit Service d40955
  /**
Packit Service d40955
   * Each thread maintains its own notion of whether the VDO is read-only so
Packit Service d40955
   * that the read-only state can be checked from any base thread without
Packit Service d40955
   * worrying about synchronization or thread safety. This does mean that
Packit Service d40955
   * knowledge of the VDO going read-only does not occur simultaneously across
Packit Service d40955
   * the VDO's threads, but that does not seem to cause any problems.
Packit Service d40955
   */
Packit Service d40955
  bool              isReadOnly;
Packit Service d40955
  /**
Packit Service d40955
   * A list of objects waiting to be notified on this thread that the VDO has
Packit Service d40955
   * entered read-only mode.
Packit Service d40955
   **/
Packit Service d40955
  ReadOnlyListener *listeners;
Packit Service d40955
} ThreadData;
Packit Service d40955
Packit Service d40955
struct readOnlyNotifier {
Packit Service d40955
  /** The completion for entering read-only mode */
Packit Service d40955
  VDOCompletion       completion;
Packit Service d40955
  /** A completion waiting for notifications to be drained or enabled */
Packit Service d40955
  VDOCompletion      *waiter;
Packit Service d40955
  /** The code of the error which put the VDO into read-only mode */
Packit Service d40955
  Atomic32            readOnlyError;
Packit Service d40955
  /** The current state of the notifier (values described above) */
Packit Service d40955
  Atomic32            state;
Packit Service d40955
  /** The thread config of the VDO */
Packit Service d40955
  const ThreadConfig *threadConfig;
Packit Service d40955
  /** The array of per-thread data */
Packit Service d40955
  ThreadData          threadData[];
Packit Service d40955
};
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * Convert a generic VDOCompletion to a ReadOnlyNotifier.
Packit Service d40955
 *
Packit Service d40955
 * @param completion The completion to convert
Packit Service d40955
 *
Packit Service d40955
 * @return The completion as a ReadOnlyNotifier
Packit Service d40955
 **/
Packit Service d40955
static inline ReadOnlyNotifier *asNotifier(VDOCompletion *completion)
Packit Service d40955
{
Packit Service d40955
  STATIC_ASSERT(offsetof(ReadOnlyNotifier, completion) == 0);
Packit Service d40955
  assertCompletionType(completion->type, READ_ONLY_MODE_COMPLETION);
Packit Service d40955
  return (ReadOnlyNotifier *) completion;
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
int makeReadOnlyNotifier(bool                 isReadOnly,
Packit Service d40955
                         const ThreadConfig  *threadConfig,
Packit Service d40955
                         PhysicalLayer       *layer,
Packit Service d40955
                         ReadOnlyNotifier   **notifierPtr)
Packit Service d40955
{
Packit Service d40955
  ReadOnlyNotifier *notifier;
Packit Service d40955
  int result = ALLOCATE_EXTENDED(ReadOnlyNotifier,
Packit Service d40955
                                 threadConfig->baseThreadCount, ThreadData,
Packit Service d40955
                                 __func__, &notifier);
Packit Service d40955
  if (result != VDO_SUCCESS) {
Packit Service d40955
    return result;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  notifier->threadConfig = threadConfig;
Packit Service d40955
  if (isReadOnly) {
Packit Service d40955
    atomicStore32(&notifier->readOnlyError, (uint32_t) VDO_READ_ONLY);
Packit Service d40955
    atomicStore32(&notifier->state, NOTIFIED);
Packit Service d40955
  } else {
Packit Service d40955
    atomicStore32(&notifier->state, MAY_NOTIFY);
Packit Service d40955
  }
Packit Service d40955
  result = initializeEnqueueableCompletion(&notifier->completion,
Packit Service d40955
                                           READ_ONLY_MODE_COMPLETION, layer);
Packit Service d40955
  if (result != VDO_SUCCESS) {
Packit Service d40955
    freeReadOnlyNotifier(&notifier);
Packit Service d40955
    return result;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  for (ThreadCount id = 0; id < threadConfig->baseThreadCount; id++) {
Packit Service d40955
    notifier->threadData[id].isReadOnly = isReadOnly;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  *notifierPtr = notifier;
Packit Service d40955
  return VDO_SUCCESS;
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
void freeReadOnlyNotifier(ReadOnlyNotifier **notifierPtr)
Packit Service d40955
{
Packit Service d40955
  ReadOnlyNotifier *notifier = *notifierPtr;
Packit Service d40955
  if (notifier == NULL) {
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  for (ThreadCount id = 0; id < notifier->threadConfig->baseThreadCount;
Packit Service d40955
       id++) {
Packit Service d40955
    ThreadData       *threadData = &notifier->threadData[id];
Packit Service d40955
    ReadOnlyListener *listener   = threadData->listeners;
Packit Service d40955
    while (listener != NULL) {
Packit Service d40955
      ReadOnlyListener *toFree = listener;
Packit Service d40955
      listener = listener->next;
Packit Service d40955
      FREE(toFree);
Packit Service d40955
    }
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  destroyEnqueueable(&notifier->completion);
Packit Service d40955
  FREE(notifier);
Packit Service d40955
  *notifierPtr = NULL;
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * Check that a function was called on the admin thread.
Packit Service d40955
 *
Packit Service d40955
 * @param notifier  The notifier
Packit Service d40955
 * @param caller    The name of the function (for logging)
Packit Service d40955
 **/
Packit Service d40955
static void assertOnAdminThread(ReadOnlyNotifier *notifier, const char *caller)
Packit Service d40955
{
Packit Service d40955
  ThreadID threadID = getCallbackThreadID();
Packit Service d40955
  ASSERT_LOG_ONLY((getAdminThread(notifier->threadConfig) == threadID),
Packit Service d40955
                  "%s called on admin thread", caller);
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
void waitUntilNotEnteringReadOnlyMode(ReadOnlyNotifier *notifier,
Packit Service d40955
                                      VDOCompletion    *parent)
Packit Service d40955
{
Packit Service d40955
  if (notifier == NULL) {
Packit Service d40955
    finishCompletion(parent, VDO_SUCCESS);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  assertOnAdminThread(notifier, __func__);
Packit Service d40955
  if (notifier->waiter != NULL) {
Packit Service d40955
    finishCompletion(parent, VDO_COMPONENT_BUSY);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  uint32_t state = atomicLoad32(&notifier->state);
Packit Service d40955
  if ((state == MAY_NOT_NOTIFY) || (state == NOTIFIED)) {
Packit Service d40955
    // Notifications are already done or disallowed.
Packit Service d40955
    completeCompletion(parent);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  if (compareAndSwap32(&notifier->state, MAY_NOTIFY, MAY_NOT_NOTIFY)) {
Packit Service d40955
    // A notification was not in progress, and now they are disallowed.
Packit Service d40955
    completeCompletion(parent);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  /*
Packit Service d40955
   * A notification is in progress, so wait for it to finish. There is no race
Packit Service d40955
   * here since the notification can't finish while the admin thread is in this
Packit Service d40955
   * method.
Packit Service d40955
   */
Packit Service d40955
  notifier->waiter = parent;
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * Complete the process of entering read only mode.
Packit Service d40955
 *
Packit Service d40955
 * @param completion  The read-only mode completion
Packit Service d40955
 **/
Packit Service d40955
static void finishEnteringReadOnlyMode(VDOCompletion *completion)
Packit Service d40955
{
Packit Service d40955
  ReadOnlyNotifier *notifier = asNotifier(completion);
Packit Service d40955
  assertOnAdminThread(notifier, __func__);
Packit Service d40955
  atomicStore32(&notifier->state, NOTIFIED);
Packit Service d40955
Packit Service d40955
  VDOCompletion *waiter = notifier->waiter;
Packit Service d40955
  if (waiter != NULL) {
Packit Service d40955
    notifier->waiter = NULL;
Packit Service d40955
    finishCompletion(waiter, completion->result);
Packit Service d40955
  }
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**
Packit Service d40955
 * Inform each thread that the VDO is in read-only mode.
Packit Service d40955
 *
Packit Service d40955
 * @param completion  The read-only mode completion
Packit Service d40955
 **/
Packit Service d40955
static void makeThreadReadOnly(VDOCompletion *completion)
Packit Service d40955
{
Packit Service d40955
  ThreadID          threadID   = completion->callbackThreadID;
Packit Service d40955
  ReadOnlyNotifier *notifier   = asNotifier(completion);
Packit Service d40955
  ReadOnlyListener *listener   = completion->parent;
Packit Service d40955
  if (listener == NULL) {
Packit Service d40955
    // This is the first call on this thread
Packit Service d40955
    ThreadData *threadData = &notifier->threadData[threadID];
Packit Service d40955
    threadData->isReadOnly = true;
Packit Service d40955
    listener               = threadData->listeners;
Packit Service d40955
    if (threadID == 0) {
Packit Service d40955
      // Note: This message must be recognizable by Permabit::UserMachine.
Packit Service d40955
      logErrorWithStringError((int) atomicLoad32(&notifier->readOnlyError),
Packit Service d40955
                              "Unrecoverable error, entering read-only mode");
Packit Service d40955
    }
Packit Service d40955
  } else {
Packit Service d40955
    // We've just finished notifying a listener
Packit Service d40955
    listener = listener->next;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  if (listener != NULL) {
Packit Service d40955
    // We have a listener to notify
Packit Service d40955
    prepareCompletion(completion, makeThreadReadOnly, makeThreadReadOnly,
Packit Service d40955
                      threadID, listener);
Packit Service d40955
    listener->notify(listener->listener, completion);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  // We're done with this thread
Packit Service d40955
  if (++threadID >= notifier->threadConfig->baseThreadCount) {
Packit Service d40955
    // There are no more threads
Packit Service d40955
    prepareCompletion(completion, finishEnteringReadOnlyMode,
Packit Service d40955
                      finishEnteringReadOnlyMode,
Packit Service d40955
                      getAdminThread(notifier->threadConfig), NULL);
Packit Service d40955
  } else {
Packit Service d40955
    prepareCompletion(completion, makeThreadReadOnly, makeThreadReadOnly,
Packit Service d40955
                      threadID, NULL);
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  invokeCallback(completion);
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
void allowReadOnlyModeEntry(ReadOnlyNotifier *notifier, VDOCompletion *parent)
Packit Service d40955
{
Packit Service d40955
  assertOnAdminThread(notifier, __func__);
Packit Service d40955
  if (notifier->waiter != NULL) {
Packit Service d40955
    finishCompletion(parent, VDO_COMPONENT_BUSY);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
   if (!compareAndSwap32(&notifier->state, MAY_NOT_NOTIFY, MAY_NOTIFY)) {
Packit Service d40955
    // Notifications were already allowed or complete
Packit Service d40955
    completeCompletion(parent);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  if ((int) atomicLoad32(&notifier->readOnlyError) == VDO_SUCCESS) {
Packit Service d40955
    // We're done
Packit Service d40955
    completeCompletion(parent);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  // There may have been a pending notification
Packit Service d40955
  if (!compareAndSwap32(&notifier->state, MAY_NOTIFY, NOTIFYING)) {
Packit Service d40955
    /*
Packit Service d40955
     * There wasn't, the error check raced with a thread calling
Packit Service d40955
     * enterReadOnlyMode() after we set the state to MAY_NOTIFY. It has already
Packit Service d40955
     * started the notification.
Packit Service d40955
     */
Packit Service d40955
    completeCompletion(parent);
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  // Do the pending notification.
Packit Service d40955
  notifier->waiter = parent;
Packit Service d40955
  makeThreadReadOnly(&notifier->completion);
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
void enterReadOnlyMode(ReadOnlyNotifier *notifier, int errorCode)
Packit Service d40955
{
Packit Service d40955
  ThreadData *threadData = &notifier->threadData[getCallbackThreadID()];
Packit Service d40955
  if (threadData->isReadOnly) {
Packit Service d40955
    // This thread has already gone read-only.
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  // Record for this thread that the VDO is read-only.
Packit Service d40955
  threadData->isReadOnly = true;
Packit Service d40955
Packit Service d40955
  if (!compareAndSwap32(&notifier->readOnlyError, (uint32_t) VDO_SUCCESS,
Packit Service d40955
                        (uint32_t) errorCode)) {
Packit Service d40955
    // The notifier is already aware of a read-only error
Packit Service d40955
    return;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  if (compareAndSwap32(&notifier->state, MAY_NOTIFY, NOTIFYING)) {
Packit Service d40955
    // Initiate a notification starting on the lowest numbered thread.
Packit Service d40955
    launchCallback(&notifier->completion, makeThreadReadOnly, 0);
Packit Service d40955
  }
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
bool isReadOnly(ReadOnlyNotifier *notifier)
Packit Service d40955
{
Packit Service d40955
  return notifier->threadData[getCallbackThreadID()].isReadOnly;
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
bool isOrWillBeReadOnly(ReadOnlyNotifier *notifier)
Packit Service d40955
{
Packit Service d40955
  return (((int) relaxedLoad32(&notifier->readOnlyError)) != VDO_SUCCESS);
Packit Service d40955
}
Packit Service d40955
Packit Service d40955
/**********************************************************************/
Packit Service d40955
int registerReadOnlyListener(ReadOnlyNotifier     *notifier,
Packit Service d40955
                             void                 *listener,
Packit Service d40955
                             ReadOnlyNotification *notification,
Packit Service d40955
                             ThreadID              threadID)
Packit Service d40955
{
Packit Service d40955
  ReadOnlyListener *readOnlyListener;
Packit Service d40955
  int result = ALLOCATE(1, ReadOnlyListener, __func__, &readOnlyListener);
Packit Service d40955
  if (result != VDO_SUCCESS) {
Packit Service d40955
    return result;
Packit Service d40955
  }
Packit Service d40955
Packit Service d40955
  ThreadData *threadData = &notifier->threadData[threadID];
Packit Service d40955
  *readOnlyListener = (ReadOnlyListener) {
Packit Service d40955
    .listener = listener,
Packit Service d40955
    .notify   = notification,
Packit Service d40955
    .next     = threadData->listeners,
Packit Service d40955
  };
Packit Service d40955
Packit Service d40955
  threadData->listeners = readOnlyListener;
Packit Service d40955
  return VDO_SUCCESS;
Packit Service d40955
}