Skip to content

Commit

Permalink
renaming Lock to Mutex and use appConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
fcaps committed Nov 18, 2023
1 parent c52b867 commit 8c885ea
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 85 deletions.
10 changes: 5 additions & 5 deletions lib/LeaderboardService.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
class LeaderboardService {
constructor(cacheService, lockService, leaderboardRepository, lockTimeout = 3000) {
constructor(cacheService, mutexService, leaderboardRepository, lockTimeout = 3000) {
this.lockTimeout = lockTimeout
this.cacheService = cacheService
this.lockService = lockService
this.mutexService = mutexService
this.leaderboardRepository = leaderboardRepository
}

Expand All @@ -18,13 +18,13 @@ class LeaderboardService {
return this.cacheService.get(cacheKey)
}

if (this.lockService.locked) {
await this.lockService.lock(() => {
if (this.mutexService.locked) {
await this.mutexService.acquire(() => {
}, this.lockTimeout)
return this.getLeaderboard(id)
}

await this.lockService.lock(async () => {
await this.mutexService.acquire(async () => {
const result = await this.leaderboardRepository.fetchLeaderboard(id)
this.cacheService.set(cacheKey, result);
})
Expand Down
6 changes: 3 additions & 3 deletions lib/LeaderboardServiceFactory.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const LeaderboardService = require("./LeaderboardService");
const LeaderboardRepository = require("./LeaderboardRepository");
const {LockService} = require("./LockService");
const {MutexService} = require("./MutexService");
const NodeCache = require("node-cache");
const {Axios} = require("axios");

const leaderboardLock = new LockService()
const leaderboardMutex = new MutexService()
const cacheService = new NodeCache(
{
stdTTL: 300, // use 5 min for all caches if not changed with ttl
Expand All @@ -19,5 +19,5 @@ module.exports = (javaApiBaseURL, token) => {
};
const javaApiClient = new Axios(config)

return new LeaderboardService(cacheService, leaderboardLock, new LeaderboardRepository(javaApiClient))
return new LeaderboardService(cacheService, leaderboardMutex, new LeaderboardRepository(javaApiClient))
}
12 changes: 6 additions & 6 deletions lib/LockService.js → lib/MutexService.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class LockoutTimeoutError extends Error {
class AcquireTimeoutError extends Error {
}

class LockService {
class MutexService {
constructor() {
this.queue = [];
this.locked = false;
}

async lock(callback, timeLimitMS = 500) {
async acquire(callback, timeLimitMS = 500) {
let timeoutHandle;
const lockHandler = {}

Expand All @@ -16,7 +16,7 @@ class LockService {
lockHandler.reject = reject

timeoutHandle = setTimeout(
() => reject(new LockoutTimeoutError('LockService timeout reached')),
() => reject(new AcquireTimeoutError('MutexService timeout reached')),
timeLimitMS
);
});
Expand Down Expand Up @@ -66,5 +66,5 @@ class LockService {
}
}

module.exports.LockService = LockService
module.exports.LockoutTimeoutError = LockoutTimeoutError
module.exports.MutexService = MutexService
module.exports.AcquireTimeoutError = AcquireTimeoutError
8 changes: 5 additions & 3 deletions routes/views/leaderboardRouter.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const express = require('express');
const router = express.Router();
const LeaderboardServiceFactory = require('../../lib/LeaderboardServiceFactory')
const {LockoutTimeoutError} = require("../../lib/LockService");
const {AcquireTimeoutError} = require('../../lib/MutexService');
const appConfig = require('../../config/app')


const getLeaderboardId = (leaderboardName) => {
const mapping = {
Expand Down Expand Up @@ -39,11 +41,11 @@ router.get('/:leaderboard.json', async (req, res) => {
}

const token = req.user.data.attributes.token
const leaderboardService = LeaderboardServiceFactory(process.env.API_URL, token)
const leaderboardService = LeaderboardServiceFactory(appConfig.apiUrl, token)

return res.json(await leaderboardService.getLeaderboard(leaderboardId))
} catch (e) {
if (e instanceof LockoutTimeoutError) {
if (e instanceof AcquireTimeoutError) {
return res.status(503).json({error: 'timeout reached'})
}

Expand Down
8 changes: 4 additions & 4 deletions tests/LeaderboardService.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const LeaderboardService = require("../lib/LeaderboardService")
const LeaderboardRepository = require("../lib/LeaderboardRepository")
const {LockService} = require("../lib/LockService")
const {MutexService} = require("../lib/MutexService")
const NodeCache = require("node-cache")
const {Axios} = require("axios");

Expand Down Expand Up @@ -34,7 +34,7 @@ beforeEach(() => {
new NodeCache(
{ stdTTL: 300, checkperiod: 600 }
),
new LockService(),
new MutexService(),
new LeaderboardRepository(axios)
)
})
Expand Down Expand Up @@ -97,9 +97,9 @@ test('timeout for cache creation throws an error', async () => {
expect.assertions(1);
axios.get.mockImplementationOnce(() => Promise.resolve({ status: 200, data: fakeEntry }))

leaderboardService.lockService.locked = true
leaderboardService.mutexService.locked = true
leaderboardService.getLeaderboard(0).then(() => {}).catch((e) => {
expect(e.toString()).toBe('Error: LockService timeout reached')
expect(e.toString()).toBe('Error: MutexService timeout reached')
})

jest.runOnlyPendingTimers()
Expand Down
64 changes: 0 additions & 64 deletions tests/LockService.test.js

This file was deleted.

61 changes: 61 additions & 0 deletions tests/MutexService.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const {AcquireTimeoutError, MutexService} = require('../lib/MutexService')
test('release will unlock the queue', async () => {
const mutexService = new MutexService()
expect(mutexService.locked).toBe(false)

await mutexService.acquire(() => {
expect(mutexService.locked).toBe(true)
})

expect(mutexService.locked).toBe(false)
})

test('call lock twice will fill the queue', async () => {
let oneCalled = false
let twoCalled = false
const mutexService = new MutexService()
const one = mutexService.acquire(() => oneCalled = true)
const two = mutexService.acquire(() => twoCalled = true)

expect(mutexService.queue).toHaveLength(1)
expect(mutexService.locked).toBe(true)

await one
await two
expect(oneCalled).toBe(true)
expect(twoCalled).toBe(true)
expect(mutexService.queue).toHaveLength(0)
expect(mutexService.locked).toBe(false)
})

test('lock timeout will trow an error if locked by another "process" for too long', async () => {
expect.assertions(1);

const mutexService = new MutexService()

await mutexService.acquire(async () => {
try {
await mutexService.acquire(() => {}, 1)
} catch (e) {
expect(e).toBeInstanceOf(AcquireTimeoutError)
}
})
});

test('lock timeout will remove it from queue', async () => {
expect.assertions(2);

const mutexService = new MutexService()

await mutexService.acquire(async () => {
try {
await mutexService.acquire(() => {
}, 1)
} catch (e) {
expect(e).toBeInstanceOf(AcquireTimeoutError)
expect(mutexService.queue).toHaveLength(0);
}

})
})

0 comments on commit 8c885ea

Please sign in to comment.