I have been thinking about implementing a global lock in erlang to solve some scenario requirements, so I wrote the following code.
-define(EtsGLockKey, '$EtsGLockKey').
-define(LockTimeOut, 5000).
-define(ReTryTime, 10).
start() ->
ets:new(?EtsGLockKey, [named_table, set, public, {write_concurrency, auto}, {read_concurrency, true}]).
-spec lockApplys(KeyOrKeys :: tuple() | [tuple()], MFAOrFun :: {M :: atom(), F :: atom(), Args :: list()} | {Fun :: function(), Args :: list()}, TimeOut :: integer() | infinity, FirstTime :: integer()) -> term().
lockApplys(Keys, MFAOrFun, TimeOut, FirstTime) ->
case ets:insert_new(?EtsGLockKey, Keys) of
true ->
try doApply(MFAOrFun)
catch C:R:S ->
{error, {lock_apply_error, {C, R, S}}}
after
[ets:delete(?EtsGLockKey, element(1, OneKey)) || OneKey <- Keys],
ok
end;
_ ->
loopTrys(Keys, MFAOrFun, TimeOut, FirstTime)
end.
loopTrys(Keys, MFAOrFun, TimeOut, FirstTime) ->
receive
after ?ReTryTime ->
[Key | _] = Keys,
case ets:lookup(?EtsGLockKey, element(1, Key)) of
[] ->
lockApplys(Keys, MFAOrFun, TimeOut, FirstTime);
_ ->
case TimeOut of
infinity ->
loopTrys(Keys, MFAOrFun, TimeOut, FirstTime);
_ ->
LTimeOut = TimeOut - abs(erlang:system_time(millisecond) - FirstTime),
case LTimeOut =< 0 of
true ->
{error, {lock_timeout, Keys}};
_ ->
loopTrys(Keys, MFAOrFun, TimeOut, FirstTime)
end
end
end
end.
doApply({M, F, A}) ->
apply(M, F, A);
doApply({Fun, Args}) ->
apply(Fun, Args).
This code appears to be fine, but if the process is killed while executing a function inside the lock, the lock will not be released. I think if there’s an erlang:be_monitor(mgrpid) function that lets some administrative process monitor the state of that process, and if that process state is unexpected then the administrative process can help with some things but there’s no erlang:be_monitor(mgrpid), This function can be supported? Or is there a better way to implement global locks.