From ac2b057f85e1f7cd012338872eaf0e62e17ed198 Mon Sep 17 00:00:00 2001 From: Namekuji Date: Sat, 1 Jul 2023 04:45:15 -0400 Subject: [PATCH] fix: use redis-semaphore for global mutex and memory leak prevention --- packages/backend/package.json | 1 - packages/backend/src/misc/emoji-meta.ts | 34 +++++---- pnpm-lock.yaml | 94 +++++++++++++++++++------ 3 files changed, 94 insertions(+), 35 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index b584a5691..fd5b2c3b3 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -43,7 +43,6 @@ "ajv": "8.12.0", "archiver": "5.3.1", "argon2": "^0.30.3", - "async-mutex": "^0.4.0", "autobind-decorator": "2.4.0", "autolinker": "4.0.0", "autwh": "0.1.0", diff --git a/packages/backend/src/misc/emoji-meta.ts b/packages/backend/src/misc/emoji-meta.ts index fd9d9baa5..0d385f72b 100644 --- a/packages/backend/src/misc/emoji-meta.ts +++ b/packages/backend/src/misc/emoji-meta.ts @@ -1,30 +1,36 @@ 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 Logger from "@/services/logger.js"; -import { Cache } from "./cache.js"; +import { redisClient } from "@/db/redis.js"; export type Size = { width: number; height: number; }; -const cache = new Cache(1000 * 60 * 10); // once every 10 minutes for the same url -const mutex = withTimeout(new Mutex(), 1000); +const logger = new Logger("emoji"); export async function getEmojiSize(url: string): Promise { - const logger = new Logger("emoji"); + let attempted = true; - await mutex.runExclusive(() => { - const attempted = cache.get(url); + const lock = new Mutex(redisClient, "getEmojiSize"); + await lock.acquire(); + try { + const key = `getEmojiSize:${url}`; + attempted = await redisClient.get(key) !== null; if (!attempted) { - cache.set(url, true); - } else { - logger.warn(`Attempt limit exceeded: ${url}`); - throw new Error("Too many attempts"); + await redisClient.set(key, "done", "EX", 60 * 10); } - }); + } finally { + await lock.release(); + } + + if (attempted) { + logger.warn(`Attempt limit exceeded: ${url}`); + throw new Error("attempt limit exceeded"); + } try { logger.info(`Retrieving emoji size from ${url}`); @@ -32,11 +38,11 @@ export async function getEmojiSize(url: string): Promise { timeout: 5000, }); if (!(mime.startsWith("image/") && FILE_TYPE_BROWSERSAFE.includes(mime))) { - throw new Error("Unsupported image type"); + throw new Error("unsupported image type"); } return { width, height }; } catch (e) { - throw new Error(`Unable to retrieve metadata: ${e}`); + throw new Error(`unable to retrieve metadata: ${e}`); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1002a6f95..c7c0089ee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -132,9 +132,6 @@ importers: argon2: specifier: ^0.30.3 version: 0.30.3 - async-mutex: - specifier: ^0.4.0 - version: 0.4.0 autobind-decorator: specifier: 2.4.0 version: 2.4.0 @@ -601,7 +598,7 @@ importers: version: 5.1.3 webpack: 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: specifier: 8.13.0 version: 8.13.0 @@ -4496,12 +4493,6 @@ packages: stream-exhaust: 1.0.2 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: resolution: {integrity: sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==} engines: {node: '>= 0.10'} @@ -4646,7 +4637,7 @@ packages: /axios@0.24.0: resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: false @@ -4662,7 +4653,7 @@ packages: /axios@1.2.2: resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -4672,7 +4663,7 @@ packages: /axios@1.4.0: resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} dependencies: - follow-redirects: 1.15.2(debug@4.3.4) + follow-redirects: 1.15.2 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -6288,6 +6279,17 @@ packages: dependencies: 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): resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -6298,6 +6300,7 @@ packages: dependencies: ms: 2.1.3 supports-color: 8.1.1 + dev: true /debug@4.3.3: resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} @@ -7535,6 +7538,16 @@ packages: tabbable: 6.2.0 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): resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -7545,6 +7558,7 @@ packages: optional: true dependencies: debug: 4.3.4(supports-color@8.1.1) + dev: true /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -8455,7 +8469,7 @@ packages: engines: {node: '>= 4.5.0'} dependencies: agent-base: 4.3.0 - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7 transitivePeerDependencies: - supports-color dev: false @@ -9854,7 +9868,7 @@ packages: json5: 2.2.3 loader-utils: 2.0.4 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 /json5@2.2.3: @@ -10097,7 +10111,7 @@ packages: resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==} engines: {node: '>= 7.6.0'} dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7 koa-send: 5.0.1 transitivePeerDependencies: - supports-color @@ -11091,7 +11105,7 @@ packages: engines: {node: '>= 4.4.x'} hasBin: true dependencies: - debug: 3.2.7(supports-color@8.1.1) + debug: 3.2.7 iconv-lite: 0.4.24 sax: 1.2.4 transitivePeerDependencies: @@ -13997,7 +14011,7 @@ packages: webpack: '>=2' dependencies: '@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 /swiper@9.3.2: @@ -14118,7 +14132,7 @@ packages: schema-utils: 3.3.0 serialize-javascript: 6.0.1 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 /terser@5.18.2: @@ -14418,7 +14432,7 @@ packages: micromatch: 4.0.5 semver: 7.5.1 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 /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'} 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): resolution: {integrity: sha512-xTb7MRf4LY8Z5rzn7aIx4TDrwYJrjcHnIfU1TqtyZOoObyuGSpAUwIvVuqq5wPnv7WEgQr8UvO1q/dgoGG4HjA==} engines: {node: '>=10.13.0'}