19
19
20
20
-export_type ([spinlock / 0 ]).
21
21
22
+ % % @equiv new([])
22
23
-spec new () -> # spinlock {}.
23
24
new () ->
24
25
new ([]).
25
26
26
- -spec new ([option ()]) -> # spinlock {}.
27
+ % % @doc Creates a new spinlock instance with the given options.
28
+ % %
29
+ % % Possible options are:
30
+ % % <ul>
31
+ % % <li>`{max_retry, MaxRetry}'
32
+ % % <p>The lock is forecibly released after `MaxRetry' number of attempts.</p>
33
+ % % </li>
34
+ % % <li>`{atomics_ref, AtomicsRef}'
35
+ % % <p>Uses the first two index of the given atomics array to store the state of the lock.
36
+ % % If you want to use spinlock to implement transactions for an atomics array, you can use this
37
+ % % option to avoid creating an extra atomics array.</p>
38
+ % % </li>
39
+ % % </ul>
40
+ -spec new (Options :: [option ()]) -> # spinlock {}.
27
41
new (Options ) ->
28
42
MaxRetry = proplists :get_value (max_retry , Options , 100 _000 ),
29
43
is_integer (MaxRetry ) andalso MaxRetry > 0 orelse error (badarg ),
@@ -37,20 +51,35 @@ new(Options) ->
37
51
max_retry = MaxRetry
38
52
}.
39
53
40
- -spec acquire (# spinlock {}) -> lock_id ().
54
+ % % @doc Acquires a lock for the current process.
55
+ % %
56
+ % % This will busy-wait until a lock can be acquired, or a maximum configured number
57
+ % % of attemps is reached. Returned `lock_id' is used to release the lock later.
58
+ -spec acquire (Lock :: # spinlock {}) -> lock_id ().
41
59
acquire (Lock = # spinlock {ref = Ref }) ->
42
60
LockId = atomics :add_get (Ref , 1 , 1 ),
43
61
spin (Lock , LockId - 1 , undefined , 0 ).
44
62
45
- -spec release (# spinlock {}, lock_id ()) -> ok | {error , already_released | invalid_lock_id }.
63
+ % % @doc Releases an already acquired lock.
64
+ % %
65
+ % % This will release the lock for the given LockId.
66
+ % % Returns `ok' if the lock is successfully released. Otherwise returns
67
+ % % `{error, already_released}' if the lock is already released
68
+ % % or `{error, invalid_lock_state}' if the given LockId has never been acquired.
69
+ -spec release (Lock :: # spinlock {}, LockId :: lock_id ()) ->
70
+ ok | {error , already_released | invalid_lock_id }.
46
71
release (# spinlock {ref = Ref }, LockId ) ->
47
72
case atomics :compare_exchange (Ref , 2 , LockId - 1 , LockId ) of
48
73
ok -> ok ;
49
74
Id when Id >= LockId -> {error , already_released };
50
75
_ -> {error , invalid_lock_id }
51
76
end .
52
77
53
- -spec transaction (# spinlock {}, fun (() -> any ())) -> any ().
78
+ % % @doc Executes the given function in a transaction.
79
+ % %
80
+ % % Acquires the lock, executes the given function, and releases the lock when the function has returend.
81
+ % % The lock is released even if the function fails with an exception.
82
+ -spec transaction (Lock :: # spinlock {}, Fun :: fun (() -> any ())) -> any ().
54
83
transaction (Lock , Fun ) ->
55
84
LockId = acquire (Lock ),
56
85
try Fun () of
@@ -59,7 +88,11 @@ transaction(Lock, Fun) ->
59
88
release (Lock , LockId )
60
89
end .
61
90
62
- - spec status (# spinlock {}) ->
91
+ % % @doc Returns the status of the given lock.
92
+ % %
93
+ % % You can use this function for debugging purposes, to check the current status of the lock,
94
+ % % and to see how many process are waiting to acquire the lock.
95
+ - spec status (Lock :: # spinlock {}) ->
63
96
#{released => non_neg_integer (), is_locked => boolean (), waiting => non_neg_integer ()}.
64
97
status (# spinlock {ref = Ref }) ->
65
98
Released = atomics :get (Ref , 2 ),
@@ -70,13 +103,17 @@ status(#spinlock{ref = Ref}) ->
70
103
waiting => max (Total - Released - 1 , 0 )
71
104
}.
72
105
106
+ % %%-------------------------------------------------------------------
107
+ % % Internal functions
108
+ % %%-------------------------------------------------------------------
109
+
73
110
spin (Lock = # spinlock {ref = Ref , max_retry = MaxRetry }, ExpectedId , ReleasedId , MaxRetry ) ->
74
111
atomics :compare_exchange (Ref , 2 , ReleasedId , ReleasedId + 1 ),
75
112
spin (Lock , ExpectedId , ReleasedId , 0 );
76
113
spin (Lock = # spinlock {ref = Ref }, ExpectedId , LastReleasedId , Retry ) ->
77
114
case atomics :get (Ref , 2 ) of
78
115
LastReleasedId ->
79
- LastReleasedId < ExpectedId - 10 andalso erlang :yield (),
116
+ LastReleasedId < ExpectedId - 1 andalso erlang :yield (),
80
117
spin (Lock , ExpectedId , LastReleasedId , Retry + 1 );
81
118
ExpectedId ->
82
119
ExpectedId + 1 ;
0 commit comments