@@ -3,10 +3,12 @@ extern crate winapi;
3
3
4
4
use super :: { KVStore , TransactionalWrite } ;
5
5
6
+ use std:: collections:: HashMap ;
6
7
use std:: fs;
7
8
use std:: io:: { BufReader , BufWriter , Read , Write } ;
8
9
use std:: path:: { Path , PathBuf } ;
9
10
use std:: str:: FromStr ;
11
+ use std:: sync:: { Arc , Mutex , RwLock } ;
10
12
11
13
#[ cfg( not( target_os = "windows" ) ) ]
12
14
use std:: os:: unix:: io:: AsRawFd ;
@@ -38,11 +40,13 @@ fn path_to_windows_str<T: AsRef<OsStr>>(path: T) -> Vec<winapi::shared::ntdef::W
38
40
39
41
pub struct FilesystemStore {
40
42
dest_dir : PathBuf ,
43
+ locks : Mutex < HashMap < ( String , String ) , Arc < RwLock < ( ) > > > > ,
41
44
}
42
45
43
46
impl FilesystemStore {
44
47
pub fn new ( dest_dir : PathBuf ) -> Self {
45
- Self { dest_dir }
48
+ let locks = Mutex :: new ( HashMap :: new ( ) ) ;
49
+ Self { dest_dir, locks }
46
50
}
47
51
}
48
52
@@ -51,20 +55,34 @@ impl KVStore for FilesystemStore {
51
55
type Writer = FilesystemWriter ;
52
56
53
57
fn read ( & self , namespace : & str , key : & str ) -> std:: io:: Result < Self :: Reader > {
58
+ let mut outer_lock = self . locks . lock ( ) . unwrap ( ) ;
59
+ let lock_key = ( namespace. to_string ( ) , key. to_string ( ) ) ;
60
+ let inner_lock_ref = Arc :: clone ( & outer_lock. entry ( lock_key) . or_default ( ) ) ;
61
+
54
62
let mut dest_file = self . dest_dir . clone ( ) ;
55
63
dest_file. push ( namespace) ;
56
64
dest_file. push ( key) ;
57
- FilesystemReader :: new ( dest_file)
65
+ FilesystemReader :: new ( dest_file, inner_lock_ref )
58
66
}
59
67
60
68
fn write ( & self , namespace : & str , key : & str ) -> std:: io:: Result < Self :: Writer > {
69
+ let mut outer_lock = self . locks . lock ( ) . unwrap ( ) ;
70
+ let lock_key = ( namespace. to_string ( ) , key. to_string ( ) ) ;
71
+ let inner_lock_ref = Arc :: clone ( & outer_lock. entry ( lock_key) . or_default ( ) ) ;
72
+
61
73
let mut dest_file = self . dest_dir . clone ( ) ;
62
74
dest_file. push ( namespace) ;
63
75
dest_file. push ( key) ;
64
- FilesystemWriter :: new ( dest_file)
76
+ FilesystemWriter :: new ( dest_file, inner_lock_ref )
65
77
}
66
78
67
79
fn remove ( & self , namespace : & str , key : & str ) -> std:: io:: Result < bool > {
80
+ let mut outer_lock = self . locks . lock ( ) . unwrap ( ) ;
81
+ let lock_key = ( namespace. to_string ( ) , key. to_string ( ) ) ;
82
+ let inner_lock_ref = Arc :: clone ( & outer_lock. entry ( lock_key. clone ( ) ) . or_default ( ) ) ;
83
+
84
+ let _guard = inner_lock_ref. write ( ) . unwrap ( ) ;
85
+
68
86
let mut dest_file = self . dest_dir . clone ( ) ;
69
87
dest_file. push ( namespace) ;
70
88
dest_file. push ( key) ;
@@ -96,6 +114,21 @@ impl KVStore for FilesystemStore {
96
114
return Err ( std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , "Removing key failed" ) ) ;
97
115
}
98
116
117
+ if Arc :: strong_count ( & inner_lock_ref) == 2 {
118
+ // It's safe to remove the lock entry if we're the only one left holding a strong
119
+ // reference. Checking this is necessary to ensure we continue to distribute references to the
120
+ // same lock as long as some Writers/Readers are around. However, we still want to
121
+ // clean up the table when possible.
122
+ //
123
+ // Note that this by itself is still leaky as lock entries will remain when more Readers/Writers are
124
+ // around, but is preferable to doing nothing *or* something overly complex such as
125
+ // implementing yet another RAII structure just for this pupose.
126
+ outer_lock. remove ( & lock_key) ;
127
+ }
128
+
129
+ // Garbage collect all lock entries that are not referenced anymore.
130
+ outer_lock. retain ( |_, v| Arc :: strong_count ( & v) > 1 ) ;
131
+
99
132
Ok ( true )
100
133
}
101
134
@@ -134,18 +167,20 @@ impl KVStore for FilesystemStore {
134
167
135
168
pub struct FilesystemReader {
136
169
inner : BufReader < fs:: File > ,
170
+ lock_ref : Arc < RwLock < ( ) > > ,
137
171
}
138
172
139
173
impl FilesystemReader {
140
- pub fn new ( dest_file : PathBuf ) -> std:: io:: Result < Self > {
174
+ fn new ( dest_file : PathBuf , lock_ref : Arc < RwLock < ( ) > > ) -> std:: io:: Result < Self > {
141
175
let f = fs:: File :: open ( dest_file. clone ( ) ) ?;
142
176
let inner = BufReader :: new ( f) ;
143
- Ok ( Self { inner } )
177
+ Ok ( Self { inner, lock_ref } )
144
178
}
145
179
}
146
180
147
181
impl Read for FilesystemReader {
148
182
fn read ( & mut self , buf : & mut [ u8 ] ) -> std:: io:: Result < usize > {
183
+ let _guard = self . lock_ref . read ( ) . unwrap ( ) ;
149
184
self . inner . read ( buf)
150
185
}
151
186
}
@@ -155,10 +190,11 @@ pub struct FilesystemWriter {
155
190
parent_directory : PathBuf ,
156
191
tmp_file : PathBuf ,
157
192
tmp_writer : BufWriter < fs:: File > ,
193
+ lock_ref : Arc < RwLock < ( ) > > ,
158
194
}
159
195
160
196
impl FilesystemWriter {
161
- pub fn new ( dest_file : PathBuf ) -> std:: io:: Result < Self > {
197
+ fn new ( dest_file : PathBuf , lock_ref : Arc < RwLock < ( ) > > ) -> std:: io:: Result < Self > {
162
198
let msg = format ! ( "Could not retrieve parent directory of {}." , dest_file. display( ) ) ;
163
199
let parent_directory = dest_file
164
200
. parent ( )
@@ -179,7 +215,7 @@ impl FilesystemWriter {
179
215
180
216
let tmp_writer = BufWriter :: new ( fs:: File :: create ( & tmp_file) ?) ;
181
217
182
- Ok ( Self { dest_file, parent_directory, tmp_file, tmp_writer } )
218
+ Ok ( Self { dest_file, parent_directory, tmp_file, tmp_writer, lock_ref } )
183
219
}
184
220
}
185
221
@@ -198,6 +234,8 @@ impl Write for FilesystemWriter {
198
234
impl TransactionalWrite for FilesystemWriter {
199
235
fn commit ( & mut self ) -> std:: io:: Result < ( ) > {
200
236
self . flush ( ) ?;
237
+
238
+ let _guard = self . lock_ref . write ( ) . unwrap ( ) ;
201
239
// Fsync the parent directory on Unix.
202
240
#[ cfg( not( target_os = "windows" ) ) ]
203
241
{
0 commit comments