libmdbx/src/lck-windows.c

398 lines
12 KiB
C
Raw Normal View History

/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>
* and other libmdbx authors: please see AUTHORS file.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted only as authorized by the OpenLDAP
* Public License.
*
* A copy of this license is available in the file LICENSE in the
* top-level directory of the distribution or, alternatively, at
* <http://www.OpenLDAP.org/license.html>.
*/
#include "./bits.h"
/* PREAMBLE FOR WINDOWS:
*
* We are not concerned for performance here.
* If you are running Windows a performance could NOT be the goal.
* Otherwise please use Linux.
*
* Regards,
* LY
*/
/*----------------------------------------------------------------------------*/
/* rthc */
static CRITICAL_SECTION rthc_critical_section;
static void NTAPI tls_callback(PVOID module, DWORD reason, PVOID reserved) {
(void)module;
(void)reserved;
switch (reason) {
case DLL_PROCESS_ATTACH:
InitializeCriticalSection(&rthc_critical_section);
break;
case DLL_PROCESS_DETACH:
DeleteCriticalSection(&rthc_critical_section);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
mdbx_rthc_cleanup();
break;
}
}
void mdbx_rthc_lock(void) { EnterCriticalSection(&rthc_critical_section); }
void mdbx_rthc_unlock(void) { LeaveCriticalSection(&rthc_critical_section); }
/* *INDENT-OFF* */
/* clang-format off */
#if defined(_MSC_VER)
# pragma const_seg(push)
# pragma data_seg(push)
# ifdef _WIN64
/* kick a linker to create the TLS directory if not already done */
# pragma comment(linker, "/INCLUDE:_tls_used")
/* Force some symbol references. */
# pragma comment(linker, "/INCLUDE:mdbx_tls_callback")
/* specific const-segment for WIN64 */
# pragma const_seg(".CRT$XLB")
const
# else
/* kick a linker to create the TLS directory if not already done */
# pragma comment(linker, "/INCLUDE:__tls_used")
/* Force some symbol references. */
# pragma comment(linker, "/INCLUDE:_mdbx_tls_callback")
/* specific data-segment for WIN32 */
# pragma data_seg(".CRT$XLB")
# endif
PIMAGE_TLS_CALLBACK mdbx_tls_callback = tls_callback;
# pragma data_seg(pop)
# pragma const_seg(pop)
#elif defined(__GNUC__)
# ifdef _WIN64
const
# endif
PIMAGE_TLS_CALLBACK mdbx_tls_callback __attribute__((section(".CRT$XLB"), used))
= tls_callback;
#else
# error FIXME
#endif
/* *INDENT-ON* */
/* clang-format on */
/*----------------------------------------------------------------------------*/
#define LCK_SHARED 0
#define LCK_EXCLUSIVE LOCKFILE_EXCLUSIVE_LOCK
#define LCK_WAITFOR 0
#define LCK_DONTWAIT LOCKFILE_FAIL_IMMEDIATELY
static __inline BOOL flock(mdbx_filehandle_t fd, DWORD flags, off_t offset,
size_t bytes) {
OVERLAPPED ov;
ov.hEvent = 0;
ov.Offset = (DWORD)offset;
ov.OffsetHigh = HIGH_DWORD(offset);
return LockFileEx(fd, flags, 0, (DWORD)bytes, HIGH_DWORD(bytes), &ov);
}
static __inline BOOL funlock(mdbx_filehandle_t fd, off_t offset, size_t bytes) {
return UnlockFile(fd, (DWORD)offset, HIGH_DWORD(offset), (DWORD)bytes,
HIGH_DWORD(bytes));
}
/*----------------------------------------------------------------------------*/
/* global `write` lock for write-txt processing,
* exclusive locking both meta-pages) */
#define LCK_MAXLEN (1u + (size_t)(MAXSSIZE_T))
#define LCK_META_OFFSET 0
#define LCK_META_LEN 0x10000u
#define LCK_BODY_OFFSET LCK_META_LEN
#define LCK_BODY_LEN (LCK_MAXLEN - LCK_BODY_OFFSET + 1u)
#define LCK_META LCK_META_OFFSET, LCK_META_LEN
#define LCK_BODY LCK_BODY_OFFSET, LCK_BODY_LEN
#define LCK_WHOLE 0, LCK_MAXLEN
2017-05-24 01:42:10 +03:00
int mdbx_txn_lock(MDBX_env *env) {
if (flock(env->me_fd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_BODY))
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS;
2017-05-15 12:08:04 +03:00
return mdbx_get_errno_checked();
}
2017-05-24 01:42:10 +03:00
void mdbx_txn_unlock(MDBX_env *env) {
if (!funlock(env->me_fd, LCK_BODY))
mdbx_panic("%s failed: errcode %u", mdbx_func_, GetLastError());
}
/*----------------------------------------------------------------------------*/
/* global `read` lock for readers registration,
* exclusive locking `mti_numreaders` (second) cacheline */
#define LCK_LO_OFFSET 0
#define LCK_LO_LEN offsetof(MDBX_lockinfo, mti_numreaders)
#define LCK_UP_OFFSET LCK_LO_LEN
#define LCK_UP_LEN (MDBX_LOCKINFO_WHOLE_SIZE - LCK_UP_OFFSET)
#define LCK_LOWER LCK_LO_OFFSET, LCK_LO_LEN
#define LCK_UPPER LCK_UP_OFFSET, LCK_UP_LEN
2017-05-24 01:42:10 +03:00
int mdbx_rdt_lock(MDBX_env *env) {
if (env->me_lfd == INVALID_HANDLE_VALUE)
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS; /* readonly database in readonly filesystem */
/* transite from S-? (used) to S-E (locked), e.g. exlcusive lock upper-part */
if (flock(env->me_lfd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_UPPER))
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS;
2017-05-15 12:08:04 +03:00
return mdbx_get_errno_checked();
}
2017-05-24 01:42:10 +03:00
void mdbx_rdt_unlock(MDBX_env *env) {
if (env->me_lfd != INVALID_HANDLE_VALUE) {
/* transite from S-E (locked) to S-? (used), e.g. unlock upper-part */
if (!funlock(env->me_lfd, LCK_UPPER))
mdbx_panic("%s failed: errcode %u", mdbx_func_, GetLastError());
}
}
/*----------------------------------------------------------------------------*/
/* global `initial` lock for lockfile initialization,
* exclusive/shared locking first cacheline */
/* FIXME: locking schema/algo descritpion.
?-? = free
S-? = used
E-?
?-S
?-E = middle
S-S
S-E = locked
E-S
E-E = exclusive
*/
2017-05-24 01:42:10 +03:00
int mdbx_lck_init(MDBX_env *env) {
(void)env;
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS;
}
/* Seize state as exclusive (E-E and returns MDBX_RESULT_TRUE)
* or used (S-? and returns MDBX_RESULT_FALSE), otherwise returns an error */
static int internal_seize_lck(HANDLE lfd) {
int rc;
assert(lfd != INVALID_HANDLE_VALUE);
/* 1) now on ?-? (free), get ?-E (middle) */
mdbx_jitter4testing(false);
if (!flock(lfd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_UPPER)) {
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked() /* 2) something went wrong, give up */;
mdbx_error("%s(%s) failed: errcode %u", mdbx_func_,
"?-?(free) >> ?-E(middle)", rc);
return rc;
}
/* 3) now on ?-E (middle), try E-E (exclusive) */
mdbx_jitter4testing(false);
if (flock(lfd, LCK_EXCLUSIVE | LCK_DONTWAIT, LCK_LOWER))
return MDBX_RESULT_TRUE; /* 4) got E-E (exclusive), done */
/* 5) still on ?-E (middle) */
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
mdbx_jitter4testing(false);
if (rc != ERROR_SHARING_VIOLATION && rc != ERROR_LOCK_VIOLATION) {
/* 6) something went wrong, give up */
2017-05-15 12:08:04 +03:00
if (!funlock(lfd, LCK_UPPER))
mdbx_panic("%s(%s) failed: errcode %u", mdbx_func_,
2017-05-15 12:08:04 +03:00
"?-E(middle) >> ?-?(free)", GetLastError());
return rc;
}
/* 7) still on ?-E (middle), try S-E (locked) */
mdbx_jitter4testing(false);
2017-05-15 12:08:04 +03:00
rc = flock(lfd, LCK_SHARED | LCK_DONTWAIT, LCK_LOWER)
? MDBX_RESULT_FALSE
: mdbx_get_errno_checked();
mdbx_jitter4testing(false);
if (rc != MDBX_RESULT_FALSE)
mdbx_error("%s(%s) failed: errcode %u", mdbx_func_,
"?-E(middle) >> S-E(locked)", rc);
/* 8) now on S-E (locked) or still on ?-E (middle),
* transite to S-? (used) or ?-? (free) */
2017-05-15 12:08:04 +03:00
if (!funlock(lfd, LCK_UPPER))
mdbx_panic("%s(%s) failed: errcode %u", mdbx_func_,
2017-05-15 12:08:04 +03:00
"X-E(locked/middle) >> X-?(used/free)", GetLastError());
/* 9) now on S-? (used, DONE) or ?-? (free, FAILURE) */
return rc;
}
2017-05-24 01:42:10 +03:00
int mdbx_lck_seize(MDBX_env *env) {
int rc;
assert(env->me_fd != INVALID_HANDLE_VALUE);
if (env->me_lfd == INVALID_HANDLE_VALUE) {
/* LY: without-lck mode (e.g. on read-only filesystem) */
mdbx_jitter4testing(false);
if (!flock(env->me_fd, LCK_SHARED | LCK_DONTWAIT, LCK_WHOLE)) {
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
mdbx_error("%s(%s) failed: errcode %u", mdbx_func_, "without-lck", rc);
return rc;
}
return MDBX_RESULT_FALSE;
}
rc = internal_seize_lck(env->me_lfd);
mdbx_jitter4testing(false);
2017-05-24 01:42:10 +03:00
if (rc == MDBX_RESULT_TRUE && (env->me_flags & MDBX_RDONLY) == 0) {
/* Check that another process don't operates in without-lck mode.
* Doing such check by exclusive locking the body-part of db. Should be
* noted:
* - we need an exclusive lock for do so;
* - we can't lock meta-pages, otherwise other process could get an error
* while opening db in valid (non-conflict) mode. */
if (!flock(env->me_fd, LCK_EXCLUSIVE | LCK_DONTWAIT, LCK_BODY)) {
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
mdbx_error("%s(%s) failed: errcode %u", mdbx_func_,
"lock-against-without-lck", rc);
mdbx_jitter4testing(false);
mdbx_lck_destroy(env);
} else {
mdbx_jitter4testing(false);
2017-05-15 12:08:04 +03:00
if (!funlock(env->me_fd, LCK_BODY))
mdbx_panic("%s(%s) failed: errcode %u", mdbx_func_,
2017-05-15 12:08:04 +03:00
"unlock-against-without-lck", GetLastError());
}
}
return rc;
}
/* Transite from exclusive state (E-E) to used (S-?) */
2017-05-24 01:42:10 +03:00
int mdbx_lck_downgrade(MDBX_env *env) {
int rc;
assert(env->me_fd != INVALID_HANDLE_VALUE);
if (env->me_lfd != INVALID_HANDLE_VALUE) {
/* 1) must be at E-E (exclusive), transite to ?_E (middle) */
2017-05-15 12:08:04 +03:00
if (!funlock(env->me_lfd, LCK_LOWER))
mdbx_panic("%s(%s) failed: errcode %u", mdbx_func_,
2017-05-15 12:08:04 +03:00
"E-E(exclusive) >> ?-E(middle)", GetLastError());
/* 2) now at ?-E (middle), transite to S-E (locked) */
if (!flock(env->me_lfd, LCK_SHARED | LCK_DONTWAIT, LCK_LOWER)) {
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked() /* 3) something went wrong, give up */;
return rc;
}
/* 4) got S-E (locked), continue transition to S-? (used) */
2017-05-15 12:08:04 +03:00
if (!funlock(env->me_lfd, LCK_UPPER))
mdbx_panic("%s(%s) failed: errcode %u", mdbx_func_,
2017-05-15 12:08:04 +03:00
"S-E(locked) >> S-?(used)", GetLastError());
}
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS /* 5) now at S-? (used), done */;
}
2017-05-24 01:42:10 +03:00
void mdbx_lck_destroy(MDBX_env *env) {
int rc;
if (env->me_lfd != INVALID_HANDLE_VALUE) {
/* double `unlock` for robustly remove overlapped shared/exclusive locks */
while (funlock(env->me_lfd, LCK_LOWER))
;
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
assert(rc == ERROR_NOT_LOCKED);
(void)rc;
SetLastError(ERROR_SUCCESS);
while (funlock(env->me_lfd, LCK_UPPER))
;
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
assert(rc == ERROR_NOT_LOCKED);
(void)rc;
SetLastError(ERROR_SUCCESS);
}
if (env->me_fd != INVALID_HANDLE_VALUE) {
/* explicitly unlock to avoid latency for other processes (windows kernel
* releases such locks via deferred queues) */
while (funlock(env->me_fd, LCK_BODY))
;
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
assert(rc == ERROR_NOT_LOCKED);
(void)rc;
SetLastError(ERROR_SUCCESS);
while (funlock(env->me_fd, LCK_META))
;
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
assert(rc == ERROR_NOT_LOCKED);
(void)rc;
SetLastError(ERROR_SUCCESS);
while (funlock(env->me_fd, LCK_WHOLE))
;
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
assert(rc == ERROR_NOT_LOCKED);
(void)rc;
SetLastError(ERROR_SUCCESS);
}
}
/*----------------------------------------------------------------------------*/
/* reader checking (by pid) */
2017-05-24 01:42:10 +03:00
int mdbx_rpid_set(MDBX_env *env) {
(void)env;
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS;
}
2017-05-24 01:42:10 +03:00
int mdbx_rpid_clear(MDBX_env *env) {
(void)env;
2017-05-24 01:42:10 +03:00
return MDBX_SUCCESS;
}
2017-04-21 18:26:32 +03:00
/* Checks reader by pid.
*
* Returns:
* MDBX_RESULT_TRUE, if pid is live (unable to acquire lock)
* MDBX_RESULT_FALSE, if pid is dead (lock acquired)
* or otherwise the errcode. */
2017-05-24 01:42:10 +03:00
int mdbx_rpid_check(MDBX_env *env, mdbx_pid_t pid) {
(void)env;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
int rc;
if (hProcess) {
rc = WaitForSingleObject(hProcess, 0);
CloseHandle(hProcess);
} else {
2017-05-15 12:08:04 +03:00
rc = mdbx_get_errno_checked();
}
switch (rc) {
case ERROR_INVALID_PARAMETER:
/* pid seem invalid */
return MDBX_RESULT_FALSE;
case WAIT_OBJECT_0:
/* process just exited */
return MDBX_RESULT_FALSE;
case WAIT_TIMEOUT:
/* pid running */
return MDBX_RESULT_TRUE;
default:
/* failure */
return rc;
}
}