HOWTO: add rate example
[fio.git] / filelock.c
1 /*
2  * Really simple exclusive file locking based on filename.
3  * No hash indexing, just a list, so only works well for < 100 files or
4  * so. But that's more than what fio needs, so should be fine.
5  */
6 #include <inttypes.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <assert.h>
10
11 #include "flist.h"
12 #include "filelock.h"
13 #include "smalloc.h"
14 #include "mutex.h"
15 #include "hash.h"
16 #include "log.h"
17
18 struct fio_filelock {
19         uint32_t hash;
20         struct fio_mutex lock;
21         struct flist_head list;
22         unsigned int references;
23 };
24
25 #define MAX_FILELOCKS   128
26         
27 static struct filelock_data {
28         struct flist_head list;
29         struct fio_mutex lock;
30
31         struct flist_head free_list;
32         struct fio_filelock ffs[MAX_FILELOCKS];
33 } *fld;
34
35 static void put_filelock(struct fio_filelock *ff)
36 {
37         flist_add(&ff->list, &fld->free_list);
38 }
39
40 static struct fio_filelock *__get_filelock(void)
41 {
42         struct fio_filelock *ff;
43
44         if (flist_empty(&fld->free_list))
45                 return NULL;
46
47         ff = flist_first_entry(&fld->free_list, struct fio_filelock, list);
48         flist_del_init(&ff->list);
49         return ff;
50 }
51
52 static struct fio_filelock *get_filelock(int trylock, int *retry)
53 {
54         struct fio_filelock *ff;
55
56         do {
57                 ff = __get_filelock();
58                 if (ff || trylock)
59                         break;
60
61                 fio_mutex_up(&fld->lock);
62                 usleep(1000);
63                 fio_mutex_down(&fld->lock);
64                 *retry = 1;
65         } while (1);
66
67         return ff;
68 }
69
70 int fio_filelock_init(void)
71 {
72         int i;
73
74         fld = smalloc(sizeof(*fld));
75         if (!fld)
76                 return 1;
77
78         INIT_FLIST_HEAD(&fld->list);
79         INIT_FLIST_HEAD(&fld->free_list);
80
81         if (__fio_mutex_init(&fld->lock, FIO_MUTEX_UNLOCKED))
82                 goto err;
83
84         for (i = 0; i < MAX_FILELOCKS; i++) {
85                 struct fio_filelock *ff = &fld->ffs[i];
86
87                 if (__fio_mutex_init(&ff->lock, FIO_MUTEX_UNLOCKED))
88                         goto err;
89                 flist_add_tail(&ff->list, &fld->free_list);
90         }
91
92         return 0;
93 err:
94         fio_filelock_exit();
95         return 1;
96 }
97
98 void fio_filelock_exit(void)
99 {
100         if (!fld)
101                 return;
102
103         assert(flist_empty(&fld->list));
104         __fio_mutex_remove(&fld->lock);
105
106         while (!flist_empty(&fld->free_list)) {
107                 struct fio_filelock *ff;
108
109                 ff = flist_first_entry(&fld->free_list, struct fio_filelock, list);
110
111                 flist_del_init(&ff->list);
112                 __fio_mutex_remove(&ff->lock);
113         }
114
115         sfree(fld);
116         fld = NULL;
117 }
118
119 static struct fio_filelock *fio_hash_find(uint32_t hash)
120 {
121         struct flist_head *entry;
122         struct fio_filelock *ff;
123
124         flist_for_each(entry, &fld->list) {
125                 ff = flist_entry(entry, struct fio_filelock, list);
126                 if (ff->hash == hash)
127                         return ff;
128         }
129
130         return NULL;
131 }
132
133 static struct fio_filelock *fio_hash_get(uint32_t hash, int trylock)
134 {
135         struct fio_filelock *ff;
136
137         ff = fio_hash_find(hash);
138         if (!ff) {
139                 int retry = 0;
140
141                 ff = get_filelock(trylock, &retry);
142                 if (!ff)
143                         return NULL;
144
145                 /*
146                  * If we dropped the main lock, re-lookup the hash in case
147                  * someone else added it meanwhile. If it's now there,
148                  * just return that.
149                  */
150                 if (retry) {
151                         struct fio_filelock *__ff;
152
153                         __ff = fio_hash_find(hash);
154                         if (__ff) {
155                                 put_filelock(ff);
156                                 return __ff;
157                         }
158                 }
159
160                 ff->hash = hash;
161                 ff->references = 0;
162                 flist_add(&ff->list, &fld->list);
163         }
164
165         return ff;
166 }
167
168 static bool __fio_lock_file(const char *fname, int trylock)
169 {
170         struct fio_filelock *ff;
171         uint32_t hash;
172
173         hash = jhash(fname, strlen(fname), 0);
174
175         fio_mutex_down(&fld->lock);
176         ff = fio_hash_get(hash, trylock);
177         if (ff)
178                 ff->references++;
179         fio_mutex_up(&fld->lock);
180
181         if (!ff) {
182                 assert(!trylock);
183                 return true;
184         }
185
186         if (!trylock) {
187                 fio_mutex_down(&ff->lock);
188                 return false;
189         }
190
191         if (!fio_mutex_down_trylock(&ff->lock))
192                 return false;
193
194         fio_mutex_down(&fld->lock);
195
196         /*
197          * If we raced and the only reference to the lock is us, we can
198          * grab it
199          */
200         if (ff->references != 1) {
201                 ff->references--;
202                 ff = NULL;
203         }
204
205         fio_mutex_up(&fld->lock);
206
207         if (ff) {
208                 fio_mutex_down(&ff->lock);
209                 return false;
210         }
211
212         return true;
213 }
214
215 bool fio_trylock_file(const char *fname)
216 {
217         return __fio_lock_file(fname, 1);
218 }
219
220 void fio_lock_file(const char *fname)
221 {
222         __fio_lock_file(fname, 0);
223 }
224
225 void fio_unlock_file(const char *fname)
226 {
227         struct fio_filelock *ff;
228         uint32_t hash;
229
230         hash = jhash(fname, strlen(fname), 0);
231
232         fio_mutex_down(&fld->lock);
233
234         ff = fio_hash_find(hash);
235         if (ff) {
236                 int refs = --ff->references;
237                 fio_mutex_up(&ff->lock);
238                 if (!refs) {
239                         flist_del_init(&ff->list);
240                         put_filelock(ff);
241                 }
242         } else
243                 log_err("fio: file not found for unlocking\n");
244
245         fio_mutex_up(&fld->lock);
246 }