Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
Merge pull request #4 from eukarya-inc/support-multithread-cache
Browse files Browse the repository at this point in the history
Make the cache class thread safe
  • Loading branch information
tomoyane authored Mar 26, 2024
2 parents 5850e2c + dbd371f commit 6f59f0d
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 7 deletions.
18 changes: 17 additions & 1 deletion src/proxy/contentCache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class ContentCache {
constructor(expiresSec) {
this.cache = {};
this.locks = {};
this.expiresSec = parseInt(expiresSec);
}

Expand All @@ -13,27 +14,42 @@ class ContentCache {
this.cache[originUrl] = new CacheData(now + this.expiresSec, content);
}

getData(originUrl) {
async getData(originUrl) {
if (this.expiresSec === 0) {
return null;
}

if (!this.locks[originUrl]) {
this.locks[originUrl] = new Promise(resolve => resolve());
}

await this.locks[originUrl];

const data = this.cache[originUrl];
if (data === undefined) {
this.releaseLock(originUrl);
return null;
}

const now = this.toSec(Date.now());
if (now > data.timestamp) {
this.delData(originUrl);
this.releaseLock(originUrl);
return null;
}

this.releaseLock(originUrl);
return data.contentData;
}

delData(originUrl) {
delete this.cache[originUrl];
}

releaseLock(originUrl) {
delete this.locks[originUrl];
}

toSec(unixTimeMilliseconds) {
return Math.floor(unixTimeMilliseconds / 1000)
}
Expand Down
34 changes: 30 additions & 4 deletions src/proxy/contentCache.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

test('Cache set and get', () => {
test('Cache set and get', async () => {
const cache = new ContentCache('2');

const targetUrl = '/test';
cache.setData(targetUrl, 'xxxxx');

let fetchedData = cache.getData(targetUrl)
let fetchedData = await cache.getData(targetUrl)
expect(fetchedData).toBe('xxxxx');

fetchedData = cache.getData('NOT_FOUND')
fetchedData = await cache.getData('NOT_FOUND')
expect(fetchedData).toBe(null);
});

Expand All @@ -27,6 +27,32 @@ test('Cache already expired data', async () => {
await sleep(3000);

// Data is expired so data is removed
const fetchedData = cache.getData(targetUrl)
const fetchedData = await cache.getData(targetUrl)
expect(fetchedData).toBe(null);
});

test('Access multiple thread', async () => {
const cache = new ContentCache(60);
const getDataSpy = jest.spyOn(cache, 'getData');
const releaseLockSpy = jest.spyOn(cache, 'releaseLock');

const targetUrl = '/test';
const valueData = "XXXXXXXXXXXXXXX"
cache.setData(targetUrl, valueData);

// Define getData tasks
const asyncTaskCnt = 100000;
const tasks = [];
for (let i = 0; i < asyncTaskCnt; i++) {
tasks.push(cache.getData(targetUrl));
}

// Execute getData tasks concurrently
const results = await Promise.all(tasks);
results.forEach(result => {
expect(result).toBe(valueData);
});

expect(getDataSpy).toHaveBeenCalledTimes(asyncTaskCnt);
expect(releaseLockSpy).toHaveBeenCalledTimes(asyncTaskCnt);
});
4 changes: 2 additions & 2 deletions src/proxy/notionProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class NotionProxy {
* @param res Response of express
* @returns {*|void}
*/
get(req, res) {
async get(req, res) {
let url;
try {
url = utility.generateNotionUrl(req, this.SLUG_TO_PAGE);
Expand Down Expand Up @@ -87,7 +87,7 @@ class NotionProxy {
res.removeHeader('Content-Security-Policy')
res.removeHeader('X-Content-Security-Policy')

const cachedData = this.CACHE_STORE.getData(req.originalUrl);
const cachedData = await this.CACHE_STORE.getData(req.originalUrl);
if (cachedData !== null) {
return res.send(cachedData);
}
Expand Down

0 comments on commit 6f59f0d

Please sign in to comment.