Move LockFileLock to separate file
Project: http://git-wip-us.apache.org/repos/asf/lucy/repo Commit: http://git-wip-us.apache.org/repos/asf/lucy/commit/6e8538a8 Tree: http://git-wip-us.apache.org/repos/asf/lucy/tree/6e8538a8 Diff: http://git-wip-us.apache.org/repos/asf/lucy/diff/6e8538a8 Branch: refs/heads/master Commit: 6e8538a8834a49da7e8cce8b94decc130467b0f3 Parents: 2bd2bc6 Author: Nick Wellnhofer <wellnho...@aevum.de> Authored: Sun Feb 19 17:01:50 2017 +0100 Committer: Nick Wellnhofer <wellnho...@aevum.de> Committed: Mon Feb 20 16:51:32 2017 +0100 ---------------------------------------------------------------------- core/Lucy/Index/IndexManager.c | 2 +- core/Lucy/Store/Lock.c | 376 ++-------------------------------- core/Lucy/Store/Lock.cfh | 33 +-- core/Lucy/Store/LockFileLock.c | 363 ++++++++++++++++++++++++++++++++ core/Lucy/Store/LockFileLock.cfh | 49 +++++ test/Lucy/Test/Store/TestLock.c | 2 +- 6 files changed, 437 insertions(+), 388 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lucy/blob/6e8538a8/core/Lucy/Index/IndexManager.c ---------------------------------------------------------------------- diff --git a/core/Lucy/Index/IndexManager.c b/core/Lucy/Index/IndexManager.c index bd3aa30..8e56509 100644 --- a/core/Lucy/Index/IndexManager.c +++ b/core/Lucy/Index/IndexManager.c @@ -25,7 +25,7 @@ #include "Lucy/Index/Snapshot.h" #include "Lucy/Store/DirHandle.h" #include "Lucy/Store/Folder.h" -#include "Lucy/Store/Lock.h" +#include "Lucy/Store/LockFileLock.h" #include "Lucy/Util/IndexFileNames.h" #include "Lucy/Util/Json.h" #include "Lucy/Util/StringHelper.h" http://git-wip-us.apache.org/repos/asf/lucy/blob/6e8538a8/core/Lucy/Store/Lock.c ---------------------------------------------------------------------- diff --git a/core/Lucy/Store/Lock.c b/core/Lucy/Store/Lock.c index e716099..7a4a060 100644 --- a/core/Lucy/Store/Lock.c +++ b/core/Lucy/Store/Lock.c @@ -15,18 +15,12 @@ */ #define C_LUCY_LOCK -#define C_LUCY_LOCKFILELOCK #include "Lucy/Util/ToolSet.h" -#include <stdio.h> #include <ctype.h> #include "Lucy/Store/Lock.h" -#include "Lucy/Store/DirHandle.h" #include "Lucy/Store/Folder.h" -#include "Lucy/Store/OutStream.h" -#include "Lucy/Util/Json.h" -#include "Lucy/Util/ProcessID.h" #include "Lucy/Util/Sleep.h" Lock* @@ -76,6 +70,26 @@ Lock_Destroy_IMP(Lock *self) { } bool +Lock_make_lock_dir(Folder *folder) { + String *lock_dir_name = SSTR_WRAP_C("locks"); + + if (!Folder_MkDir(folder, lock_dir_name)) { + Err *err = (Err*)INCREF(Err_get_error()); + // Maybe our attempt failed because another process succeeded. + if (Folder_Find_Folder(folder, lock_dir_name)) { + DECREF(err); + } + else { + // Nope, everything failed, so bail out. + Err_set_error(err); + return false; + } + } + + return true; +} + +bool Lock_Obtain_Shared_IMP(Lock *self) { LockIVARS *const ivars = Lock_IVARS(self); int32_t time_left = ivars->interval == 0 ? 0 : ivars->timeout; @@ -111,356 +125,6 @@ Lock_Obtain_Exclusive_IMP(Lock *self) { /***************************************************************************/ -#define LFLOCK_STATE_UNLOCKED 0 -#define LFLOCK_STATE_LOCKED_SHARED 1 -#define LFLOCK_STATE_LOCKED_EXCLUSIVE 2 - -static bool -S_request(LockFileLockIVARS *ivars, String *lock_path); - -static bool -S_is_locked_exclusive(LockFileLockIVARS *ivars); - -static bool -S_is_locked(LockFileLockIVARS *ivars); - -static bool -S_is_shared_lock_file(LockFileLockIVARS *ivars, String *entry); - -static bool -S_maybe_delete_file(LockFileLockIVARS *ivars, String *path, - bool delete_mine, bool delete_other); - -LockFileLock* -LFLock_new(Folder *folder, String *name, String *host, int32_t timeout, - int32_t interval, bool exclusive_only) { - LockFileLock *self = (LockFileLock*)Class_Make_Obj(LOCKFILELOCK); - return LFLock_init(self, folder, name, host, timeout, interval, - exclusive_only); -} - -LockFileLock* -LFLock_init(LockFileLock *self, Folder *folder, String *name, String *host, - int32_t timeout, int32_t interval, bool exclusive_only) { - int pid = PID_getpid(); - Lock_init((Lock*)self, folder, name, timeout, interval); - LockFileLockIVARS *const ivars = LFLock_IVARS(self); - ivars->host = (String*)INCREF(host); - ivars->link_path = Str_newf("%o.%o.%i64", ivars->lock_path, host, - (int64_t)pid); - ivars->exclusive_only = exclusive_only; - return self; -} - -struct lockfile_context { - OutStream *outstream; - String *json; -}; - -static void -S_write_lockfile_json(void *context) { - struct lockfile_context *stuff = (struct lockfile_context*)context; - size_t size = Str_Get_Size(stuff->json); - OutStream_Write_Bytes(stuff->outstream, Str_Get_Ptr8(stuff->json), size); - OutStream_Close(stuff->outstream); -} - -bool -LFLock_Request_Shared_IMP(LockFileLock *self) { - LockFileLockIVARS *const ivars = LFLock_IVARS(self); - - if (ivars->exclusive_only) { - THROW(ERR, "Can't request shared lock if exclusive_only is set"); - } - - if (ivars->state != LFLOCK_STATE_UNLOCKED) { - THROW(ERR, "Lock already acquired"); - } - - // TODO: The is_locked test and subsequent file creation is prone to a - // race condition. We could protect the whole process with an internal - // exclusive lock. - - if (S_is_locked_exclusive(ivars)) { - String *msg = Str_newf("'%o.lock' is locked", ivars->name); - Err_set_error((Err*)LockErr_new(msg)); - return false; - } - - String *path = NULL; - - uint32_t i = 0; - do { - DECREF(path); - path = Str_newf("locks/%o-%u32.lock", ivars->name, ++i); - } while (Folder_Exists(ivars->folder, path)); - - if (S_request(ivars, path)) { - ivars->shared_lock_path = path; - ivars->state = LFLOCK_STATE_LOCKED_SHARED; - return true; - } - else { - DECREF(path); - return false; - } -} - -bool -LFLock_Request_Exclusive_IMP(LockFileLock *self) { - LockFileLockIVARS *const ivars = LFLock_IVARS(self); - - if (ivars->state != LFLOCK_STATE_UNLOCKED) { - THROW(ERR, "Lock already acquired"); - } - - // TODO: The is_locked test and subsequent file creation is prone to a - // race condition. We could protect the whole process with an internal - // exclusive lock. - - if (ivars->exclusive_only - ? S_is_locked_exclusive(ivars) - : S_is_locked(ivars) - ) { - String *msg = Str_newf("'%o.lock' is locked", ivars->name); - Err_set_error((Err*)LockErr_new(msg)); - return false; - } - - if (S_request(ivars, ivars->lock_path)) { - ivars->state = LFLOCK_STATE_LOCKED_EXCLUSIVE; - return true; - } - else { - return false; - } -} - -static bool -S_request(LockFileLockIVARS *ivars, String *lock_path) { - bool success = false; - - // Create the "locks" subdirectory if necessary. - String *lock_dir_name = SSTR_WRAP_C("locks"); - if (!Folder_Exists(ivars->folder, lock_dir_name)) { - if (!Folder_MkDir(ivars->folder, lock_dir_name)) { - Err *err = (Err*)INCREF(Err_get_error()); - // Maybe our attempt failed because another process succeeded. - if (Folder_Find_Folder(ivars->folder, lock_dir_name)) { - DECREF(err); - } - else { - // Nope, everything failed, so bail out. - Err_set_error(err); - return false; - } - } - } - - // Prepare to write pid, lock name, and host to the lock file as JSON. - Hash *file_data = Hash_new(3); - Hash_Store_Utf8(file_data, "pid", 3, - (Obj*)Str_newf("%i32", (int32_t)PID_getpid())); - Hash_Store_Utf8(file_data, "host", 4, INCREF(ivars->host)); - Hash_Store_Utf8(file_data, "name", 4, INCREF(ivars->name)); - String *json = Json_to_json((Obj*)file_data); - DECREF(file_data); - - // Write to a temporary file, then use the creation of a hard link to - // ensure atomic but non-destructive creation of the lockfile with its - // complete contents. - - OutStream *outstream = Folder_Open_Out(ivars->folder, ivars->link_path); - if (!outstream) { - ERR_ADD_FRAME(Err_get_error()); - DECREF(json); - return false; - } - - struct lockfile_context context; - context.outstream = outstream; - context.json = json; - Err *json_error = Err_trap(S_write_lockfile_json, &context); - DECREF(outstream); - DECREF(json); - if (json_error) { - Err_set_error(json_error); - } - else { - success = Folder_Hard_Link(ivars->folder, ivars->link_path, - lock_path); - if (!success) { - // TODO: Only return a LockErr if errno == EEXIST, otherwise - // return a normal Err. - Err *hard_link_err = (Err*)CERTIFY(Err_get_error(), ERR); - String *msg = Str_newf("Failed to obtain lock at '%o': %o", - lock_path, Err_Get_Mess(hard_link_err)); - Err_set_error((Err*)LockErr_new(msg)); - } - } - - // Verify that our temporary file got zapped. - bool deletion_failed = !Folder_Delete(ivars->folder, ivars->link_path); - if (deletion_failed) { - String *mess = MAKE_MESS("Failed to delete '%o'", ivars->link_path); - Err_throw_mess(ERR, mess); - } - - return success; -} - -void -LFLock_Release_IMP(LockFileLock *self) { - LockFileLockIVARS *const ivars = LFLock_IVARS(self); - - if (ivars->state == LFLOCK_STATE_UNLOCKED) { - THROW(ERR, "Lock not acquired"); - } - - if (ivars->state == LFLOCK_STATE_LOCKED_EXCLUSIVE) { - if (Folder_Exists(ivars->folder, ivars->lock_path)) { - S_maybe_delete_file(ivars, ivars->lock_path, true, false); - } - } - else { // Shared lock. - if (Folder_Exists(ivars->folder, ivars->shared_lock_path)) { - S_maybe_delete_file(ivars, ivars->shared_lock_path, true, false); - } - - // Empty out lock_path. - DECREF(ivars->shared_lock_path); - ivars->shared_lock_path = NULL; - } - - ivars->state = LFLOCK_STATE_UNLOCKED; -} - -static bool -S_is_locked_exclusive(LockFileLockIVARS *ivars) { - return Folder_Exists(ivars->folder, ivars->lock_path) - && !S_maybe_delete_file(ivars, ivars->lock_path, false, true); -} - -static bool -S_is_locked(LockFileLockIVARS *ivars) { - if (S_is_locked_exclusive(ivars)) { return true; } - - // Check for shared lock. - - String *lock_dir_name = SSTR_WRAP_C("locks"); - if (!Folder_Find_Folder(ivars->folder, lock_dir_name)) { - return false; - } - - bool locked = false; - DirHandle *dh = Folder_Open_Dir(ivars->folder, lock_dir_name); - if (!dh) { RETHROW(INCREF(Err_get_error())); } - - while (DH_Next(dh)) { - String *entry = DH_Get_Entry(dh); - if (S_is_shared_lock_file(ivars, entry)) { - String *candidate = Str_newf("%o/%o", lock_dir_name, entry); - if (!S_maybe_delete_file(ivars, candidate, false, true)) { - locked = true; - } - DECREF(candidate); - } - DECREF(entry); - } - - DECREF(dh); - return locked; -} - -static bool -S_is_shared_lock_file(LockFileLockIVARS *ivars, String *entry) { - // Translation: $match = $entry =~ /^\Q$name-\d+\.lock\z/ - bool match = false; - - // $name - if (Str_Starts_With(entry, ivars->name)) { - StringIterator *iter = Str_Top(entry); - StrIter_Advance(iter, Str_Length(ivars->name)); - - // Hyphen-minus - if (StrIter_Next(iter) == '-') { - int32_t code_point = StrIter_Next(iter); - - // Digit - if (code_point >= '0' && code_point <= '9') { - // Optional digits - do { - code_point = StrIter_Next(iter); - } while (code_point >= '0' && code_point <= '9'); - - // ".lock" - match = code_point == '.' - && StrIter_Starts_With_Utf8(iter, "lock", 4) - && StrIter_Advance(iter, SIZE_MAX) == 4; - } - } - - DECREF(iter); - } - - return match; -} - -static bool -S_maybe_delete_file(LockFileLockIVARS *ivars, String *path, - bool delete_mine, bool delete_other) { - Folder *folder = ivars->folder; - bool success = false; - - Hash *hash = (Hash*)Json_slurp_json(folder, path); - if (hash != NULL && Obj_is_a((Obj*)hash, HASH)) { - String *pid_buf = (String*)Hash_Fetch_Utf8(hash, "pid", 3); - String *host = (String*)Hash_Fetch_Utf8(hash, "host", 4); - String *name = (String*)Hash_Fetch_Utf8(hash, "name", 4); - - // Match hostname and lock name. - if (host != NULL - && Str_is_a(host, STRING) - && Str_Equals(host, (Obj*)ivars->host) - && name != NULL - && Str_is_a(name, STRING) - && Str_Equals(name, (Obj*)ivars->name) - && pid_buf != NULL - && Str_is_a(pid_buf, STRING) - ) { - // Verify that pid is either mine or dead. - int pid = (int)Str_To_I64(pid_buf); - if ((delete_mine && pid == PID_getpid()) // This process. - || (delete_other && !PID_active(pid)) // Dead pid. - ) { - if (Folder_Delete(folder, path)) { - success = true; - } - else { - String *mess - = MAKE_MESS("Can't delete '%o'", path); - DECREF(hash); - Err_throw_mess(ERR, mess); - } - } - } - } - DECREF(hash); - - return success; -} - -void -LFLock_Destroy_IMP(LockFileLock *self) { - LockFileLockIVARS *const ivars = LFLock_IVARS(self); - if (ivars->state != LFLOCK_STATE_UNLOCKED) { LFLock_Release(self); } - DECREF(ivars->host); - DECREF(ivars->link_path); - SUPER_DESTROY(self, LOCKFILELOCK); -} - -/***************************************************************************/ - LockErr* LockErr_new(String *message) { LockErr *self = (LockErr*)Class_Make_Obj(LOCKERR); http://git-wip-us.apache.org/repos/asf/lucy/blob/6e8538a8/core/Lucy/Store/Lock.cfh ---------------------------------------------------------------------- diff --git a/core/Lucy/Store/Lock.cfh b/core/Lucy/Store/Lock.cfh index 11f31c0..09c2c54 100644 --- a/core/Lucy/Store/Lock.cfh +++ b/core/Lucy/Store/Lock.cfh @@ -49,6 +49,9 @@ abstract class Lucy::Store::Lock inherits Clownfish::Obj { init(Lock *self, Folder *folder, String *name, int32_t timeout = 0, int32_t interval = 100); + inert bool + make_lock_dir(Folder *folder); + /** Call [](.Request_Shared) once per `interval` until [](.Request_Shared) * returns success or the `timeout` has been reached. * @@ -99,36 +102,6 @@ abstract class Lucy::Store::Lock inherits Clownfish::Obj { Destroy(Lock *self); } -class Lucy::Store::LockFileLock nickname LFLock - inherits Lucy::Store::Lock { - - String *host; - String *shared_lock_path; - String *link_path; - int state; - bool exclusive_only; - - inert incremented LockFileLock* - new(Folder *folder, String *name, String *host, int32_t timeout = 0, - int32_t interval = 100, bool exclusive_only); - - public inert LockFileLock* - init(LockFileLock *self, Folder *folder, String *name, String *host, - int32_t timeout = 0, int32_t interval = 100, bool exclusive_only); - - public bool - Request_Shared(LockFileLock *self); - - public bool - Request_Exclusive(LockFileLock *self); - - public void - Release(LockFileLock *self); - - public void - Destroy(LockFileLock *self); -} - /** Lock exception. * * LockErr is a subclass of [Err](cfish:cfish.Err) which indicates http://git-wip-us.apache.org/repos/asf/lucy/blob/6e8538a8/core/Lucy/Store/LockFileLock.c ---------------------------------------------------------------------- diff --git a/core/Lucy/Store/LockFileLock.c b/core/Lucy/Store/LockFileLock.c new file mode 100644 index 0000000..19d624e --- /dev/null +++ b/core/Lucy/Store/LockFileLock.c @@ -0,0 +1,363 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define C_LUCY_LOCKFILELOCK +#include "Lucy/Util/ToolSet.h" + +#include "Lucy/Store/LockFileLock.h" +#include "Lucy/Store/DirHandle.h" +#include "Lucy/Store/Folder.h" +#include "Lucy/Store/OutStream.h" +#include "Lucy/Util/Json.h" +#include "Lucy/Util/ProcessID.h" + +#define LFLOCK_STATE_UNLOCKED 0 +#define LFLOCK_STATE_LOCKED_SHARED 1 +#define LFLOCK_STATE_LOCKED_EXCLUSIVE 2 + +static bool +S_request(LockFileLockIVARS *ivars, String *lock_path); + +static bool +S_is_locked_exclusive(LockFileLockIVARS *ivars); + +static bool +S_is_locked(LockFileLockIVARS *ivars); + +static bool +S_is_shared_lock_file(LockFileLockIVARS *ivars, String *entry); + +static bool +S_maybe_delete_file(LockFileLockIVARS *ivars, String *path, + bool delete_mine, bool delete_other); + +LockFileLock* +LFLock_new(Folder *folder, String *name, String *host, int32_t timeout, + int32_t interval, bool exclusive_only) { + LockFileLock *self = (LockFileLock*)Class_Make_Obj(LOCKFILELOCK); + return LFLock_init(self, folder, name, host, timeout, interval, + exclusive_only); +} + +LockFileLock* +LFLock_init(LockFileLock *self, Folder *folder, String *name, String *host, + int32_t timeout, int32_t interval, bool exclusive_only) { + int pid = PID_getpid(); + Lock_init((Lock*)self, folder, name, timeout, interval); + LockFileLockIVARS *const ivars = LFLock_IVARS(self); + ivars->host = (String*)INCREF(host); + ivars->link_path = Str_newf("%o.%o.%i64", ivars->lock_path, host, + (int64_t)pid); + ivars->exclusive_only = exclusive_only; + return self; +} + +struct lockfile_context { + OutStream *outstream; + String *json; +}; + +static void +S_write_lockfile_json(void *context) { + struct lockfile_context *stuff = (struct lockfile_context*)context; + size_t size = Str_Get_Size(stuff->json); + OutStream_Write_Bytes(stuff->outstream, Str_Get_Ptr8(stuff->json), size); + OutStream_Close(stuff->outstream); +} + +bool +LFLock_Request_Shared_IMP(LockFileLock *self) { + LockFileLockIVARS *const ivars = LFLock_IVARS(self); + + if (ivars->exclusive_only) { + THROW(ERR, "Can't request shared lock if exclusive_only is set"); + } + + if (ivars->state != LFLOCK_STATE_UNLOCKED) { + THROW(ERR, "Lock already acquired"); + } + + // TODO: The is_locked test and subsequent file creation is prone to a + // race condition. We could protect the whole process with an internal + // exclusive lock. + + if (S_is_locked_exclusive(ivars)) { + String *msg = Str_newf("'%o.lock' is locked", ivars->name); + Err_set_error((Err*)LockErr_new(msg)); + return false; + } + + String *path = NULL; + + uint32_t i = 0; + do { + DECREF(path); + path = Str_newf("locks/%o-%u32.lock", ivars->name, ++i); + } while (Folder_Exists(ivars->folder, path)); + + if (S_request(ivars, path)) { + ivars->shared_lock_path = path; + ivars->state = LFLOCK_STATE_LOCKED_SHARED; + return true; + } + else { + DECREF(path); + return false; + } +} + +bool +LFLock_Request_Exclusive_IMP(LockFileLock *self) { + LockFileLockIVARS *const ivars = LFLock_IVARS(self); + + if (ivars->state != LFLOCK_STATE_UNLOCKED) { + THROW(ERR, "Lock already acquired"); + } + + // TODO: The is_locked test and subsequent file creation is prone to a + // race condition. We could protect the whole process with an internal + // exclusive lock. + + if (ivars->exclusive_only + ? S_is_locked_exclusive(ivars) + : S_is_locked(ivars) + ) { + String *msg = Str_newf("'%o.lock' is locked", ivars->name); + Err_set_error((Err*)LockErr_new(msg)); + return false; + } + + if (S_request(ivars, ivars->lock_path)) { + ivars->state = LFLOCK_STATE_LOCKED_EXCLUSIVE; + return true; + } + else { + return false; + } +} + +static bool +S_request(LockFileLockIVARS *ivars, String *lock_path) { + bool success = false; + + // Create the "locks" subdirectory if necessary. + String *lock_dir_name = SSTR_WRAP_C("locks"); + if (!Folder_Exists(ivars->folder, lock_dir_name)) { + if (!Lock_make_lock_dir(ivars->folder)) { return false; } + } + + // Prepare to write pid, lock name, and host to the lock file as JSON. + Hash *file_data = Hash_new(3); + Hash_Store_Utf8(file_data, "pid", 3, + (Obj*)Str_newf("%i32", (int32_t)PID_getpid())); + Hash_Store_Utf8(file_data, "host", 4, INCREF(ivars->host)); + Hash_Store_Utf8(file_data, "name", 4, INCREF(ivars->name)); + String *json = Json_to_json((Obj*)file_data); + DECREF(file_data); + + // Write to a temporary file, then use the creation of a hard link to + // ensure atomic but non-destructive creation of the lockfile with its + // complete contents. + + OutStream *outstream = Folder_Open_Out(ivars->folder, ivars->link_path); + if (!outstream) { + ERR_ADD_FRAME(Err_get_error()); + DECREF(json); + return false; + } + + struct lockfile_context context; + context.outstream = outstream; + context.json = json; + Err *json_error = Err_trap(S_write_lockfile_json, &context); + DECREF(outstream); + DECREF(json); + if (json_error) { + Err_set_error(json_error); + } + else { + success = Folder_Hard_Link(ivars->folder, ivars->link_path, + lock_path); + if (!success) { + // TODO: Only return a LockErr if errno == EEXIST, otherwise + // return a normal Err. + Err *hard_link_err = (Err*)CERTIFY(Err_get_error(), ERR); + String *msg = Str_newf("Failed to obtain lock at '%o': %o", + lock_path, Err_Get_Mess(hard_link_err)); + Err_set_error((Err*)LockErr_new(msg)); + } + } + + // Verify that our temporary file got zapped. + bool deletion_failed = !Folder_Delete(ivars->folder, ivars->link_path); + if (deletion_failed) { + String *mess = MAKE_MESS("Failed to delete '%o'", ivars->link_path); + Err_throw_mess(ERR, mess); + } + + return success; +} + +void +LFLock_Release_IMP(LockFileLock *self) { + LockFileLockIVARS *const ivars = LFLock_IVARS(self); + + if (ivars->state == LFLOCK_STATE_UNLOCKED) { + THROW(ERR, "Lock not acquired"); + } + + if (ivars->state == LFLOCK_STATE_LOCKED_EXCLUSIVE) { + if (Folder_Exists(ivars->folder, ivars->lock_path)) { + S_maybe_delete_file(ivars, ivars->lock_path, true, false); + } + } + else { // Shared lock. + if (Folder_Exists(ivars->folder, ivars->shared_lock_path)) { + S_maybe_delete_file(ivars, ivars->shared_lock_path, true, false); + } + + // Empty out lock_path. + DECREF(ivars->shared_lock_path); + ivars->shared_lock_path = NULL; + } + + ivars->state = LFLOCK_STATE_UNLOCKED; +} + +static bool +S_is_locked_exclusive(LockFileLockIVARS *ivars) { + return Folder_Exists(ivars->folder, ivars->lock_path) + && !S_maybe_delete_file(ivars, ivars->lock_path, false, true); +} + +static bool +S_is_locked(LockFileLockIVARS *ivars) { + if (S_is_locked_exclusive(ivars)) { return true; } + + // Check for shared lock. + + String *lock_dir_name = SSTR_WRAP_C("locks"); + if (!Folder_Find_Folder(ivars->folder, lock_dir_name)) { + return false; + } + + bool locked = false; + DirHandle *dh = Folder_Open_Dir(ivars->folder, lock_dir_name); + if (!dh) { RETHROW(INCREF(Err_get_error())); } + + while (DH_Next(dh)) { + String *entry = DH_Get_Entry(dh); + if (S_is_shared_lock_file(ivars, entry)) { + String *candidate = Str_newf("%o/%o", lock_dir_name, entry); + if (!S_maybe_delete_file(ivars, candidate, false, true)) { + locked = true; + } + DECREF(candidate); + } + DECREF(entry); + } + + DECREF(dh); + return locked; +} + +static bool +S_is_shared_lock_file(LockFileLockIVARS *ivars, String *entry) { + // Translation: $match = $entry =~ /^\Q$name-\d+\.lock\z/ + bool match = false; + + // $name + if (Str_Starts_With(entry, ivars->name)) { + StringIterator *iter = Str_Top(entry); + StrIter_Advance(iter, Str_Length(ivars->name)); + + // Hyphen-minus + if (StrIter_Next(iter) == '-') { + int32_t code_point = StrIter_Next(iter); + + // Digit + if (code_point >= '0' && code_point <= '9') { + // Optional digits + do { + code_point = StrIter_Next(iter); + } while (code_point >= '0' && code_point <= '9'); + + // ".lock" + match = code_point == '.' + && StrIter_Starts_With_Utf8(iter, "lock", 4) + && StrIter_Advance(iter, SIZE_MAX) == 4; + } + } + + DECREF(iter); + } + + return match; +} + +static bool +S_maybe_delete_file(LockFileLockIVARS *ivars, String *path, + bool delete_mine, bool delete_other) { + Folder *folder = ivars->folder; + bool success = false; + + Hash *hash = (Hash*)Json_slurp_json(folder, path); + if (hash != NULL && Obj_is_a((Obj*)hash, HASH)) { + String *pid_buf = (String*)Hash_Fetch_Utf8(hash, "pid", 3); + String *host = (String*)Hash_Fetch_Utf8(hash, "host", 4); + String *name = (String*)Hash_Fetch_Utf8(hash, "name", 4); + + // Match hostname and lock name. + if (host != NULL + && Str_is_a(host, STRING) + && Str_Equals(host, (Obj*)ivars->host) + && name != NULL + && Str_is_a(name, STRING) + && Str_Equals(name, (Obj*)ivars->name) + && pid_buf != NULL + && Str_is_a(pid_buf, STRING) + ) { + // Verify that pid is either mine or dead. + int pid = (int)Str_To_I64(pid_buf); + if ((delete_mine && pid == PID_getpid()) // This process. + || (delete_other && !PID_active(pid)) // Dead pid. + ) { + if (Folder_Delete(folder, path)) { + success = true; + } + else { + String *mess + = MAKE_MESS("Can't delete '%o'", path); + DECREF(hash); + Err_throw_mess(ERR, mess); + } + } + } + } + DECREF(hash); + + return success; +} + +void +LFLock_Destroy_IMP(LockFileLock *self) { + LockFileLockIVARS *const ivars = LFLock_IVARS(self); + if (ivars->state != LFLOCK_STATE_UNLOCKED) { LFLock_Release(self); } + DECREF(ivars->host); + DECREF(ivars->link_path); + SUPER_DESTROY(self, LOCKFILELOCK); +} + http://git-wip-us.apache.org/repos/asf/lucy/blob/6e8538a8/core/Lucy/Store/LockFileLock.cfh ---------------------------------------------------------------------- diff --git a/core/Lucy/Store/LockFileLock.cfh b/core/Lucy/Store/LockFileLock.cfh new file mode 100644 index 0000000..30fc23a --- /dev/null +++ b/core/Lucy/Store/LockFileLock.cfh @@ -0,0 +1,49 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +parcel Lucy; + +class Lucy::Store::LockFileLock nickname LFLock + inherits Lucy::Store::Lock { + + String *host; + String *shared_lock_path; + String *link_path; + int state; + bool exclusive_only; + + inert incremented LockFileLock* + new(Folder *folder, String *name, String *host, int32_t timeout = 0, + int32_t interval = 100, bool exclusive_only); + + public inert LockFileLock* + init(LockFileLock *self, Folder *folder, String *name, String *host, + int32_t timeout = 0, int32_t interval = 100, bool exclusive_only); + + public bool + Request_Shared(LockFileLock *self); + + public bool + Request_Exclusive(LockFileLock *self); + + public void + Release(LockFileLock *self); + + public void + Destroy(LockFileLock *self); +} + + http://git-wip-us.apache.org/repos/asf/lucy/blob/6e8538a8/test/Lucy/Test/Store/TestLock.c ---------------------------------------------------------------------- diff --git a/test/Lucy/Test/Store/TestLock.c b/test/Lucy/Test/Store/TestLock.c index b52f366..df3eb20 100644 --- a/test/Lucy/Test/Store/TestLock.c +++ b/test/Lucy/Test/Store/TestLock.c @@ -30,7 +30,7 @@ #include "Lucy/Test/Store/TestLock.h" #include "Clownfish/TestHarness/TestBatchRunner.h" #include "Lucy/Store/FSFolder.h" -#include "Lucy/Store/Lock.h" +#include "Lucy/Store/LockFileLock.h" #include "Lucy/Store/RAMFolder.h" #include "Lucy/Util/Json.h"