const std::string WATCHER_LOCK_COOKIE_PREFIX = "auto";
+template <typename I>
+struct C_SendReleaseRequest : public Context {
+ ReleaseRequest<I>* request;
+ C_SendReleaseRequest(ReleaseRequest<I>* request) : request(request) {
+ }
+ virtual void finish(int r) override {
+ request->send();
+ }
+};
+
} // anonymous namespace
template <typename I>
using el = ExclusiveLock<I>;
AcquireRequest<I>* req = AcquireRequest<I>::create(
m_image_ctx, encode_lock_cookie(),
+ util::create_context_callback<el, &el::handle_acquiring_lock>(this),
util::create_context_callback<el, &el::handle_acquire_lock>(this));
m_lock.Unlock();
m_lock.Lock();
}
+template <typename I>
+void ExclusiveLock<I>::handle_acquiring_lock(int r) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << this << " " << __func__ << dendl;
+
+ assert(r == 0);
+ assert(m_state == STATE_ACQUIRING);
+
+ // lock is owned at this point
+ m_state = STATE_POST_ACQUIRING;
+}
+
template <typename I>
void ExclusiveLock<I>::handle_acquire_lock(int r) {
CephContext *cct = m_image_ctx.cct;
{
m_lock.Lock();
+ assert(m_state == STATE_ACQUIRING ||
+ m_state == STATE_POST_ACQUIRING);
+
Action action = get_active_action();
assert(action == ACTION_TRY_LOCK || action == ACTION_REQUEST_LOCK);
if (action == ACTION_REQUEST_LOCK && r < 0 && r != -EBLACKLISTED) {
}
if (next_state == STATE_LOCKED) {
- {
- Mutex::Locker locker(m_lock);
- m_state = STATE_POST_ACQUIRING;
- }
m_image_ctx.image_watcher->notify_acquired_lock();
m_image_ctx.aio_work_queue->unblock_writes();
}
ldout(m_image_ctx.cct, 10) << this << " " << __func__ << dendl;
m_state = STATE_PRE_RELEASING;
- m_image_ctx.op_work_queue->queue(
- new C_BlockWrites(m_image_ctx, new C_ReleaseBlockWrites(this)), 0);
+ using el = ExclusiveLock<I>;
+ ReleaseRequest<I>* req = ReleaseRequest<I>::create(
+ m_image_ctx, encode_lock_cookie(),
+ util::create_context_callback<el, &el::handle_releasing_lock>(this),
+ util::create_context_callback<el, &el::handle_release_lock>(this));
+
+ // send in alternate thread context to avoid re-entrant locking
+ m_image_ctx.op_work_queue->queue(new C_SendReleaseRequest<I>(req), 0);
}
template <typename I>
-void ExclusiveLock<I>::handle_release_blocked_writes(int r) {
- if (r < 0) {
- handle_release_lock(r);
- return;
- }
-
- ldout(m_image_ctx.cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
-
+void ExclusiveLock<I>::handle_releasing_lock(int r) {
Mutex::Locker locker(m_lock);
- assert(m_state == STATE_PRE_RELEASING);
- m_state = STATE_RELEASING;
+ ldout(m_image_ctx.cct, 10) << this << " " << __func__ << dendl;
- using el = ExclusiveLock<I>;
- ReleaseRequest<I>* req = ReleaseRequest<I>::create(
- m_image_ctx, encode_lock_cookie(),
- util::create_context_callback<el, &el::handle_release_lock>(this));
+ assert(r == 0);
+ assert(m_state == STATE_PRE_RELEASING);
- m_lock.Unlock();
- req->send();
- m_lock.Lock();
+ // all IO and ops should be blocked/canceled by this point
+ m_state = STATE_RELEASING;
}
template <typename I>
ldout(m_image_ctx.cct, 10) << this << " " << __func__ << ": r=" << r
<< dendl;
- assert(m_state == STATE_RELEASING);
- if (r < 0) {
- m_image_ctx.aio_work_queue->unblock_writes();
- } else {
+ assert(m_state == STATE_PRE_RELEASING ||
+ m_state == STATE_RELEASING);
+ if (r >= 0) {
m_lock.Unlock();
m_image_ctx.image_watcher->notify_released_lock();
pending_writes = !m_image_ctx.aio_work_queue->writes_empty();
using el = ExclusiveLock<I>;
ReleaseRequest<I>* req = ReleaseRequest<I>::create(
- m_image_ctx, cookie,
+ m_image_ctx, cookie, nullptr,
util::create_context_callback<el, &el::handle_shutdown>(this));
req->send();
}
if (r < 0) {
lderr(cct) << "failed to shut down exclusive lock: " << cpp_strerror(r)
<< dendl;
+ } else {
+ m_image_ctx.aio_work_queue->unblock_writes();
}
m_image_ctx.image_watcher->notify_released_lock();
}
};
- struct C_BlockWrites : public Context {
- ImageCtxT &image_ctx;
- Context *on_finish;
- C_BlockWrites(ImageCtxT &image_ctx, Context *on_finish)
- : image_ctx(image_ctx), on_finish(on_finish) {
- }
- virtual void finish(int r) override {
- RWLock::RLocker owner_locker(image_ctx.owner_lock);
- image_ctx.aio_work_queue->block_writes(on_finish);
- }
- };
-
- struct C_ReleaseBlockWrites : public Context {
- ExclusiveLock *exclusive_lock;
- C_ReleaseBlockWrites(ExclusiveLock *exclusive_lock)
- : exclusive_lock(exclusive_lock) {
- }
- virtual void finish(int r) override {
- exclusive_lock->handle_release_blocked_writes(r);
- }
- };
-
struct C_ShutDownRelease : public Context {
ExclusiveLock *exclusive_lock;
C_ShutDownRelease(ExclusiveLock *exclusive_lock)
void handle_init_complete();
void send_acquire_lock();
+ void handle_acquiring_lock(int r);
void handle_acquire_lock(int r);
void send_release_lock();
- void handle_release_blocked_writes(int r);
+ void handle_releasing_lock(int r);
void handle_release_lock(int r);
void send_shutdown();
template <typename I>
AcquireRequest<I>* AcquireRequest<I>::create(I &image_ctx,
const std::string &cookie,
+ Context *on_acquire,
Context *on_finish) {
- return new AcquireRequest(image_ctx, cookie, on_finish);
+ return new AcquireRequest(image_ctx, cookie, on_acquire, on_finish);
}
template <typename I>
AcquireRequest<I>::AcquireRequest(I &image_ctx, const std::string &cookie,
- Context *on_finish)
- : m_image_ctx(image_ctx), m_cookie(cookie),
+ Context *on_acquire, Context *on_finish)
+ : m_image_ctx(image_ctx), m_cookie(cookie), m_on_acquire(on_acquire),
m_on_finish(create_async_context_callback(image_ctx, on_finish)),
m_object_map(nullptr), m_journal(nullptr), m_error_result(0) {
}
+template <typename I>
+AcquireRequest<I>::~AcquireRequest() {
+ delete m_on_acquire;
+}
+
template <typename I>
void AcquireRequest<I>::send() {
send_lock();
template <typename I>
Context *AcquireRequest<I>::send_open_journal() {
+ // alert caller that we now own the exclusive lock
+ m_on_acquire->complete(0);
+ m_on_acquire = nullptr;
+
if (!m_image_ctx.test_features(RBD_FEATURE_JOURNALING)) {
apply();
return m_on_finish;
class AcquireRequest {
public:
static AcquireRequest* create(ImageCtxT &image_ctx, const std::string &cookie,
- Context *on_finish);
+ Context *on_acquire, Context *on_finish);
+ ~AcquireRequest();
void send();
private:
*/
AcquireRequest(ImageCtxT &image_ctx, const std::string &cookie,
- Context *on_finish);
+ Context *on_acquire, Context *on_finish);
ImageCtxT &m_image_ctx;
std::string m_cookie;
+ Context *m_on_acquire;
Context *m_on_finish;
bufferlist m_out_bl;
#include "common/errno.h"
#include "common/WorkQueue.h"
#include "include/stringify.h"
+#include "librbd/AioImageRequestWQ.h"
#include "librbd/ExclusiveLock.h"
#include "librbd/ImageCtx.h"
#include "librbd/Journal.h"
template <typename I>
ReleaseRequest<I>* ReleaseRequest<I>::create(I &image_ctx,
const std::string &cookie,
+ Context *on_releasing,
Context *on_finish) {
- return new ReleaseRequest(image_ctx, cookie, on_finish);
+ return new ReleaseRequest(image_ctx, cookie, on_releasing, on_finish);
}
template <typename I>
ReleaseRequest<I>::ReleaseRequest(I &image_ctx, const std::string &cookie,
- Context *on_finish)
- : m_image_ctx(image_ctx), m_cookie(cookie),
+ Context *on_releasing, Context *on_finish)
+ : m_image_ctx(image_ctx), m_cookie(cookie), m_on_releasing(on_releasing),
m_on_finish(create_async_context_callback(image_ctx, on_finish)),
m_object_map(nullptr), m_journal(nullptr) {
}
+template <typename I>
+ReleaseRequest<I>::~ReleaseRequest() {
+ delete m_on_releasing;
+}
+
template <typename I>
void ReleaseRequest<I>::send() {
+ send_block_writes();
+}
+
+template <typename I>
+void ReleaseRequest<I>::send_block_writes() {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << dendl;
+
+ using klass = ReleaseRequest<I>;
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_block_writes>(this);
+
+ {
+ RWLock::RLocker owner_locker(m_image_ctx.owner_lock);
+ m_image_ctx.aio_work_queue->block_writes(ctx);
+ }
+}
+
+template <typename I>
+Context *ReleaseRequest<I>::handle_block_writes(int *ret_val) {
+ CephContext *cct = m_image_ctx.cct;
+ ldout(cct, 10) << __func__ << ": r=" << *ret_val << dendl;
+
+ if (*ret_val < 0) {
+ m_image_ctx.aio_work_queue->unblock_writes();
+ return m_on_finish;
+ }
+
send_cancel_op_requests();
+ return nullptr;
}
template <typename I>
ldout(cct, 10) << __func__ << dendl;
using klass = ReleaseRequest<I>;
- Context *ctx = create_context_callback<klass,
- &klass::handle_cancel_op_requests>(this);
+ Context *ctx = create_context_callback<
+ klass, &klass::handle_cancel_op_requests>(this);
m_image_ctx.cancel_async_requests(ctx);
}
ldout(cct, 10) << __func__ << ": r=" << *ret_val << dendl;
assert(*ret_val == 0);
+
+ if (m_on_releasing != nullptr) {
+ // alert caller that we no longer own the exclusive lock
+ m_on_releasing->complete(0);
+ m_on_releasing = nullptr;
+ }
+
send_close_journal();
return nullptr;
}
class ReleaseRequest {
public:
static ReleaseRequest* create(ImageCtxT &image_ctx, const std::string &cookie,
- Context *on_finish);
+ Context *on_releasing, Context *on_finish);
+ ~ReleaseRequest();
void send();
private:
* <start>
* |
* v
+ * BLOCK_WRITES
+ * |
+ * v
* CANCEL_OP_REQUESTS . . . . . . . . . . . .
* | .
* v .
*/
ReleaseRequest(ImageCtxT &image_ctx, const std::string &cookie,
- Context *on_finish);
+ Context *on_releasing, Context *on_finish);
ImageCtxT &m_image_ctx;
std::string m_cookie;
+ Context *m_on_releasing;
Context *m_on_finish;
decltype(m_image_ctx.object_map) m_object_map;
decltype(m_image_ctx.journal) m_journal;
+ void send_block_writes();
+ Context *handle_block_writes(int *ret_val);
+
void send_cancel_op_requests();
Context *handle_cancel_op_requests(int *ret_val);
expect_create_journal(mock_image_ctx, &mock_journal);
expect_open_journal(mock_image_ctx, mock_journal, 0);
+ C_SaferCond acquire_ctx;
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &acquire_ctx, &ctx);
req->send();
+ ASSERT_EQ(0, acquire_ctx.wait());
ASSERT_EQ(0, ctx.wait());
}
expect_test_features(mock_image_ctx, RBD_FEATURE_JOURNALING, false);
+ C_SaferCond acquire_ctx;
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &acquire_ctx, &ctx);
req->send();
+ ASSERT_EQ(0, acquire_ctx.wait());
ASSERT_EQ(0, ctx.wait());
}
expect_create_journal(mock_image_ctx, &mock_journal);
expect_open_journal(mock_image_ctx, mock_journal, 0);
+ C_SaferCond acquire_ctx;
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &acquire_ctx, &ctx);
req->send();
+ ASSERT_EQ(0, acquire_ctx.wait());
ASSERT_EQ(0, ctx.wait());
}
expect_open_journal(mock_image_ctx, *mock_journal, -EINVAL);
expect_unlock_object_map(mock_image_ctx, *mock_object_map);
+ C_SaferCond acquire_ctx;
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &acquire_ctx, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-ENOENT, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EBUSY, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EBUSY, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EBUSY, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EAGAIN, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-ENOENT, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
C_SaferCond ctx;
MockAcquireRequest *req = MockAcquireRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(-EINVAL, ctx.wait());
}
public:
typedef ReleaseRequest<MockImageCtx> MockReleaseRequest;
+ void expect_block_writes(MockImageCtx &mock_image_ctx, int r) {
+ EXPECT_CALL(*mock_image_ctx.aio_work_queue, block_writes(_))
+ .WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
+ }
+
+ void expect_unblock_writes(MockImageCtx &mock_image_ctx) {
+ EXPECT_CALL(*mock_image_ctx.aio_work_queue, unblock_writes());
+ }
+
void expect_cancel_op_requests(MockImageCtx &mock_image_ctx, int r) {
EXPECT_CALL(mock_image_ctx, cancel_async_requests(_))
.WillOnce(CompleteContext(r, mock_image_ctx.image_ctx->op_work_queue));
expect_op_work_queue(mock_image_ctx);
InSequence seq;
+ expect_block_writes(mock_image_ctx, 0);
expect_cancel_op_requests(mock_image_ctx, 0);
MockJournal *mock_journal = new MockJournal();
expect_unlock(mock_image_ctx, 0);
+ C_SaferCond release_ctx;
C_SaferCond ctx;
MockReleaseRequest *req = MockReleaseRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &release_ctx, &ctx);
req->send();
+ ASSERT_EQ(0, release_ctx.wait());
ASSERT_EQ(0, ctx.wait());
}
ASSERT_EQ(0, open_image(m_image_name, &ictx));
MockImageCtx mock_image_ctx(*ictx);
+ expect_block_writes(mock_image_ctx, 0);
expect_op_work_queue(mock_image_ctx);
InSequence seq;
expect_unlock(mock_image_ctx, 0);
+ C_SaferCond release_ctx;
C_SaferCond ctx;
MockReleaseRequest *req = MockReleaseRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &release_ctx, &ctx);
req->send();
+ ASSERT_EQ(0, release_ctx.wait());
ASSERT_EQ(0, ctx.wait());
}
ASSERT_EQ(0, open_image(m_image_name, &ictx));
MockImageCtx mock_image_ctx(*ictx);
+ expect_block_writes(mock_image_ctx, 0);
expect_op_work_queue(mock_image_ctx);
InSequence seq;
expect_unlock(mock_image_ctx, 0);
+ C_SaferCond release_ctx;
C_SaferCond ctx;
MockReleaseRequest *req = MockReleaseRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ &release_ctx, &ctx);
req->send();
+ ASSERT_EQ(0, release_ctx.wait());
ASSERT_EQ(0, ctx.wait());
}
+TEST_F(TestMockExclusiveLockReleaseRequest, BlockWritesError) {
+ REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
+
+ librbd::ImageCtx *ictx;
+ ASSERT_EQ(0, open_image(m_image_name, &ictx));
+
+ MockImageCtx mock_image_ctx(*ictx);
+ expect_op_work_queue(mock_image_ctx);
+
+ InSequence seq;
+ expect_block_writes(mock_image_ctx, -EINVAL);
+ expect_unblock_writes(mock_image_ctx);
+
+ C_SaferCond ctx;
+ MockReleaseRequest *req = MockReleaseRequest::create(mock_image_ctx,
+ TEST_COOKIE,
+ nullptr, &ctx);
+ req->send();
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
TEST_F(TestMockExclusiveLockReleaseRequest, UnlockError) {
REQUIRE_FEATURE(RBD_FEATURE_EXCLUSIVE_LOCK);
expect_op_work_queue(mock_image_ctx);
InSequence seq;
+ expect_block_writes(mock_image_ctx, 0);
expect_cancel_op_requests(mock_image_ctx, 0);
expect_unlock(mock_image_ctx, -EINVAL);
C_SaferCond ctx;
MockReleaseRequest *req = MockReleaseRequest::create(mock_image_ctx,
TEST_COOKIE,
- &ctx);
+ nullptr, &ctx);
req->send();
ASSERT_EQ(0, ctx.wait());
}
template<typename T>
struct BaseRequest {
static std::list<T *> s_requests;
+ Context *on_lock_unlock;
Context *on_finish;
static T* create(MockImageCtx &image_ctx, const std::string &cookie,
- Context *on_finish) {
+ Context *on_lock_unlock, Context *on_finish) {
assert(!s_requests.empty());
T* req = s_requests.front();
+ req->on_lock_unlock = on_lock_unlock;
req->on_finish = on_finish;
s_requests.pop_front();
return req;
.WillOnce(FinishRequest(&acquire_request, r, &mock_image_ctx));
if (r == 0) {
expect_notify_acquired_lock(mock_image_ctx);
- expect_unblock_writes(mock_image_ctx);
}
}
void expect_release_lock(MockImageCtx &mock_image_ctx,
MockReleaseRequest &release_request, int r,
bool shutting_down = false) {
- if (!shutting_down) {
- expect_block_writes(mock_image_ctx);
- }
EXPECT_CALL(release_request, send())
.WillOnce(FinishRequest(&release_request, r, &mock_image_ctx));
- if (!shutting_down && r < 0) {
- expect_unblock_writes(mock_image_ctx);
- }
if (r == 0) {
expect_notify_released_lock(mock_image_ctx);
expect_writes_empty(mock_image_ctx);
MockReleaseRequest release;
C_SaferCond wait_for_send_ctx2;
- expect_block_writes(mock_image_ctx);
EXPECT_CALL(release, send())
.WillOnce(Notify(&wait_for_send_ctx2));
expect_notify_released_lock(mock_image_ctx);