Skip to content

Commit 9a2801a

Browse files
committed
Fix atomicity of counter and test for it
1 parent 5803417 commit 9a2801a

5 files changed

Lines changed: 41 additions & 11 deletions

File tree

src/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ module.exports = async function (req, res) {
2727
}
2828
const shouldIncrement = query.inc !== 'false' && query.inc !== false
2929
try {
30+
const currentViews = db.has(pathname) ? db.get(pathname).views.length : 0
3031
// Add a view and send the total views back to the client
3132
if (shouldIncrement) {
32-
await pushView(pathname, { time: Date.now() })
33+
pushView(pathname, { time: Date.now() })
3334
}
3435
if (req.method === 'GET') {
35-
send(res, 200, { views: db.has(pathname) ? db.get(pathname).views.length : 0 })
36+
send(res, 200, { views: shouldIncrement ? currentViews + 1 : currentViews })
3637
} else {
3738
send(res, 200)
3839
}

src/utils.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ const db = require('./db')
44
// db messing up.
55
const pushView = async (key, view) => {
66
const locks = {}
7-
await push()
7+
push()
88

9-
async function push() {
9+
function push() {
1010
if (locks[key]) return setImmediate(push)
1111
locks[key] = true
1212

@@ -18,9 +18,13 @@ const pushView = async (key, view) => {
1818
}
1919

2020
try {
21-
await db.put(key, { views: views.concat([view]) })
22-
delete locks[key]
21+
db.put(key, { views: views.concat([view]) })
22+
.then(() => {
23+
delete locks[key]
24+
})
25+
.catch(err => { throw err })
2326
} catch (err) {
27+
delete locks[key]
2428
throw err
2529
}
2630
}

tests/atomicity.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const request = require('request-promise')
2+
const { listen, mockDb } = require('./utils')
3+
4+
jest.mock('../src/db', () => mockDb)
5+
const service = require('../src')
6+
let url
7+
8+
beforeEach(async () => {
9+
url = await listen(service)
10+
mockDb._setDelay(10)
11+
})
12+
13+
afterEach(async () => {
14+
mockDb._reset()
15+
mockDb._setDelay()
16+
})
17+
18+
it('should atomically set two views coming in at the same time', async () => {
19+
// Request twice at the same time
20+
// NOTE: These two requests will return a wrong view count, they'll both say it's 1
21+
request(`${url}/path`)
22+
request(`${url}/path`)
23+
// After the data is persisted (in the mocked case after 10ms) the path shows the right view count
24+
setTimeout(async () => {
25+
const body = JSON.parse(await request(`${url}/path`))
26+
expect(body.views).toEqual(3)
27+
}, 10)
28+
})

tests/items.test.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ const service = require('../src')
66
let url
77

88
beforeEach(async () => {
9-
url = await listen(service)
10-
})
11-
12-
afterEach(async () => {
139
mockDb._reset()
10+
url = await listen(service)
1411
})
1512

1613
describe('single', () => {

tests/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const DB = () => {
1717
keys: () => Object.keys(data),
1818
// Custom methods used in tests
1919
_reset: () => { data = {} },
20-
_setDelay: (ms) => { DELAY = ms }
20+
_setDelay: (ms) => { DELAY = ms || 1 }
2121
}
2222
}
2323

0 commit comments

Comments
 (0)