Skip to content

Commit 273f6b5

Browse files
authored
Merge pull request #1163 from solid/fix/lock-timeout
Make stale locks result in 500 instead of server crash
2 parents 7c0879c + 006a9c3 commit 273f6b5

File tree

3 files changed

+61
-62
lines changed

3 files changed

+61
-62
lines changed

lib/handlers/patch.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const $rdf = require('rdflib')
1010
const crypto = require('crypto')
1111
const overQuota = require('../utils').overQuota
1212
const getContentType = require('../utils').getContentType
13-
const { lock } = require('proper-lockfile')
13+
const withLock = require('../lock')
1414

1515
// Patch parsers by request body content type
1616
const PATCH_PARSERS = {
@@ -24,7 +24,6 @@ const DEFAULT_FOR_NEW_CONTENT_TYPE = 'text/turtle'
2424
async function patchHandler (req, res, next) {
2525
debug(`PATCH -- ${req.originalUrl}`)
2626
res.header('MS-Author-Via', 'SPARQL')
27-
let releaseLock
2827
try {
2928
// Obtain details of the target resource
3029
const ldp = req.app.locals.ldp
@@ -57,19 +56,16 @@ async function patchHandler (req, res, next) {
5756
await checkPermission(req, patchObject)
5857

5958
// Patch the graph and write it back to the file
60-
releaseLock = await lock(path, { retries: 10, realpath: false })
61-
const graph = await readGraph(resource)
62-
await applyPatch(patchObject, graph, url)
63-
const result = await writeGraph(graph, resource, ldp.resourceMapper.rootPath, ldp.serverUri)
59+
const result = await withLock(path, { mustExist: false }, async () => {
60+
const graph = await readGraph(resource)
61+
await applyPatch(patchObject, graph, url)
62+
return writeGraph(graph, resource, ldp.resourceMapper.rootPath, ldp.serverUri)
63+
})
6464

6565
// Send the result to the client
6666
res.send(result)
6767
} catch (err) {
6868
return next(err)
69-
} finally {
70-
if (releaseLock) {
71-
await releaseLock()
72-
}
7369
}
7470
return next()
7571
}

lib/ldp.js

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const parse = require('./utils').parse
1818
const fetch = require('node-fetch')
1919
const { promisify } = require('util')
2020
const URI = require('urijs')
21-
const { lock } = require('proper-lockfile')
21+
const withLock = require('./lock')
2222

2323
const RDF_MIME_TYPES = new Set([
2424
'text/turtle', // .ttl
@@ -78,17 +78,11 @@ class LDP {
7878
}
7979

8080
async readResource (url) {
81-
let releaseLock
8281
try {
8382
const { path } = await this.resourceMapper.mapUrlToFile({ url })
84-
releaseLock = await lock(path, { retries: 10 })
85-
return await promisify(fs.readFile)(path, {encoding: 'utf8'})
83+
return await withLock(path, () => promisify(fs.readFile)(path, {encoding: 'utf8'}))
8684
} catch (err) {
8785
throw error(err.status, err.message)
88-
} finally {
89-
if (releaseLock) {
90-
await releaseLock()
91-
}
9286
}
9387
}
9488

@@ -244,24 +238,16 @@ class LDP {
244238
}
245239

246240
// Directory created, now write the file
247-
let releaseLock
248-
try {
249-
releaseLock = await lock(path, { retries: 10, realpath: false })
250-
return await new Promise((resolve, reject) => {
251-
const file = stream.pipe(fs.createWriteStream(path))
252-
file.on('error', function () {
253-
reject(error(500, 'Error writing data'))
254-
})
255-
file.on('finish', function () {
256-
debug.handlers('PUT -- Wrote data to: ' + path)
257-
resolve()
258-
})
241+
return withLock(path, { mustExist: false }, () => new Promise((resolve, reject) => {
242+
const file = stream.pipe(fs.createWriteStream(path))
243+
file.on('error', function () {
244+
reject(error(500, 'Error writing data'))
259245
})
260-
} finally {
261-
if (releaseLock) {
262-
await releaseLock()
263-
}
264-
}
246+
file.on('finish', function () {
247+
debug.handlers('PUT -- Wrote data to: ' + path)
248+
resolve()
249+
})
250+
}))
265251
}
266252

267253
async exists (hostname, path, searchIndex = true) {
@@ -379,26 +365,18 @@ class LDP {
379365
chunksize = (end - start) + 1
380366
contentRange = 'bytes ' + start + '-' + end + '/' + total
381367
}
382-
let releaseLock
383-
try {
384-
releaseLock = await lock(path, { retries: 10 })
385-
return await new Promise((resolve, reject) => {
386-
const stream = fs.createReadStream(path, start && end ? {start, end} : {})
387-
stream
388-
.on('error', function (err) {
389-
debug.handlers(`GET -- error reading ${path}: ${err.message}`)
390-
return reject(error(err, "Can't read file " + err))
391-
})
392-
.on('open', function () {
393-
debug.handlers(`GET -- Reading ${path}`)
394-
return resolve({ stream, contentType, container: false, contentRange, chunksize })
395-
})
396-
})
397-
} finally {
398-
if (releaseLock) {
399-
await releaseLock()
400-
}
401-
}
368+
return withLock(path, () => new Promise((resolve, reject) => {
369+
const stream = fs.createReadStream(path, start && end ? {start, end} : {})
370+
stream
371+
.on('error', function (err) {
372+
debug.handlers(`GET -- error reading ${path}: ${err.message}`)
373+
return reject(error(err, "Can't read file " + err))
374+
})
375+
.on('open', function () {
376+
debug.handlers(`GET -- Reading ${path}`)
377+
return resolve({ stream, contentType, container: false, contentRange, chunksize })
378+
})
379+
}))
402380
}
403381
}
404382

@@ -447,17 +425,11 @@ class LDP {
447425
}
448426

449427
async deleteResource (path) {
450-
let releaseLock
451428
try {
452-
releaseLock = await lock(path, { retries: 10 })
453-
return await promisify(fs.unlink)(path)
429+
return await withLock(path, { mustExist: false }, () => promisify(fs.unlink)(path))
454430
} catch (err) {
455431
debug.container('DELETE -- unlink() error: ' + err)
456432
throw error(err, 'Failed to delete resource')
457-
} finally {
458-
if (releaseLock) {
459-
await releaseLock()
460-
}
461433
}
462434
}
463435

lib/lock.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { lock } = require('proper-lockfile')
2+
3+
const staleSeconds = 30
4+
5+
// Obtains a lock on the path, and maintains it until the callback finishes
6+
function withLock (path, options = {}, callback = options) {
7+
return new Promise(async (resolve, reject) => {
8+
let releaseLock, result
9+
try {
10+
// Obtain the lock
11+
releaseLock = await lock(path, {
12+
retries: 10,
13+
update: 1000,
14+
stale: staleSeconds * 1000,
15+
realpath: !!options.mustExist,
16+
onCompromised: () =>
17+
reject(new Error(`The file at ${path} was not updated within ${staleSeconds}s.`))
18+
})
19+
// Hold on to the lock until the callback's returned promise resolves
20+
result = await callback()
21+
} catch (error) {
22+
reject(error)
23+
// Ensure the lock is always released
24+
} finally {
25+
await releaseLock()
26+
}
27+
resolve(result)
28+
})
29+
}
30+
31+
module.exports = withLock

0 commit comments

Comments
 (0)