From 27ea5a120fddeac8d22fae49e34020b24f7b9a41 Mon Sep 17 00:00:00 2001 From: SisMaker <1713699517@qq.com> Date: Sat, 20 Apr 2024 02:14:33 +0800 Subject: [PATCH] =?UTF-8?q?ft:=20=E6=B7=BB=E5=8A=A0clock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c_src/eCLock/eCLock.c | 20 +++++ c_src/eCLock/rebar.config | 7 ++ c_src/eNifLock/eNifLock.c | 83 ++++++++++++++++++++ c_src/eNifLock/rebar.config | 7 ++ src/eCLock.erl | 148 ++++++++++++++++++++++++++++++++++++ src/eNifLock.erl | 32 ++++++++ 6 files changed, 297 insertions(+) create mode 100644 c_src/eCLock/eCLock.c create mode 100644 c_src/eCLock/rebar.config create mode 100644 c_src/eNifLock/eNifLock.c create mode 100644 c_src/eNifLock/rebar.config create mode 100644 src/eCLock.erl create mode 100644 src/eNifLock.erl diff --git a/c_src/eCLock/eCLock.c b/c_src/eCLock/eCLock.c new file mode 100644 index 0000000..f3c0f6b --- /dev/null +++ b/c_src/eCLock/eCLock.c @@ -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); \ No newline at end of file diff --git a/c_src/eCLock/rebar.config b/c_src/eCLock/rebar.config new file mode 100644 index 0000000..e830bd8 --- /dev/null +++ b/c_src/eCLock/rebar.config @@ -0,0 +1,7 @@ +{port_specs, [ + {"../../priv/eGPidInt.so", ["*.c"]} +]}. + + + + diff --git a/c_src/eNifLock/eNifLock.c b/c_src/eNifLock/eNifLock.c new file mode 100644 index 0000000..de63ada --- /dev/null +++ b/c_src/eNifLock/eNifLock.c @@ -0,0 +1,83 @@ +#include "erl_nif.h" +#include + +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) \ No newline at end of file diff --git a/c_src/eNifLock/rebar.config b/c_src/eNifLock/rebar.config new file mode 100644 index 0000000..f04da96 --- /dev/null +++ b/c_src/eNifLock/rebar.config @@ -0,0 +1,7 @@ +{port_specs, [ + {"../../priv/eNifLock.so", ["*.c"]} +]}. + + + + diff --git a/src/eCLock.erl b/src/eCLock.erl new file mode 100644 index 0000000..8ddb17e --- /dev/null +++ b/src/eCLock.erl @@ -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). + + + diff --git a/src/eNifLock.erl b/src/eNifLock.erl new file mode 100644 index 0000000..491445d --- /dev/null +++ b/src/eNifLock.erl @@ -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}]}). \ No newline at end of file