forked from basho/bitcask
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bitcask_lockops.erl
162 lines (144 loc) · 6.03 KB
/
bitcask_lockops.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
%% -------------------------------------------------------------------
%%
%% bitcask: Eric Brewer-inspired key/value store
%%
%% Copyright (c) 2010 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
-module(bitcask_lockops).
-author('Dave Smith <dizzyd@basho.com>').
-author('Justin Sheehy <justin@basho.com>').
-export([acquire/2,
release/1,
delete_stale_lock/2,
read_activefile/2,
write_activefile/2]).
-type lock_types() :: merge | write.
%% @doc Attempt to lock the specified directory with a specific type of lock
%% (merge or write).
-spec acquire(Type::lock_types(), Dirname::string()) -> {ok, reference()} | {error, any()}.
acquire(Type, Dirname) ->
LockFilename = lock_filename(Type, Dirname),
case bitcask_nifs:lock_acquire(LockFilename, 1) of
{ok, Lock} ->
%% Successfully acquired our lock. Update the file with our PID.
ok = bitcask_nifs:lock_writedata(Lock, iolist_to_binary([os:getpid(), " \n"])),
{ok, Lock};
{error, eexist} ->
%% Lock file already exists, but may be stale. Delete it if it's stale
%% and try to acquire again
case delete_stale_lock(LockFilename) of
ok ->
acquire(Type, Dirname);
not_stale ->
{error, locked}
end;
{error, Reason} ->
{error, Reason}
end.
%% @doc Release a previously acquired write/merge lock.
-spec release(reference()) -> ok.
release(Lock) ->
bitcask_nifs:lock_release(Lock).
%% @doc Read the active filename stored in a given lockfile.
-spec read_activefile(Type::lock_types(), Dirname::string()) -> string() | undefined.
read_activefile(Type, Dirname) ->
LockFilename = lock_filename(Type, Dirname),
case bitcask_nifs:lock_acquire(LockFilename, 0) of
{ok, Lock} ->
try
case read_lock_data(Lock) of
{ok, _Pid, ActiveFile} ->
ActiveFile;
_ ->
undefined
end
after
bitcask_nifs:lock_release(Lock)
end;
{error, _Reason} ->
undefined
end.
%% @doc Write a new active filename to an open lockfile.
-spec write_activefile(reference(), string()) -> {ftruncate_error, integer()} | {pwrite_error, integer()} | ok | {error, lock_not_writable}.
write_activefile(Lock, ActiveFilename) ->
Contents = iolist_to_binary([os:getpid(), " ", ActiveFilename, "\n"]),
bitcask_nifs:lock_writedata(Lock, Contents).
delete_stale_lock(Type, Dirname) ->
delete_stale_lock(lock_filename(Type,Dirname)).
%% ===================================================================
%% Internal functions
%% ===================================================================
lock_filename(Type, Dirname) ->
filename:join(Dirname, lists:concat(["bitcask.", Type, ".lock"])).
read_lock_data(Lock) ->
case bitcask_nifs:lock_readdata(Lock) of
{ok, Contents} ->
case re:run(Contents, "([0-9]+) (.*)\n",
[{capture, all_but_first, list}]) of
{match, [OsPid, []]} ->
{ok, OsPid, undefined};
{match, [OsPid, LockedFilename]} ->
{ok, OsPid, LockedFilename};
nomatch ->
{error, invalid_data}
end;
{error, Reason} ->
{error, Reason}
end.
os_pid_exists(Pid) ->
%% Use kill -0 trick to determine if a process exists. This _should_ be
%% portable across all unix variants we are interested in.
[] == os:cmd(io_lib:format("kill -0 ~s", [Pid])).
delete_stale_lock(Filename) ->
%% Open the lock for read-only access. We do this to avoid race-conditions
%% with other O/S processes that are attempting the same task. Opening a
%% fd and holding it open until AFTER the unlink ensures that the file we
%% initially read is the same one we are deleting.
case bitcask_nifs:lock_acquire(Filename, 0) of
{ok, Lock} ->
try
case read_lock_data(Lock) of
{ok, OsPid, _LockedFilename} ->
case os_pid_exists(OsPid) of
true ->
%% The lock IS NOT stale, so we can't delete it.
not_stale;
false ->
%% The lock IS stale; delete the file.
file:delete(Filename),
ok
end;
{error, Reason} ->
error_logger:error_msg("Failed to read lock data from ~s: ~p\n",
[Filename, Reason]),
not_stale
end
after
bitcask_nifs:lock_release(Lock)
end;
{error, enoent} ->
%% Failed to open the lock for reading, but only because it doesn't exist
%% any longer. Treat this as a successful delete; the lock file existed
%% when we started.
ok;
{error, Reason} ->
%% Failed to open the lock for reading due to other errors.
error_logger:error_msg("Failed to open lock file ~s: ~p\n",
[Filename, Reason]),
not_stale
end.