4 if (process.version.match(/^v0\.[0-6]/)) {
5 var c = require('constants')
6 wx = c.O_TRUNC | c.O_CREAT | c.O_WRONLY | c.O_EXCL
10 exports.filetime = 'ctime'
11 if (os.platform() == "win32") {
12 exports.filetime = 'mtime'
16 var util = require('util')
18 debug = util.debuglog('LOCKFILE')
19 else if (/\blockfile\b/i.test(process.env.NODE_DEBUG))
21 var msg = util.format.apply(util, arguments)
22 console.error('LOCKFILE %d %s', process.pid, msg)
29 function hasOwnProperty (obj, prop) {
30 return Object.prototype.hasOwnProperty.call(obj, prop)
33 process.on('exit', function () {
34 debug('exit listener')
36 Object.keys(locks).forEach(exports.unlockSync)
39 // XXX https://github.com/joyent/node/issues/3555
40 // Remove when node 0.8 is deprecated.
41 if (/^v0\.[0-8]\./.test(process.version)) {
42 debug('uncaughtException, version = %s', process.version)
43 process.on('uncaughtException', function H (er) {
44 debug('uncaughtException')
45 var l = process.listeners('uncaughtException').filter(function (h) {
50 try { Object.keys(locks).forEach(exports.unlockSync) } catch (e) {}
51 process.removeListener('uncaughtException', H)
57 exports.unlock = function (path, cb) {
59 // best-effort. unlocking an already-unlocked lock is a noop
61 fs.unlink(path, function (unlinkEr) { cb() })
64 exports.unlockSync = function (path) {
65 debug('unlockSync', path)
66 // best-effort. unlocking an already-unlocked lock is a noop
67 try { fs.unlinkSync(path) } catch (er) {}
72 // if the file can be opened in readonly mode, then it's there.
73 // if the error is something other than ENOENT, then it's not.
74 exports.check = function (path, opts, cb) {
75 if (typeof opts === 'function') cb = opts, opts = {}
76 debug('check', path, opts)
77 fs.open(path, 'r', function (er, fd) {
79 if (er.code !== 'ENOENT') return cb(er)
80 return cb(null, false)
84 return fs.close(fd, function (er) {
89 fs.fstat(fd, function (er, st) {
90 if (er) return fs.close(fd, function (er2) {
94 fs.close(fd, function (er) {
95 var age = Date.now() - st[exports.filetime].getTime()
96 return cb(er, age <= opts.stale)
102 exports.checkSync = function (path, opts) {
104 debug('checkSync', path, opts)
106 throw new Error('opts.wait not supported sync for obvious reasons')
110 var fd = fs.openSync(path, 'r')
112 if (er.code !== 'ENOENT') throw er
117 try { fs.closeSync(fd) } catch (er) {}
121 // file exists. however, might be stale
124 var st = fs.fstatSync(fd)
128 var age = Date.now() - st[exports.filetime].getTime()
129 return (age <= opts.stale)
136 exports.lock = function (path, opts, cb) {
137 if (typeof opts === 'function') cb = opts, opts = {}
138 opts.req = opts.req || req++
139 debug('lock', path, opts)
140 opts.start = opts.start || Date.now()
142 if (typeof opts.retries === 'number' && opts.retries > 0) {
143 debug('has retries', opts.retries)
144 var retries = opts.retries
146 cb = (function (orig) { return function cb (er, fd) {
147 debug('retry-mutated callback')
149 if (!er || retries < 0) return orig(er, fd)
151 debug('lock retry', path, opts)
153 if (opts.retryWait) setTimeout(retry, opts.retryWait)
157 opts.start = Date.now()
158 debug('retrying', opts.start)
159 exports.lock(path, opts, cb)
164 // try to engage the lock.
165 // if this succeeds, then we're in business.
166 fs.open(path, wx, function (er, fd) {
168 debug('locked', path, fd)
170 return fs.close(fd, function () {
175 // something other than "currently locked"
176 // maybe eperm or something.
177 if (er.code !== 'EEXIST') return cb(er)
179 // someone's got this one. see if it's valid.
180 if (!opts.stale) return notStale(er, path, opts, cb)
182 return maybeStale(er, path, opts, false, cb)
187 // Staleness checking algorithm
188 // 1. acquire $lock, fail
189 // 2. stat $lock, find that it is stale
190 // 3. acquire $lock.STALE
191 // 4. stat $lock, assert that it is still stale
193 // 6. link $lock.STALE $lock
194 // 7. unlink $lock.STALE
195 // On any failure, clean up whatever we've done, and raise the error.
196 function maybeStale (originalEr, path, opts, hasStaleLock, cb) {
197 fs.stat(path, function (statEr, st) {
199 if (statEr.code === 'ENOENT') {
202 debug('lock stale enoent retry', path, opts)
203 exports.lock(path, opts, cb)
209 var age = Date.now() - st[exports.filetime].getTime()
210 if (age <= opts.stale) return notStale(originalEr, path, opts, cb)
212 debug('lock stale', path, opts)
214 exports.unlock(path, function (er) {
215 if (er) return cb(er)
216 debug('lock stale retry', path, opts)
217 fs.link(path + '.STALE', path, function (er) {
218 fs.unlink(path + '.STALE', function () {
219 // best effort. if the unlink fails, oh well.
225 debug('acquire .STALE file lock', opts)
226 exports.lock(path + '.STALE', opts, function (er) {
227 if (er) return cb(er)
228 maybeStale(originalEr, path, opts, true, cb)
234 function notStale (er, path, opts, cb) {
235 debug('notStale', path, opts)
237 // if we can't wait, then just call it a failure
238 if (typeof opts.wait !== 'number' || opts.wait <= 0)
241 // poll for some ms for the lock to clear
243 var start = opts.start || now
244 var end = start + opts.wait
249 debug('now=%d, wait until %d (delta=%d)', start, end, end-start)
250 var wait = Math.min(end - start, opts.pollPeriod || 100)
251 var timer = setTimeout(poll, wait)
254 debug('notStale, polling', path, opts)
255 exports.lock(path, opts, cb)
259 exports.lockSync = function (path, opts) {
261 opts.req = opts.req || req++
262 debug('lockSync', path, opts)
263 if (opts.wait || opts.retryWait) {
264 throw new Error('opts.wait not supported sync for obvious reasons')
268 var fd = fs.openSync(path, wx)
270 try { fs.closeSync(fd) } catch (er) {}
271 debug('locked sync!', path, fd)
274 if (er.code !== 'EEXIST') return retryThrow(path, opts, er)
277 var st = fs.statSync(path)
278 var ct = st[exports.filetime].getTime()
279 if (!(ct % 1000) && (opts.stale % 1000)) {
280 // probably don't have subsecond resolution.
281 // round up the staleness indicator.
282 // Yes, this will be wrong 1/1000 times on platforms
283 // with subsecond stat precision, but that's acceptable
284 // in exchange for not mistakenly removing locks on
285 // most other systems.
286 opts.stale = 1000 * Math.ceil(opts.stale / 1000)
288 var age = Date.now() - ct
289 if (age > opts.stale) {
290 debug('lockSync stale', path, opts, age)
291 exports.unlockSync(path)
292 return exports.lockSync(path, opts)
297 debug('failed to lock', path, opts, er)
298 return retryThrow(path, opts, er)
302 function retryThrow (path, opts, er) {
303 if (typeof opts.retries === 'number' && opts.retries > 0) {
304 var newRT = opts.retries - 1
305 debug('retryThrow', path, opts, newRT)
307 return exports.lockSync(path, opts)