diff --git a/packages/backend/src/@types/redis-lock.d.ts b/packages/backend/src/@types/redis-lock.d.ts index b037cde5ee..e27b943956 100644 --- a/packages/backend/src/@types/redis-lock.d.ts +++ b/packages/backend/src/@types/redis-lock.d.ts @@ -4,9 +4,14 @@ */ declare module 'redis-lock' { - import type Redis from 'ioredis'; + export interface NodeRedis { + readonly v4: true; + set(key: string, value: string | number, opts?: { PX?: number, NX?: boolean }): Promise<'OK' | null>; + del(key: string): Promise; + } - type Lock = (lockName: string, timeout?: number, taskToPerform?: () => Promise) => void; + export type Unlock = () => Promise; + export type Lock = (lockName: string, timeout?: number) => Promise; function redisLock(client: Redis.Redis, retryDelay: number): Lock; export = redisLock; diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts index bd2749cb87..9d31247e25 100644 --- a/packages/backend/src/core/AppLockService.ts +++ b/packages/backend/src/core/AppLockService.ts @@ -5,7 +5,7 @@ import { promisify } from 'node:util'; import { Inject, Injectable } from '@nestjs/common'; -import redisLock from 'redis-lock'; +import redisLock, { Unlock, NodeRedis } from 'redis-lock'; import * as Redis from 'ioredis'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; @@ -17,13 +17,13 @@ const retryDelay = 100; @Injectable() export class AppLockService { - private lock: (key: string, timeout?: number, _?: (() => Promise) | undefined) => Promise<() => void>; + private lock: (key: string, timeout?: number) => Promise; constructor( @Inject(DI.redis) private redisClient: Redis.Redis, ) { - this.lock = promisify(redisLock(this.redisClient, retryDelay)); + this.lock = redisLock(adaptRedis(this.redisClient), retryDelay); } /** @@ -33,12 +33,36 @@ export class AppLockService { * @returns Unlock function */ @bindThis - public getApLock(uri: string, timeout = 30 * 1000): Promise<() => void> { + public getApLock(uri: string, timeout = 30 * 1000): Promise { return this.lock(`ap-object:${uri}`, timeout); } @bindThis - public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise<() => void> { + public getChartInsertLock(lockKey: string, timeout = 30 * 1000): Promise { return this.lock(`chart-insert:${lockKey}`, timeout); } } + +/** + * Adapts an ioredis instance into something close enough to NodeRedis that it works with redis-lock. + */ +function adaptRedis(ioredis: Redis.Redis): NodeRedis { + return { + v4: true, + async set(key: string, value: string | number, opts?: { PX?: number, NX?: boolean }) { + if (opts) { + if (opts.PX != null && opts.NX) { + return ioredis.set(key, value, 'PX', opts.PX, 'NX'); + } else if (opts.PX != null) { + return ioredis.set(key, value, 'PX', opts.PX); + } else if (opts.NX) { + return ioredis.set(key, value, 'NX'); + } + } + return ioredis.set(key, value); + }, + async del(key: string) { + return await ioredis.del(key); + }, + }; +} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 6c6d576114..97fbab9e69 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -399,7 +399,7 @@ export class ApInboxService { uri, }); } finally { - unlock(); + await unlock(); } } @@ -549,7 +549,7 @@ export class ApInboxService { await this.apNoteService.createNote(note, actor, resolver, silent); return 'ok'; } finally { - unlock(); + await unlock(); } } @@ -634,7 +634,7 @@ export class ApInboxService { await this.noteDeleteService.delete(actor, note); return 'ok: note deleted'; } finally { - unlock(); + await unlock(); } } diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index 6afe15b1f3..def6058c4e 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -562,7 +562,7 @@ export class ApNoteService implements OnModuleInit { const createFrom = haveSameAuthority ? value : uri; return await this.createNote(createFrom, undefined, options.resolver, true); } finally { - unlock(); + await unlock(); } } diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index a991516577..69d808b9f1 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -829,7 +829,7 @@ export class ApPersonService implements OnModuleInit { const createFrom = haveSameAuthority ? value : uri; return await this._createPerson(createFrom, resolver); } finally { - unlock(); + await unlock(); } } diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index 07c19deca5..2f1ed27132 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -16,6 +16,7 @@ import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; import { MiRepository, miRepository } from '@/models/_.js'; import type { DataSource, Repository } from 'typeorm'; +import type { Lock } from 'redis-lock'; const COLUMN_PREFIX = '___' as const; const UNIQUE_TEMP_COLUMN_PREFIX = 'unique_temp___' as const; @@ -258,11 +259,11 @@ export default abstract class Chart { }; } - private lock: (key: string) => Promise<() => void>; + private lock: Lock; constructor( db: DataSource, - lock: (key: string) => Promise<() => void>, + lock: Lock, logger: Logger, name: string, schema: T, @@ -400,7 +401,7 @@ export default abstract class Chart { return log; } finally { - unlock(); + await unlock(); } }