1 /*
2 * Copyright (c) 2013, Joyent, Inc. All rights reserved.
3 */
4 // vim: set sts=4 sw=4 et:
5
6 // Ensure we're using the platform's node
7 require('/usr/node/node_modules/platform_node_version').assert();
8
9 var mod_path = require('path');
10 var mod_fs = require('fs');
11 var mod_assert = require('assert');
12
13 var mod_lockfd = require('/usr/node/0.8/node_modules/lockfd');
14
15 // If we fail to lock (i.e. the call to fcntl() in node-lockfd), and
16 // the errno is in this list, then we should back off for some delay
17 // and retry. Note that an EDEADLK, in particular, is not necessarilly
18 // a permanent failure in a program using multiple lock files through
19 // multiple threads of control.
20 var RETRY_CODES = [
21 'EAGAIN',
22 'ENOLCK',
23 'EDEADLK'
24 ];
25 var RETRY_DELAY = 250; // ms
26
27 var LOCKFILE_MODE = 0644;
28
29 var LOCKFILES = [];
30 var NEXT_HOLDER_ID = 1;
31
32 function lockfile_create(path) {
33 path = mod_path.normalize(path);
34
35 mod_assert.strictEqual(lockfile_lookup(path), null);
36
37 var lf = {
38 lf_path: path,
39 lf_state: 'UNLOCKED',
40 lf_cbq: [],
41 lf_fd: -1,
42 lf_holder_id: -1
43 };
44
45 LOCKFILES.push(lf);
46
47 return (lf);
48 }
49
50 function lockfile_lookup(path) {
51 path = mod_path.normalize(path);
52
53 for (var i = 0; i < LOCKFILES.length; i++) {
54 var lf = LOCKFILES[i];
55
56 if (lf.lf_path === path)
57 return (lf);
58 }
59
60 return (null);
61 }
62
63 // Make an unlock callback for this lockfile to hand to the waiter for whom
64 // we acquired the lock:
65 function lockfile_make_unlock(lf) {
66 var holder_id;
67
68 mod_assert.strictEqual(lf.lf_holder_id, -1);
69
70 lf.lf_holder_id = holder_id = ++NEXT_HOLDER_ID;
71
72 return (function __unlock(ulcb) {
73 mod_assert.strictEqual(lf.lf_holder_id, holder_id,
74 'mismatched lock holder or already unlocked');
75 lf.lf_holder_id = -1;
76
77 mod_assert.strictEqual(lf.lf_state, 'LOCKED');
78 mod_assert.notStrictEqual(lf.lf_fd, -1);
79
80 lf.lf_state = 'UNLOCKING';
81
82 mod_fs.close(lf.lf_fd, function (err) {
83 lf.lf_state = 'UNLOCKED';
84 lf.lf_fd = -1;
85
86 ulcb(err);
87
88 lockfile_dispatch(lf);
89 });
90 });
91 }
92
93 function lockfile_dispatch(lf) {
94 if (lf.lf_state !== 'UNLOCKED')
95 return;
96
97 if (lf.lf_cbq.length === 0) {
98 // No more waiters to service for now.
99 return;
100 }
101
102 lockfile_to_locking(lf);
103 }
104
105 function lockfile_to_locking(lf) {
106 mod_assert.strictEqual(lf.lf_state, 'UNLOCKED');
107 mod_assert.strictEqual(lf.lf_fd, -1);
108
109 lf.lf_state = 'LOCKING';
110
111 // Open the lock file, creating it if it does not exist:
112 mod_fs.open(lf.lf_path, 'w+', LOCKFILE_MODE, function __opencb(err, fd) {
113 mod_assert.strictEqual(lf.lf_state, 'LOCKING');
114 mod_assert.strictEqual(lf.lf_fd, -1);
115
116 if (err) {
117 lf.lf_state = 'UNLOCKED';
118
119 // Dispatch error to the first waiter
120 lf.lf_cbq.shift()(err);
121 lockfile_dispatch(lf);
122 return;
123 }
124
125 lf.lf_fd = fd;
126
127 // Attempt to get an exclusive lock on the file via our file
128 // descriptor:
129 mod_lockfd.lockfd(lf.lf_fd, function __lockfdcb(_err) {
130 mod_assert.strictEqual(lf.lf_state, 'LOCKING');
131
132 if (_err) {
133 var do_retry = (RETRY_CODES.indexOf(_err.code) !== -1);
134
135 // We could not lock the file, so we should close our fd now:
136 mod_fs.close(lf.lf_fd, function __closecb(__err) {
137 // It would be most unfortunate to fail here:
138 mod_assert.ifError(__err);
139
140 lf.lf_fd = -1;
141 lf.lf_state = 'UNLOCKED';
142
143 if (do_retry) {
144 // Back off and try again.
145 setTimeout(function __tocb() {
146 lockfile_dispatch(lf);
147 }, RETRY_DELAY);
148 return;
149 }
150
151 // Report the condition to the first waiter:
152 lf.lf_cbq.shift()(_err);
153
154 lockfile_dispatch(lf);
155 });
156 return;
157 }
158
159 lf.lf_state = 'LOCKED';
160
161 // Dispatch locking success to first waiter, with unlock callback:
162 lf.lf_cbq.shift()(null, lockfile_make_unlock(lf));
163 });
164 });
165 }
166
167 exports.lock = function (path, callback) {
168 var lf = lockfile_lookup(path);
169 if (!lf) {
170 lf = lockfile_create(path);
171 }
172
173 lf.lf_cbq.push(callback);
174
175 lockfile_dispatch(lf);
176
177 };