Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 793c668

Browse files
committed
refactor: simplify gc-lock
1 parent a869b93 commit 793c668

File tree

2 files changed

+90
-71
lines changed

2 files changed

+90
-71
lines changed

src/core/components/pin/gc-lock.js

Lines changed: 18 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,27 @@
11
'use strict'
22

3-
const assert = require('assert')
4-
const mortice = require('mortice')
5-
const nanoid = require('nanoid')
63
const pull = require('pull-stream/pull')
74
const pullThrough = require('pull-stream/throughs/through')
85
const pullAsyncMap = require('pull-stream/throughs/async-map')
96
const EventEmitter = require('events')
7+
const Mutex = require('../../../utils/mutex')
108
const log = require('debug')('ipfs:gc:lock')
119

1210
class GCLock extends EventEmitter {
1311
constructor (repoOwner) {
1412
super()
1513

16-
// Ensure that we get a different mutex for each instance of GCLock
17-
// (There should only be one GCLock instance per IPFS instance, but
18-
// there may be multiple IPFS instances, eg in unit tests)
19-
const randId = nanoid()
20-
this.mutex = mortice(randId, {
21-
singleProcess: repoOwner
22-
})
23-
24-
this.lockId = 0
14+
this.mutex = new Mutex(repoOwner, { log })
2515
}
2616

2717
readLock (lockedFn, cb) {
28-
return this.lock('readLock', lockedFn, cb)
18+
this.emit(`readLock request`)
19+
return this.mutex.readLock(lockedFn, cb)
2920
}
3021

3122
writeLock (lockedFn, cb) {
32-
return this.lock('writeLock', lockedFn, cb)
33-
}
34-
35-
lock (type, lockedFn, cb) {
36-
assert(typeof lockedFn === 'function', `first argument to GCLock.${type} must be a function`)
37-
assert(typeof cb === 'function', `second argument to GCLock.${type} must be a callback function`)
38-
39-
const lockId = this.lockId++
40-
log(`[${lockId}] ${type} requested`)
41-
this.emit(`${type} request`, lockId)
42-
const locked = () => new Promise((resolve, reject) => {
43-
this.emit(`${type} start`, lockId)
44-
log(`[${lockId}] ${type} started`)
45-
lockedFn((err, res) => {
46-
this.emit(`${type} release`, lockId)
47-
log(`[${lockId}] ${type} released`)
48-
err ? reject(err) : resolve(res)
49-
})
50-
})
51-
52-
const lock = this.mutex[type](locked)
53-
return lock.then(res => cb(null, res), cb)
23+
this.emit(`writeLock request`)
24+
return this.mutex.writeLock(lockedFn, cb)
5425
}
5526

5627
pullReadLock (lockedPullFn) {
@@ -73,60 +44,36 @@ class GCLock extends EventEmitter {
7344
}
7445

7546
class PullLocker {
76-
constructor (emitter, mutex, type, lockId) {
47+
constructor (emitter, mutex, type) {
7748
this.emitter = emitter
7849
this.mutex = mutex
7950
this.type = type
80-
this.lockId = lockId
8151

82-
// This Promise resolves when the mutex gives us permission to start
83-
// running the locked piece of code
84-
this.lockReady = new Promise((resolve) => {
85-
this.lockReadyResolver = resolve
86-
})
52+
// The function to call to release the lock. It is set when the lock is taken
53+
this.releaseLock = null
8754
}
8855

89-
// Returns a Promise that resolves when the locked piece of code completes
90-
_locked () {
91-
return new Promise((resolve, reject) => {
92-
this.releaseLock = (err) => err ? reject(err) : resolve()
93-
94-
log(`[${this.lockId}] ${this.type} (pull) started`)
95-
this.emitter.emit(`${this.type} start`, this.lockId)
96-
97-
// The locked piece of code is ready to start, so resolve the
98-
// this.lockReady Promise (created in the constructor)
99-
this.lockReadyResolver()
100-
})
101-
}
102-
103-
// Requests a lock and then waits for the mutex to give us permission to run
104-
// the locked piece of code
10556
take () {
10657
return pull(
10758
pullAsyncMap((i, cb) => {
108-
if (!this.lock) {
109-
log(`[${this.lockId}] ${this.type} (pull) requested`)
110-
this.emitter.emit(`${this.type} request`, this.lockId)
111-
// Request the lock
112-
this.lock = this.mutex[this.type](() => this._locked())
113-
// If there is an error, it gets passed through to the caller using
114-
// pull streams, so here we just catch the error and ignore it so
115-
// that there isn't an UnhandledPromiseRejectionWarning
116-
this.lock.catch(() => {})
59+
if (this.lockRequested) {
60+
return cb(null, i)
11761
}
62+
this.lockRequested = true
63+
64+
this.emitter.emit(`${this.type} request`)
11865

119-
// Wait for the mutex to give us permission
120-
this.lockReady.then(() => cb(null, i))
66+
this.mutex[this.type]((releaseLock) => {
67+
cb(null, i)
68+
this.releaseLock = releaseLock
69+
})
12170
})
12271
)
12372
}
12473

12574
// Releases the lock
12675
release () {
12776
return pullThrough(null, (err) => {
128-
log(`[${this.lockId}] ${this.type} (pull) released`)
129-
this.emitter.emit(`${this.type} release`, this.lockId)
13077
this.releaseLock(err)
13178
})
13279
}

src/utils/mutex.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict'
2+
3+
const assert = require('assert')
4+
const mortice = require('mortice')
5+
const nanoid = require('nanoid')
6+
const setImmediate = require('async/setImmediate')
7+
const noop = () => {}
8+
9+
// Wrap mortice to present a callback interface
10+
class Mutex {
11+
constructor (repoOwner, options) {
12+
// Ensure that we get a different mutex for each instance of the lock
13+
const randId = nanoid()
14+
this.mutex = mortice(randId, {
15+
singleProcess: repoOwner
16+
})
17+
18+
this.log = options.log || noop
19+
this.lockId = 0
20+
}
21+
22+
readLock (lockedFn, cb) {
23+
return this._lock('readLock', lockedFn, cb)
24+
}
25+
26+
writeLock (lockedFn, cb) {
27+
return this._lock('writeLock', lockedFn, cb)
28+
}
29+
30+
/**
31+
* Request a read or write lock
32+
*
33+
* @param {String} type The type of lock: readLock / writeLock
34+
* @param {function(releaseLock)} lockedFn A function that runs the locked piece of code and calls releaseLock when it completes
35+
* @param {function(err, res)} [cb] A function that is called when the locked function completes
36+
* @returns {void}
37+
*/
38+
_lock (type, lockedFn, cb) {
39+
assert(typeof lockedFn === 'function', `first argument to CBLock.${type}() must be a function`)
40+
41+
if (typeof cb === 'undefined') {
42+
cb = noop
43+
}
44+
assert(typeof cb === 'function', `second argument to CBLock.${type}() must be a callback function`)
45+
46+
const lockId = this.lockId++
47+
this.log(`[${lockId}] ${type} requested`)
48+
49+
// mortice presents a promise based API, so we need to give it a function
50+
// that returns a Promise.
51+
// The function is invoked when mortice gives permission to run the locked
52+
// piece of code
53+
const locked = () => new Promise((resolve, reject) => {
54+
this.log(`[${lockId}] ${type} started`)
55+
lockedFn((err, res) => {
56+
this.log(`[${lockId}] ${type} released`)
57+
err ? reject(err) : resolve(res)
58+
})
59+
})
60+
61+
// Get a Promise for the lock
62+
const lock = this.mutex[type](locked)
63+
64+
// When the locked piece of code is complete, the Promise resolves
65+
return lock.then(
66+
(res) => setImmediate(() => cb(null, res)),
67+
(err) => setImmediate(() => cb(err))
68+
)
69+
}
70+
}
71+
72+
module.exports = Mutex

0 commit comments

Comments
 (0)