/*
* Copyright (C) 2012 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#pragma once
#include "mongo/base/disallow_copying.h"
#include "mongo/db/jsobj.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/stdx/mutex.h"
namespace mongo {
/**
* A simple thread-safe fail point implementation that can be activated and
* deactivated, as well as embed temporary data into it.
*
* The fail point has a static instance, which is represented by a FailPoint
* object, and dynamic instance, which are all the threads in between
* shouldFailOpenBlock and shouldFailCloseBlock.
*
* Sample use:
* // Declared somewhere:
* FailPoint makeBadThingsHappen;
*
* // Somewhere in the code
* return false || MONGO_FAIL_POINT(makeBadThingsHappen);
*
* or
*
* // Somewhere in the code
* MONGO_FAIL_POINT_BLOCK(makeBadThingsHappen, blockMakeBadThingsHappen) {
* const BSONObj& data = blockMakeBadThingsHappen.getData();
* // Do something
* }
*
* Invariants:
*
* 1. Always refer to _fpInfo first to check if failPoint is active or not before
* entering fail point or modifying fail point.
* 2. Client visible fail point states are read-only when active.
*/
class FailPoint {
MONGO_DISALLOW_COPYING(FailPoint);
public:
typedef AtomicUInt32::WordType ValType;
enum Mode { off, alwaysOn, random, nTimes, numModes };
enum RetCode { fastOff = 0, slowOff, slowOn };
/**
* Explicitly resets the seed used for the PRNG in this thread. If not called on a thread,
* an instance of SecureRandom is used to seed the PRNG.
*/
static void setThreadPRNGSeed(int32_t seed);
FailPoint();
/**
* Note: This is not side-effect free - it can change the state to OFF after calling.
*
* @return true if fail point is active.
*/
inline bool shouldFail() {
RetCode ret = shouldFailOpenBlock();
if (MONGO_likely(ret == fastOff)) {
return false;
}
shouldFailCloseBlock();
return ret == slowOn;
}
/**
* Checks whether fail point is active and increments the reference counter without
* decrementing it. Must call shouldFailCloseBlock afterwards when the return value
* is not fastOff. Otherwise, this will remain read-only forever.
*
* @return slowOn if fail point is active.
*/
inline RetCode shouldFailOpenBlock() {
if (MONGO_likely((_fpInfo.loadRelaxed() & ACTIVE_BIT) == 0)) {
return fastOff;
}
return slowShouldFailOpenBlock();
}
/**
* Decrements the reference counter.
* @see #shouldFailOpenBlock
*/
void shouldFailCloseBlock();
/**
* Changes the settings of this fail point. This will turn off the fail point
* and waits for all dynamic instances referencing this fail point to go away before
* actually modifying the settings.
*
* @param mode the new mode for this fail point.
* @param val the value that can have different usage depending on the mode:
*
* - off, alwaysOn: ignored
* - random: static_cast(std::numeric_limits::max() * p), where
* where p is the probability that any given evaluation of the failpoint should
* activate.
* - nTimes: the number of times this fail point will be active when
* #shouldFail or #shouldFailOpenBlock is called.
*
* @param extra arbitrary BSON object that can be stored to this fail point
* that can be referenced afterwards with #getData. Defaults to an empty
* document.
*/
void setMode(Mode mode, ValType val = 0, const BSONObj& extra = BSONObj());
/**
* @returns a BSON object showing the current mode and data stored.
*/
BSONObj toBSON() const;
private:
static const ValType ACTIVE_BIT = 1 << 31;
static const ValType REF_COUNTER_MASK = ~ACTIVE_BIT;
// Bit layout:
// 31: tells whether this fail point is active.
// 0~30: unsigned ref counter for active dynamic instances.
AtomicUInt32 _fpInfo;
// Invariant: These should be read only if ACTIVE_BIT of _fpInfo is set.
Mode _mode;
AtomicInt32 _timesOrPeriod;
BSONObj _data;
// protects _mode, _timesOrPeriod, _data
mutable stdx::mutex _modMutex;
/**
* Enables this fail point.
*/
void enableFailPoint();
/**
* Disables this fail point.
*/
void disableFailPoint();
/**
* slow path for #shouldFailOpenBlock
*/
RetCode slowShouldFailOpenBlock();
/**
* @return the stored BSONObj in this fail point. Note that this cannot be safely
* read if this fail point is off.
*/
const BSONObj& getData() const;
friend class ScopedFailPoint;
};
/**
* Helper class for making sure that FailPoint#shouldFailCloseBlock is called when
* FailPoint#shouldFailOpenBlock was called. This should only be used within the
* MONGO_FAIL_POINT_BLOCK macro.
*/
class ScopedFailPoint {
MONGO_DISALLOW_COPYING(ScopedFailPoint);
public:
ScopedFailPoint(FailPoint* failPoint);
~ScopedFailPoint();
/**
* @return true if fail point is on. This will be true at most once.
*/
inline bool isActive() {
if (_once) {
return false;
}
_once = true;
FailPoint::RetCode ret = _failPoint->shouldFailOpenBlock();
_shouldClose = ret != FailPoint::fastOff;
return ret == FailPoint::slowOn;
}
/**
* @return the data stored in the fail point. #isActive must be true
* before you can call this.
*/
const BSONObj& getData() const;
private:
FailPoint* _failPoint;
bool _once;
bool _shouldClose;
};
#define MONGO_FAIL_POINT(symbol) MONGO_unlikely(symbol.shouldFail())
#define MONGO_FAIL_POINT_PAUSE_WHILE_SET(symbol) \
while (MONGO_FAIL_POINT(symbol)) { \
sleepmillis(100); \
}
/**
* Macro for creating a fail point with block context. Also use this when
* you want to access the data stored in the fail point.
*/
#define MONGO_FAIL_POINT_BLOCK(symbol, blockSymbol) \
for (mongo::ScopedFailPoint blockSymbol(&symbol); MONGO_unlikely(blockSymbol.isActive());)
}