reflock: first version of combined reference counter and lock

This commit is contained in:
Bert Belder 2017-09-23 13:52:53 +02:00
parent 4a0354a6d2
commit 6029e38d79
3 changed files with 219 additions and 1 deletions

View File

@ -3,6 +3,7 @@
#include "error.h"
#include "init.h"
#include "nt.h"
#include "reflock.h"
#include "win.h"
static bool _initialized = false;
@ -19,7 +20,7 @@ static int _init_winsock(void) {
}
static int _init_once(void) {
if (_init_winsock() < 0 || nt_init() < 0)
if (_init_winsock() < 0 || nt_init() < 0 || reflock_global_init() < 0)
return -1;
_initialized = true;

194
src/reflock.c Normal file
View File

@ -0,0 +1,194 @@
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include "error.h"
#include "nt.h"
#include "reflock.h"
#include "util.h"
#include "win.h"
typedef union _reflock_private {
volatile uint32_t counter;
uint64_t dummy;
struct {
uint32_t lock;
uint32_t delete;
} events;
} _reflock_private_t;
static const uint32_t _CTR_REF = (uint32_t) 1;
static const uint32_t _CTR_LOCK = (uint32_t) 1 << 16;
static const uint32_t _CTR_DELETE = (uint32_t) 1 << 31;
static const uint32_t _CTR_REF_MASK = ((uint32_t) 1 << 16) - 1;
static const uint32_t _CTR_LOCK_MASK = (((uint32_t) 1 << 15) - 1) << 16;
/* clang-format off */
#define _TOK_REFERENCED 0x01
#define _TOK_LOCK_ANNOUNCED 0x02
#define _TOK_DELETE_ANNOUNCED 0x04
#define _TOK_MUST_WAIT 0x08
#define _TOK_NO_WAIT 0x10
#define _TOK_LOCKED 0x20
#define _TOK_DELETED 0x40
/* clang-format on */
static HANDLE _global_event_handle = NULL;
uint32_t _sync_add(volatile uint32_t* target, uint32_t addend) {
static_assert(sizeof(*target) == sizeof(long), "");
return InterlockedAdd((volatile long*) target, addend) - addend;
}
int reflock_global_init(void) {
NTSTATUS status =
NtCreateKeyedEvent(&_global_event_handle, ~(ACCESS_MASK) 0, NULL, 0);
if (status != STATUS_SUCCESS)
return_error(-1, RtlNtStatusToDosError(status));
return 0;
}
void reflock_init(reflock_t* reflock) {
memset(reflock, 0, sizeof *reflock);
}
static _reflock_private_t* _reflock_private(reflock_t* reflock) {
static_assert(sizeof(reflock_t) == sizeof(_reflock_private_t), "");
return (_reflock_private_t*) reflock;
}
static void _wait_for_event(const uint32_t* address) {
NTSTATUS status =
NtWaitForKeyedEvent(_global_event_handle, (PVOID) address, FALSE, NULL);
if (status != STATUS_SUCCESS)
abort();
}
static void _signal_event(const uint32_t* address) {
NTSTATUS status =
NtReleaseKeyedEvent(_global_event_handle, (PVOID) address, FALSE, NULL);
if (status != STATUS_SUCCESS)
abort();
}
reflock_token_t reflock_ref_and_announce_lock(reflock_t* reflock) {
_reflock_private_t* priv = _reflock_private(reflock);
uint32_t prev = _sync_add(&priv->counter, _CTR_REF | _CTR_LOCK);
uint32_t prev_lock_count = prev & _CTR_LOCK_MASK;
if (prev & _CTR_DELETE)
abort();
if (prev_lock_count == 0)
return _TOK_REFERENCED | _TOK_LOCK_ANNOUNCED | _TOK_NO_WAIT;
else
return _TOK_REFERENCED | _TOK_LOCK_ANNOUNCED | _TOK_MUST_WAIT;
}
reflock_token_t reflock_lock(reflock_t* reflock, reflock_token_t token) {
_reflock_private_t* priv = _reflock_private(reflock);
switch (token) {
case _TOK_REFERENCED: {
/* Lock hasn't been announced; increase lock count and wait if necessary.
*/
uint32_t prev_lock_count =
_sync_add(&priv->counter, _CTR_LOCK) & _CTR_LOCK_MASK;
if (prev_lock_count > 0)
_wait_for_event(&priv->events.lock);
break;
}
case _TOK_REFERENCED | _TOK_LOCK_ANNOUNCED | _TOK_MUST_WAIT:
/* Lock has been announced; wait for the thread currently owning the lock
* to signal the event. */
_wait_for_event(&priv->events.lock);
break;
case _TOK_REFERENCED | _TOK_LOCK_ANNOUNCED | _TOK_NO_WAIT:
/* There were no waiters when the lock was announced; no need to wait for
* an event. */
break;
default:
abort();
}
return _TOK_REFERENCED | _TOK_LOCKED;
}
void _unlock_signal_event(_reflock_private_t* priv, uint32_t counter) {
if (counter & _CTR_LOCK_MASK) {
/* Wake a thread that is waiting to acquire the lock. */
_signal_event(&priv->events.lock);
} else if ((counter & _CTR_DELETE) && !(counter & _CTR_REF_MASK)) {
/* Wake the thread that is waiting to delete the lock. */
_signal_event(&priv->events.delete);
}
}
reflock_token_t reflock_unlock(reflock_t* reflock, reflock_token_t token) {
_reflock_private_t* priv = _reflock_private(reflock);
uint32_t add = -(int32_t) _CTR_LOCK;
uint32_t prev_counter = _sync_add(&priv->counter, add);
_unlock_signal_event(priv, prev_counter + add);
unused(token);
assert(token == (_TOK_REFERENCED | _TOK_LOCKED));
return _TOK_REFERENCED;
}
void reflock_unref_and_unlock(reflock_t* reflock, reflock_token_t token) {
_reflock_private_t* priv = _reflock_private(reflock);
uint32_t add = -(int32_t)(_CTR_REF | _CTR_LOCK);
uint32_t prev_counter = _sync_add(&priv->counter, add);
_unlock_signal_event(priv, prev_counter + add);
unused(token);
assert(token == (_TOK_REFERENCED | _TOK_LOCKED));
}
reflock_token_t reflock_announce_delete(reflock_t* reflock) {
_reflock_private_t* priv = _reflock_private(reflock);
uint32_t prev = _sync_add(&priv->counter, _CTR_DELETE);
uint32_t ref_count = prev & _CTR_REF_MASK;
if (prev & _CTR_DELETE)
abort();
if (ref_count == 0)
return _TOK_DELETE_ANNOUNCED | _TOK_NO_WAIT;
else
return _TOK_DELETE_ANNOUNCED | _TOK_MUST_WAIT;
}
void reflock_delete(reflock_t* reflock, reflock_token_t token) {
_reflock_private_t* priv = _reflock_private(reflock);
switch (token) {
case _TOK_DELETE_ANNOUNCED | _TOK_MUST_WAIT:
/* The lock was owned when deletion was announced; wait for the last lock
* owner to signal the delete event. */
_wait_for_event(&priv->events.delete);
break;
case _TOK_DELETE_ANNOUNCED | _TOK_NO_WAIT:
/* There were no other waiters when deletion was announced; there's no
* need to wait for an event. */
break;
default:
abort();
}
priv->counter = 0;
}

23
src/reflock.h Normal file
View File

@ -0,0 +1,23 @@
#include <stdint.h>
typedef struct reflock {
uint64_t internal;
} reflock_t;
typedef uint32_t reflock_token_t;
EPOLL_INTERNAL int reflock_global_init(void);
EPOLL_INTERNAL void reflock_init(reflock_t* reflock);
EPOLL_INTERNAL reflock_token_t
reflock_ref_and_announce_lock(reflock_t* reflock);
EPOLL_INTERNAL reflock_token_t reflock_lock(reflock_t* reflock,
reflock_token_t token);
EPOLL_INTERNAL reflock_token_t reflock_unlock(reflock_t* reflock,
reflock_token_t token);
EPOLL_INTERNAL void reflock_unref_and_unlock(reflock_t* reflock,
reflock_token_t token);
EPOLL_INTERNAL reflock_token_t reflock_announce_delete(reflock_t* reflock);
EPOLL_INTERNAL void reflock_delete(reflock_t* reflock, reflock_token_t token);