diff options
-rw-r--r-- | src/cls/lock/cls_lock.cc | 53 | ||||
-rw-r--r-- | src/cls/lock/cls_lock_client.cc | 21 | ||||
-rw-r--r-- | src/cls/lock/cls_lock_client.h | 22 | ||||
-rw-r--r-- | src/cls/lock/cls_lock_types.h | 23 | ||||
-rw-r--r-- | src/test/cls_lock/test_cls_lock.cc | 106 | ||||
-rw-r--r-- | src/test/librados_test_stub/LibradosTestStub.cc | 6 |
6 files changed, 207 insertions, 24 deletions
diff --git a/src/cls/lock/cls_lock.cc b/src/cls/lock/cls_lock.cc index 643eba13666..88ea6a71b0c 100644 --- a/src/cls/lock/cls_lock.cc +++ b/src/cls/lock/cls_lock.cc @@ -34,7 +34,18 @@ CLS_NAME(lock) #define LOCK_PREFIX "lock." -static int read_lock(cls_method_context_t hctx, const string& name, lock_info_t *lock) +static int clean_lock(cls_method_context_t hctx) +{ + int r = cls_cxx_remove(hctx); + if (r < 0) + return r; + + return 0; +} + +static int read_lock(cls_method_context_t hctx, + const string& name, + lock_info_t *lock) { bufferlist bl; string key = LOCK_PREFIX; @@ -67,16 +78,20 @@ static int read_lock(cls_method_context_t hctx, const string& name, lock_info_t map<locker_id_t, locker_info_t>::iterator iter = lock->lockers.begin(); while (iter != lock->lockers.end()) { - map<locker_id_t, locker_info_t>::iterator next = iter; - ++next; - struct locker_info_t& info = iter->second; if (!info.expiration.is_zero() && info.expiration < now) { CLS_LOG(20, "expiring locker"); - lock->lockers.erase(iter); + iter = lock->lockers.erase(iter); + } else { + ++iter; } + } - iter = next; + if (lock->lockers.empty() && cls_lock_is_ephemeral(lock->lock_type)) { + r = clean_lock(hctx); + if (r < 0) { + CLS_ERR("error, on read, cleaning lock object %s", cpp_strerror(r).c_str()); + } } return 0; @@ -122,15 +137,17 @@ static int lock_obj(cls_method_context_t hctx, const string& cookie, const string& tag) { - bool exclusive = lock_type == LOCK_EXCLUSIVE; + bool exclusive = cls_lock_is_exclusive(lock_type); lock_info_t linfo; bool fail_if_exists = (flags & LOCK_FLAG_MAY_RENEW) == 0; bool fail_if_does_not_exist = flags & LOCK_FLAG_MUST_RENEW; - CLS_LOG(20, "requested lock_type=%s fail_if_exists=%d fail_if_does_not_exist=%d", cls_lock_type_str(lock_type), fail_if_exists, fail_if_does_not_exist); - if (lock_type != LOCK_EXCLUSIVE && - lock_type != LOCK_SHARED) + CLS_LOG(20, + "requested lock_type=%s fail_if_exists=%d fail_if_does_not_exist=%d", + cls_lock_type_str(lock_type), fail_if_exists, fail_if_does_not_exist); + if (!cls_lock_is_valid(lock_type)) { return -EINVAL; + } if (name.empty()) return -EINVAL; @@ -148,6 +165,7 @@ static int lock_obj(cls_method_context_t hctx, CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str()); return r; } + map<locker_id_t, locker_info_t>& lockers = linfo.lockers; map<locker_id_t, locker_info_t>::iterator iter; @@ -246,9 +264,9 @@ static int lock_op(cls_method_context_t hctx, * entity or cookie is wrong), or -errno on other error. */ static int remove_lock(cls_method_context_t hctx, - const string& name, - entity_name_t& locker, - const string& cookie) + const string& name, + entity_name_t& locker, + const string& cookie) { // get current lockers lock_info_t linfo; @@ -268,7 +286,12 @@ static int remove_lock(cls_method_context_t hctx, } lockers.erase(iter); - r = write_lock(hctx, name, linfo); + if (cls_lock_is_ephemeral(linfo.lock_type)) { + ceph_assert(lockers.empty()); + r = clean_lock(hctx); + } else { + r = write_lock(hctx, name, linfo); + } return r; } @@ -312,7 +335,7 @@ static int unlock_op(cls_method_context_t hctx, * is wrong), or -errno on other (unexpected) error. */ static int break_lock(cls_method_context_t hctx, - bufferlist *in, bufferlist *out) + bufferlist *in, bufferlist *out) { CLS_LOG(20, "break_lock"); cls_lock_break_op op; diff --git a/src/cls/lock/cls_lock_client.cc b/src/cls/lock/cls_lock_client.cc index 19f6a575e61..498d573f20d 100644 --- a/src/cls/lock/cls_lock_client.cc +++ b/src/cls/lock/cls_lock_client.cc @@ -208,14 +208,19 @@ namespace rados { rados_op->exec("lock", "set_cookie", in); } + void Lock::assert_locked_shared(ObjectOperation *op) + { + assert_locked(op, name, LOCK_SHARED, cookie, tag); + } + void Lock::assert_locked_exclusive(ObjectOperation *op) { assert_locked(op, name, LOCK_EXCLUSIVE, cookie, tag); } - void Lock::assert_locked_shared(ObjectOperation *op) + void Lock::assert_locked_exclusive_ephemeral(ObjectOperation *op) { - assert_locked(op, name, LOCK_SHARED, cookie, tag); + assert_locked(op, name, LOCK_EXCLUSIVE_EPHEMERAL, cookie, tag); } void Lock::lock_shared(ObjectWriteOperation *op) @@ -242,6 +247,18 @@ namespace rados { cookie, tag, description, duration, flags); } + void Lock::lock_exclusive_ephemeral(ObjectWriteOperation *op) + { + lock(op, name, LOCK_EXCLUSIVE_EPHEMERAL, + cookie, tag, description, duration, flags); + } + + int Lock::lock_exclusive_ephemeral(IoCtx *ioctx, const string& oid) + { + return lock(ioctx, oid, name, LOCK_EXCLUSIVE_EPHEMERAL, + cookie, tag, description, duration, flags); + } + void Lock::unlock(ObjectWriteOperation *op) { rados::cls::lock::unlock(op, name, cookie); diff --git a/src/cls/lock/cls_lock_client.h b/src/cls/lock/cls_lock_client.h index 002864edecc..446b6b59af2 100644 --- a/src/cls/lock/cls_lock_client.h +++ b/src/cls/lock/cls_lock_client.h @@ -101,6 +101,7 @@ namespace rados { flags &= ~LOCK_FLAG_MAY_RENEW; } } + void set_must_renew(bool renew) { if (renew) { flags |= LOCK_FLAG_MUST_RENEW; @@ -110,18 +111,31 @@ namespace rados { } } - void assert_locked_exclusive(librados::ObjectOperation *rados_op); void assert_locked_shared(librados::ObjectOperation *rados_op); + void assert_locked_exclusive(librados::ObjectOperation *rados_op); + void assert_locked_exclusive_ephemeral(librados::ObjectOperation *rados_op); /* ObjectWriteOperation */ - void lock_exclusive(librados::ObjectWriteOperation *ioctx); void lock_shared(librados::ObjectWriteOperation *ioctx); + void lock_exclusive(librados::ObjectWriteOperation *ioctx); + + // Be careful when using an exclusive ephemeral lock; it is + // intended strictly for cases when a lock object exists + // solely for a lock in a given process and the object is no + // longer needed when the lock is unlocked or expired, as the + // cls back-end will make an effort to delete it. + void lock_exclusive_ephemeral(librados::ObjectWriteOperation *ioctx); void unlock(librados::ObjectWriteOperation *ioctx); - void break_lock(librados::ObjectWriteOperation *ioctx, const entity_name_t& locker); + void break_lock(librados::ObjectWriteOperation *ioctx, + const entity_name_t& locker); /* IoCtx */ - int lock_exclusive(librados::IoCtx *ioctx, const std::string& oid); int lock_shared(librados::IoCtx *ioctx, const std::string& oid); + int lock_exclusive(librados::IoCtx *ioctx, const std::string& oid); + + // NB: see above comment on exclusive ephemeral locks + int lock_exclusive_ephemeral(librados::IoCtx *ioctx, + const std::string& oid); int unlock(librados::IoCtx *ioctx, const std::string& oid); int break_lock(librados::IoCtx *ioctx, const std::string& oid, const entity_name_t& locker); diff --git a/src/cls/lock/cls_lock_types.h b/src/cls/lock/cls_lock_types.h index e8ec4518fa9..75796996e7b 100644 --- a/src/cls/lock/cls_lock_types.h +++ b/src/cls/lock/cls_lock_types.h @@ -14,9 +14,10 @@ #define LOCK_FLAG_MUST_RENEW 0x2 /* lock must already be acquired */ enum ClsLockType { - LOCK_NONE = 0, - LOCK_EXCLUSIVE = 1, - LOCK_SHARED = 2, + LOCK_NONE = 0, + LOCK_EXCLUSIVE = 1, + LOCK_SHARED = 2, + LOCK_EXCLUSIVE_EPHEMERAL = 3, /* lock object is removed @ unlock */ }; inline const char *cls_lock_type_str(ClsLockType type) @@ -28,11 +29,27 @@ inline const char *cls_lock_type_str(ClsLockType type) return "exclusive"; case LOCK_SHARED: return "shared"; + case LOCK_EXCLUSIVE_EPHEMERAL: + return "exclusive-ephemeral"; default: return "<unknown>"; } } +inline bool cls_lock_is_exclusive(ClsLockType type) { + return LOCK_EXCLUSIVE == type || LOCK_EXCLUSIVE_EPHEMERAL == type; +} + +inline bool cls_lock_is_ephemeral(ClsLockType type) { + return LOCK_EXCLUSIVE_EPHEMERAL == type; +} + +inline bool cls_lock_is_valid(ClsLockType type) { + return LOCK_SHARED == type || + LOCK_EXCLUSIVE == type || + LOCK_EXCLUSIVE_EPHEMERAL == type; +} + namespace rados { namespace cls { namespace lock { diff --git a/src/test/cls_lock/test_cls_lock.cc b/src/test/cls_lock/test_cls_lock.cc index b8bc51e01a4..37d10a19cbc 100644 --- a/src/test/cls_lock/test_cls_lock.cc +++ b/src/test/cls_lock/test_cls_lock.cc @@ -457,3 +457,109 @@ TEST(ClsLock, TestRenew) { ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); } + +TEST(ClsLock, TestExclusiveEphemeralBasic) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + bufferlist bl; + + string oid1 = "foo1"; + string oid2 = "foo2"; + string lock_name1 = "mylock1"; + string lock_name2 = "mylock2"; + + Lock l1(lock_name1); + l1.set_duration(utime_t(5, 0)); + + uint64_t size; + time_t mod_time; + + l1.set_may_renew(true); + ASSERT_EQ(0, l1.lock_exclusive_ephemeral(&ioctx, oid1)); + ASSERT_EQ(0, ioctx.stat(oid1, &size, &mod_time)); + sleep(2); + ASSERT_EQ(0, l1.unlock(&ioctx, oid1)); + ASSERT_EQ(-ENOENT, ioctx.stat(oid1, &size, &mod_time)); + + // *********************************************** + + Lock l2(lock_name2); + utime_t lock_duration2(5, 0); + l2.set_duration(utime_t(5, 0)); + + ASSERT_EQ(0, l2.lock_exclusive(&ioctx, oid2)); + ASSERT_EQ(0, ioctx.stat(oid2, &size, &mod_time)); + sleep(2); + ASSERT_EQ(0, l2.unlock(&ioctx, oid2)); + ASSERT_EQ(0, ioctx.stat(oid2, &size, &mod_time)); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} + + +TEST(ClsLock, TestExclusiveEphemeralStealEphemeral) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + bufferlist bl; + + string oid1 = "foo1"; + string lock_name1 = "mylock1"; + + Lock l1(lock_name1); + l1.set_duration(utime_t(3, 0)); + + ASSERT_EQ(0, l1.lock_exclusive_ephemeral(&ioctx, oid1)); + sleep(4); + + // l1 is expired, l2 can take; l2 is also exclusive_ephemeral + Lock l2(lock_name1); + l2.set_duration(utime_t(3, 0)); + ASSERT_EQ(0, l2.lock_exclusive_ephemeral(&ioctx, oid1)); + sleep(1); + ASSERT_EQ(0, l2.unlock(&ioctx, oid1)); + + // l2 cannot unlock its expired lock + ASSERT_EQ(-ENOENT, l1.unlock(&ioctx, oid1)); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} + + +TEST(ClsLock, TestExclusiveEphemeralStealExclusive) { + Rados cluster; + std::string pool_name = get_temp_pool_name(); + ASSERT_EQ("", create_one_pool_pp(pool_name, cluster)); + IoCtx ioctx; + cluster.ioctx_create(pool_name.c_str(), ioctx); + + bufferlist bl; + + string oid1 = "foo1"; + string lock_name1 = "mylock1"; + + Lock l1(lock_name1); + l1.set_duration(utime_t(3, 0)); + + ASSERT_EQ(0, l1.lock_exclusive_ephemeral(&ioctx, oid1)); + sleep(4); + + // l1 is expired, l2 can take; l2 is exclusive (but not ephemeral) + Lock l2(lock_name1); + l2.set_duration(utime_t(3, 0)); + ASSERT_EQ(0, l2.lock_exclusive(&ioctx, oid1)); + sleep(1); + ASSERT_EQ(0, l2.unlock(&ioctx, oid1)); + + // l2 cannot unlock its expired lock + ASSERT_EQ(-ENOENT, l1.unlock(&ioctx, oid1)); + + ASSERT_EQ(0, destroy_one_pool_pp(pool_name, cluster)); +} diff --git a/src/test/librados_test_stub/LibradosTestStub.cc b/src/test/librados_test_stub/LibradosTestStub.cc index dd9ebba273f..17221b24c30 100644 --- a/src/test/librados_test_stub/LibradosTestStub.cc +++ b/src/test/librados_test_stub/LibradosTestStub.cc @@ -1176,6 +1176,12 @@ int cls_cxx_create(cls_method_context_t hctx, bool exclusive) { return ctx->io_ctx_impl->create(ctx->oid, exclusive); } +int cls_cxx_remove(cls_method_context_t hctx) { + librados::TestClassHandler::MethodContext *ctx = + reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx); + return ctx->io_ctx_impl->remove(ctx->oid, ctx->io_ctx_impl->get_snap_context()); +} + int cls_get_request_origin(cls_method_context_t hctx, entity_inst_t *origin) { librados::TestClassHandler::MethodContext *ctx = reinterpret_cast<librados::TestClassHandler::MethodContext*>(hctx); |