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 };