@ -0,0 +1,20 @@ | |||
#include "erl_nif.h" | |||
static ERL_NIF_TERM pidToInt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||
ErlNifUInt64 TermInt = (ErlNifUInt64)argv[0]; | |||
return enif_make_uint64(env, TermInt); | |||
} | |||
static ERL_NIF_TERM intToPid(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||
ErlNifUInt64 Pid; | |||
if (!enif_get_uint64(env, argv[0], &Pid)) | |||
return enif_make_badarg(env); | |||
return (ERL_NIF_TERM)Pid; | |||
} | |||
static ErlNifFunc nif_funcs[] = { | |||
{"pidToInt", 1, pidToInt}, | |||
{"intToPid", 1, intToPid} | |||
}; | |||
ERL_NIF_INIT(eGPidInt, nif_funcs, NULL, NULL, NULL, NULL); |
@ -0,0 +1,7 @@ | |||
{port_specs, [ | |||
{"../../priv/eGPidInt.so", ["*.c"]} | |||
]}. | |||
@ -0,0 +1,83 @@ | |||
#include "erl_nif.h" | |||
#include <stdatomic.h> | |||
atomic_ullong LockSlot[1048576]; | |||
ERL_NIF_TERM atomTrue; | |||
ERL_NIF_TERM atomFalse; | |||
ERL_NIF_TERM atomUndefined; | |||
int nifLoad(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM){ | |||
atomTrue = enif_make_atom(env, "true"); | |||
atomFalse = enif_make_atom(env, "false"); | |||
atomUndefined = enif_make_atom(env, "undefined"); | |||
return 0; | |||
} | |||
ERL_NIF_TERM tryLock(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]){ | |||
unsigned int KeyIx; | |||
if (!enif_get_uint(env, argv[0], &KeyIx)){ | |||
return enif_make_badarg(env); | |||
} | |||
ErlNifPid ThePid; | |||
enif_self(env, &ThePid); | |||
atomic_ullong Expected = 0; | |||
atomic_ullong Val = (atomic_ullong)(ThePid.pid); | |||
if (atomic_compare_exchange_strong(&(LockSlot[KeyIx]), &Expected, Val)){ | |||
return atomTrue; | |||
}else{ | |||
ThePid.pid = (ERL_NIF_TERM)Expected; | |||
if (enif_is_process_alive(env, &ThePid)){ | |||
return atomFalse; | |||
}else{ | |||
if (atomic_compare_exchange_strong(&LockSlot[KeyIx], &Expected, Val)) { | |||
return atomTrue; | |||
}else{ | |||
return atomFalse; | |||
} | |||
} | |||
} | |||
} | |||
ERL_NIF_TERM releaseLock(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]){ | |||
unsigned int KeyIx; | |||
if (!enif_get_uint(env, argv[0], &KeyIx)){ | |||
return enif_make_badarg(env); | |||
} | |||
ErlNifPid ThePid; | |||
enif_self(env, &ThePid); | |||
atomic_ullong Expected = (atomic_ullong)(ThePid.pid); | |||
atomic_ullong Val = 0; | |||
if (atomic_compare_exchange_strong(&LockSlot[KeyIx], &Expected, Val)){ | |||
return atomTrue; | |||
}else{ | |||
return atomFalse; | |||
} | |||
} | |||
ERL_NIF_TERM getLockPid(ErlNifEnv *env, int, const ERL_NIF_TERM argv[]){ | |||
unsigned int KeyIx; | |||
if (!enif_get_uint(env, argv[0], &KeyIx)){ | |||
return enif_make_badarg(env); | |||
} | |||
ErlNifPid ThePid; | |||
atomic_ullong Var = atomic_load(&(LockSlot[KeyIx])); | |||
if (Var > 0){ | |||
ThePid.pid = (ERL_NIF_TERM)Var; | |||
return enif_make_pid(env, &ThePid); | |||
}else{ | |||
return atomUndefined; | |||
} | |||
} | |||
static ErlNifFunc nifFuncs[] = | |||
{ | |||
{"tryLock", 1, tryLock}, | |||
{"releaseLock", 1, releaseLock}, | |||
{"getLockPid", 1, getLockPid} | |||
}; | |||
ERL_NIF_INIT(eNifLock, nifFuncs, &nifLoad, NULL, NULL, NULL) |
@ -0,0 +1,7 @@ | |||
{port_specs, [ | |||
{"../../priv/eNifLock.so", ["*.c"]} | |||
]}. | |||
@ -0,0 +1,148 @@ | |||
-module(eCLock). | |||
-include("eGLock.hrl"). | |||
-define(CASE(Cond, Then, That), case Cond of true -> Then; _ -> That end). | |||
-export([ | |||
tryLock/1 | |||
, tryLock/2 | |||
, releaseLock/1 | |||
, lockApply/2 | |||
, lockApply/3 | |||
]). | |||
-spec tryLock(KeyOrKeys :: term() | [term()]) -> true | ltimeout. | |||
tryLock(KeyOrKeys) -> | |||
tryLock(KeyOrKeys, ?LockTimeOut). | |||
tryLock(KeyOrKeys, TimeOut) -> | |||
case is_list(KeyOrKeys) of | |||
true -> | |||
KeyIxs = getKexIxs(KeyOrKeys, []), | |||
doTryLocks(KeyIxs, TimeOut); | |||
_ -> | |||
doTryLock(erlang:phash2(KeyOrKeys, ?eGLockSize), TimeOut) | |||
end. | |||
doTryLock(KeyIx, TimeOut) -> | |||
case eNifLock:tryLock(KeyIx) of | |||
true -> | |||
true; | |||
_ -> | |||
loopLock(KeyIx, TimeOut) | |||
end. | |||
loopLock(KeyIx, TimeOut) -> | |||
receive | |||
after ?ReTryTime -> | |||
LTimeOut = ?CASE(TimeOut == infinity, TimeOut, TimeOut - ?ReTryTime), | |||
case LTimeOut >= 0 of | |||
true -> | |||
case eNifLock:tryLock(KeyIx) of | |||
true -> | |||
true; | |||
_ -> | |||
loopLock(KeyIx, LTimeOut) | |||
end; | |||
_ -> | |||
ltimeout | |||
end | |||
end. | |||
doTryLocks(KeyIxs, TimeOut) -> | |||
case tryLockAll(KeyIxs, []) of | |||
true -> | |||
true; | |||
_ -> | |||
loopLocks(KeyIxs, TimeOut) | |||
end. | |||
loopLocks(KeyIxs, TimeOut) -> | |||
receive | |||
after ?ReTryTime -> | |||
LTimeOut = ?CASE(TimeOut == infinity, TimeOut, TimeOut - ?ReTryTime), | |||
case LTimeOut >= 0 of | |||
true -> | |||
case tryLockAll(KeyIxs, []) of | |||
true -> | |||
true; | |||
_ -> | |||
loopLocks(KeyIxs, LTimeOut) | |||
end; | |||
_ -> | |||
ltimeout | |||
end | |||
end. | |||
-spec releaseLock(KeyOrKeys :: term() | [term()]) -> ok. | |||
releaseLock(KeyOrKeys) -> | |||
case is_list(KeyOrKeys) of | |||
true -> | |||
KeyIxs = getKexIxs(KeyOrKeys, []), | |||
[eNifLock:releaseLock(OneKeyIx) || OneKeyIx <- KeyIxs], | |||
ok; | |||
_ -> | |||
eNifLock:releaseLock(erlang:phash2(KeyOrKeys, ?eGLockSize)), | |||
ok | |||
end. | |||
-spec lockApply(KeyOrKeys :: term() | [term()], MFAOrFun :: {M :: atom(), F :: atom(), Args :: list()} | {Fun :: function(), Args :: list()}) -> term() | {error, ltimeout} | {error, {lock_apply_error, term()}}. | |||
lockApply(KeyOrKeys, MFAOrFun) -> | |||
lockApply(KeyOrKeys, MFAOrFun, ?LockTimeOut). | |||
-spec lockApply(KeyOrKeys :: term() | [term()], MFAOrFun :: {M :: atom(), F :: atom(), Args :: list()} | {Fun :: function(), Args :: list()}, TimeOut :: integer() | infinity) -> term(). | |||
lockApply(KeyOrKeys, MFAOrFun, TimeOut) -> | |||
case is_list(KeyOrKeys) of | |||
true -> | |||
KeyIxs = getKexIxs(KeyOrKeys, []), | |||
case doTryLocks(KeyIxs, TimeOut) of | |||
true -> | |||
try doApply(MFAOrFun) | |||
catch C:R:S -> | |||
{error, {lock_apply_error, {C, R, S}}} | |||
after | |||
[eNifLock:releaseLock(OneKeyIx) || OneKeyIx <- KeyIxs], | |||
ok | |||
end; | |||
ltimeout -> | |||
{error, ltimeout} | |||
end; | |||
_ -> | |||
KeyIx = erlang:phash2(KeyOrKeys, ?eGLockSize), | |||
case doTryLock(KeyIx, TimeOut) of | |||
true -> | |||
try doApply(MFAOrFun) | |||
catch C:R:S -> | |||
{error, {lock_apply_error, {C, R, S}}} | |||
after | |||
eNifLock:releaseLock(KeyIx), | |||
ok | |||
end; | |||
ltimeout -> | |||
{error, ltimeout} | |||
end | |||
end. | |||
getKexIxs([], IxAcc) -> IxAcc; | |||
getKexIxs([Key | Keys], IxAcc) -> | |||
KeyIx = erlang:phash2(Key, ?eGLockSize), | |||
getKexIxs(Keys, ?CASE(lists:member(KeyIx, IxAcc), IxAcc, [KeyIx | IxAcc])). | |||
tryLockAll([], _LockAcc) -> | |||
true; | |||
tryLockAll([KeyIx | KeyIxs], LockAcc) -> | |||
case eNifLock:tryLock(KeyIx) of | |||
true -> | |||
tryLockAll(KeyIxs, [KeyIx | LockAcc]); | |||
_ -> | |||
[eNifLock:releaseLock(OneLock) || OneLock <- LockAcc], | |||
false | |||
end. | |||
doApply({M, F, A}) -> | |||
apply(M, F, A); | |||
doApply({Fun, Args}) -> | |||
apply(Fun, Args). | |||
@ -0,0 +1,32 @@ | |||
-module(eNifLock). | |||
-export([tryLock/1, releaseLock/1, getLockPid/1]). | |||
-on_load(init/0). | |||
init() -> | |||
SoName = | |||
case code:priv_dir(?MODULE) of | |||
{error, _} -> | |||
case code:which(?MODULE) of | |||
Filename when is_list(Filename) -> | |||
filename:join([filename:dirname(Filename), "../priv", "eNifLock"]); | |||
_ -> | |||
filename:join("../priv", "eNifLock") | |||
end; | |||
Dir -> | |||
filename:join(Dir, "eNifLock") | |||
end, | |||
erlang:load_nif(SoName, 0). | |||
-spec tryLock(KeyIx :: non_neg_integer()) -> true | false. | |||
tryLock(_KeyIx) -> | |||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, ?LINE}]}). | |||
-spec releaseLock(KeyIx :: non_neg_integer()) -> true | false. | |||
releaseLock(_KeyIx) -> | |||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, ?LINE}]}). | |||
-spec getLockPid(KeyIx :: non_neg_integer()) -> pid() | undefined. | |||
getLockPid(_KeyIx) -> | |||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, ?LINE}]}). |