fix: use redis-semaphore for global mutex and memory leak prevention

This commit is contained in:
Namekuji 2023-07-01 04:45:15 -04:00
parent b5907fb86d
commit ac2b057f85
No known key found for this signature in database
GPG Key ID: 1D62332C07FBA532
3 changed files with 94 additions and 35 deletions

View File

@ -43,7 +43,6 @@
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"async-mutex": "^0.4.0",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"autwh": "0.1.0", "autwh": "0.1.0",

View File

@ -1,30 +1,36 @@
import probeImageSize from "probe-image-size"; import probeImageSize from "probe-image-size";
import { Mutex, withTimeout } from "async-mutex"; import { Mutex } from "redis-semaphore";
import { FILE_TYPE_BROWSERSAFE } from "@/const.js"; import { FILE_TYPE_BROWSERSAFE } from "@/const.js";
import Logger from "@/services/logger.js"; import Logger from "@/services/logger.js";
import { Cache } from "./cache.js"; import { redisClient } from "@/db/redis.js";
export type Size = { export type Size = {
width: number; width: number;
height: number; height: number;
}; };
const cache = new Cache<boolean>(1000 * 60 * 10); // once every 10 minutes for the same url const logger = new Logger("emoji");
const mutex = withTimeout(new Mutex(), 1000);
export async function getEmojiSize(url: string): Promise<Size> { export async function getEmojiSize(url: string): Promise<Size> {
const logger = new Logger("emoji"); let attempted = true;
await mutex.runExclusive(() => { const lock = new Mutex(redisClient, "getEmojiSize");
const attempted = cache.get(url); await lock.acquire();
try {
const key = `getEmojiSize:${url}`;
attempted = await redisClient.get(key) !== null;
if (!attempted) { if (!attempted) {
cache.set(url, true); await redisClient.set(key, "done", "EX", 60 * 10);
} else { }
logger.warn(`Attempt limit exceeded: ${url}`); } finally {
throw new Error("Too many attempts"); await lock.release();
}
if (attempted) {
logger.warn(`Attempt limit exceeded: ${url}`);
throw new Error("attempt limit exceeded");
} }
});
try { try {
logger.info(`Retrieving emoji size from ${url}`); logger.info(`Retrieving emoji size from ${url}`);
@ -32,11 +38,11 @@ export async function getEmojiSize(url: string): Promise<Size> {
timeout: 5000, timeout: 5000,
}); });
if (!(mime.startsWith("image/") && FILE_TYPE_BROWSERSAFE.includes(mime))) { if (!(mime.startsWith("image/") && FILE_TYPE_BROWSERSAFE.includes(mime))) {
throw new Error("Unsupported image type"); throw new Error("unsupported image type");
} }
return { width, height }; return { width, height };
} catch (e) { } catch (e) {
throw new Error(`Unable to retrieve metadata: ${e}`); throw new Error(`unable to retrieve metadata: ${e}`);
} }
} }

View File

@ -132,9 +132,6 @@ importers:
argon2: argon2:
specifier: ^0.30.3 specifier: ^0.30.3
version: 0.30.3 version: 0.30.3
async-mutex:
specifier: ^0.4.0
version: 0.4.0
autobind-decorator: autobind-decorator:
specifier: 2.4.0 specifier: 2.4.0
version: 2.4.0 version: 2.4.0
@ -601,7 +598,7 @@ importers:
version: 5.1.3 version: 5.1.3
webpack: webpack:
specifier: ^5.85.1 specifier: ^5.85.1
version: 5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3) version: 5.85.1(@swc/core@1.3.62)
ws: ws:
specifier: 8.13.0 specifier: 8.13.0
version: 8.13.0 version: 8.13.0
@ -4496,12 +4493,6 @@ packages:
stream-exhaust: 1.0.2 stream-exhaust: 1.0.2
dev: true dev: true
/async-mutex@0.4.0:
resolution: {integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==}
dependencies:
tslib: 2.6.0
dev: false
/async-settle@1.0.0: /async-settle@1.0.0:
resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==} resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@ -4646,7 +4637,7 @@ packages:
/axios@0.24.0: /axios@0.24.0:
resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
dependencies: dependencies:
follow-redirects: 1.15.2(debug@4.3.4) follow-redirects: 1.15.2
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: false dev: false
@ -4662,7 +4653,7 @@ packages:
/axios@1.2.2: /axios@1.2.2:
resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==}
dependencies: dependencies:
follow-redirects: 1.15.2(debug@4.3.4) follow-redirects: 1.15.2
form-data: 4.0.0 form-data: 4.0.0
proxy-from-env: 1.1.0 proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@ -4672,7 +4663,7 @@ packages:
/axios@1.4.0: /axios@1.4.0:
resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==}
dependencies: dependencies:
follow-redirects: 1.15.2(debug@4.3.4) follow-redirects: 1.15.2
form-data: 4.0.0 form-data: 4.0.0
proxy-from-env: 1.1.0 proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@ -6288,6 +6279,17 @@ packages:
dependencies: dependencies:
ms: 2.0.0 ms: 2.0.0
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
dev: false
/debug@3.2.7(supports-color@8.1.1): /debug@3.2.7(supports-color@8.1.1):
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies: peerDependencies:
@ -6298,6 +6300,7 @@ packages:
dependencies: dependencies:
ms: 2.1.3 ms: 2.1.3
supports-color: 8.1.1 supports-color: 8.1.1
dev: true
/debug@4.3.3: /debug@4.3.3:
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
@ -7535,6 +7538,16 @@ packages:
tabbable: 6.2.0 tabbable: 6.2.0
dev: true dev: true
/follow-redirects@1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/follow-redirects@1.15.2(debug@4.3.4): /follow-redirects@1.15.2(debug@4.3.4):
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@ -7545,6 +7558,7 @@ packages:
optional: true optional: true
dependencies: dependencies:
debug: 4.3.4(supports-color@8.1.1) debug: 4.3.4(supports-color@8.1.1)
dev: true
/for-each@0.3.3: /for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@ -8455,7 +8469,7 @@ packages:
engines: {node: '>= 4.5.0'} engines: {node: '>= 4.5.0'}
dependencies: dependencies:
agent-base: 4.3.0 agent-base: 4.3.0
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: false dev: false
@ -9854,7 +9868,7 @@ packages:
json5: 2.2.3 json5: 2.2.3
loader-utils: 2.0.4 loader-utils: 2.0.4
schema-utils: 3.3.0 schema-utils: 3.3.0
webpack: 5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3) webpack: 5.85.1(@swc/core@1.3.62)
dev: true dev: true
/json5@2.2.3: /json5@2.2.3:
@ -10097,7 +10111,7 @@ packages:
resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}
engines: {node: '>= 7.6.0'} engines: {node: '>= 7.6.0'}
dependencies: dependencies:
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
koa-send: 5.0.1 koa-send: 5.0.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -11091,7 +11105,7 @@ packages:
engines: {node: '>= 4.4.x'} engines: {node: '>= 4.4.x'}
hasBin: true hasBin: true
dependencies: dependencies:
debug: 3.2.7(supports-color@8.1.1) debug: 3.2.7
iconv-lite: 0.4.24 iconv-lite: 0.4.24
sax: 1.2.4 sax: 1.2.4
transitivePeerDependencies: transitivePeerDependencies:
@ -13997,7 +14011,7 @@ packages:
webpack: '>=2' webpack: '>=2'
dependencies: dependencies:
'@swc/core': 1.3.62 '@swc/core': 1.3.62
webpack: 5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3) webpack: 5.85.1(@swc/core@1.3.62)
dev: true dev: true
/swiper@9.3.2: /swiper@9.3.2:
@ -14118,7 +14132,7 @@ packages:
schema-utils: 3.3.0 schema-utils: 3.3.0
serialize-javascript: 6.0.1 serialize-javascript: 6.0.1
terser: 5.18.2 terser: 5.18.2
webpack: 5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3) webpack: 5.85.1(@swc/core@1.3.62)
dev: true dev: true
/terser@5.18.2: /terser@5.18.2:
@ -14418,7 +14432,7 @@ packages:
micromatch: 4.0.5 micromatch: 4.0.5
semver: 7.5.1 semver: 7.5.1
typescript: 5.1.3 typescript: 5.1.3
webpack: 5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3) webpack: 5.85.1(@swc/core@1.3.62)
dev: true dev: true
/ts-node@10.4.0(@swc/core@1.3.62)(@types/node@20.3.1)(typescript@5.1.3): /ts-node@10.4.0(@swc/core@1.3.62)(@types/node@20.3.1)(typescript@5.1.3):
@ -15252,6 +15266,46 @@ packages:
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
dev: true dev: true
/webpack@5.85.1(@swc/core@1.3.62):
resolution: {integrity: sha512-xTb7MRf4LY8Z5rzn7aIx4TDrwYJrjcHnIfU1TqtyZOoObyuGSpAUwIvVuqq5wPnv7WEgQr8UvO1q/dgoGG4HjA==}
engines: {node: '>=10.13.0'}
hasBin: true
peerDependencies:
webpack-cli: '*'
peerDependenciesMeta:
webpack-cli:
optional: true
dependencies:
'@types/eslint-scope': 3.7.4
'@types/estree': 1.0.1
'@webassemblyjs/ast': 1.11.6
'@webassemblyjs/wasm-edit': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.9.0
acorn-import-assertions: 1.9.0(acorn@8.9.0)
browserslist: 4.21.9
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.3.0
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
json-parse-even-better-errors: 2.3.1
loader-runner: 4.3.0
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.9(@swc/core@1.3.62)(webpack@5.85.1)
watchpack: 2.4.0
webpack-sources: 3.2.3
transitivePeerDependencies:
- '@swc/core'
- esbuild
- uglify-js
dev: true
/webpack@5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3): /webpack@5.85.1(@swc/core@1.3.62)(webpack-cli@5.1.3):
resolution: {integrity: sha512-xTb7MRf4LY8Z5rzn7aIx4TDrwYJrjcHnIfU1TqtyZOoObyuGSpAUwIvVuqq5wPnv7WEgQr8UvO1q/dgoGG4HjA==} resolution: {integrity: sha512-xTb7MRf4LY8Z5rzn7aIx4TDrwYJrjcHnIfU1TqtyZOoObyuGSpAUwIvVuqq5wPnv7WEgQr8UvO1q/dgoGG4HjA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}