Revert "fix(server): wrap read-modify-write apis with distributed lock (#5979)"

This reverts commit 34f892b05b.
This commit is contained in:
LongYinan
2024-03-15 16:55:48 +08:00
parent a24320da68
commit bc465f9704
20 changed files with 79 additions and 412 deletions

View File

@@ -1,2 +1 @@
export * from './payment-required';
export * from './too-many-requests';

View File

@@ -1,14 +0,0 @@
import { HttpException, HttpStatus } from '@nestjs/common';
export class TooManyRequestsException extends HttpException {
constructor(desc?: string, code: string = 'Too Many Requests') {
super(
HttpException.createBody(
desc ?? code,
code,
HttpStatus.TOO_MANY_REQUESTS
),
HttpStatus.TOO_MANY_REQUESTS
);
}
}

View File

@@ -11,12 +11,6 @@ import { GraphQLError } from 'graphql';
import { Config } from '../config';
import { GQLLoggerPlugin } from './logger-plugin';
export type GraphqlContext = {
req: Request;
res: Response;
isAdminQuery: boolean;
};
@Global()
@Module({
imports: [
@@ -36,13 +30,7 @@ export type GraphqlContext = {
: '../../../schema.gql'
),
sortSchema: true,
context: ({
req,
res,
}: {
req: Request;
res: Response;
}): GraphqlContext => ({
context: ({ req, res }: { req: Request; res: Response }) => ({
req,
res,
isAdminQuery: false,

View File

@@ -14,23 +14,14 @@ export {
} from './config';
export * from './error';
export { EventEmitter, type EventPayload, OnEvent } from './event';
export type { GraphqlContext } from './graphql';
export { CryptoHelper, URLHelper } from './helpers';
export { MailService } from './mailer';
export { CallCounter, CallTimer, metrics } from './metrics';
export {
BucketService,
LockGuard,
MUTEX_RETRY,
MUTEX_WAIT,
MutexService,
} from './mutex';
export {
getOptionalModuleMetadata,
GlobalExceptionFilter,
OptionalModule,
} from './nestjs';
export type { PrismaTransaction } from './prisma';
export * from './storage';
export { type StorageProvider, StorageProviderFactory } from './storage';
export { AuthThrottlerGuard, CloudThrottlerGuard, Throttle } from './throttler';

View File

@@ -1,15 +0,0 @@
export class BucketService {
private readonly bucket = new Map<string, string>();
get(key: string) {
return this.bucket.get(key);
}
set(key: string, value: string) {
this.bucket.set(key, value);
}
delete(key: string) {
this.bucket.delete(key);
}
}

View File

@@ -1,14 +0,0 @@
import { Global, Module } from '@nestjs/common';
import { BucketService } from './bucket';
import { MutexService } from './mutex';
@Global()
@Module({
providers: [BucketService, MutexService],
exports: [BucketService, MutexService],
})
export class MutexModule {}
export { BucketService, MutexService };
export { LockGuard, MUTEX_RETRY, MUTEX_WAIT } from './mutex';

View File

@@ -1,96 +0,0 @@
import { randomUUID } from 'node:crypto';
import { setTimeout } from 'node:timers/promises';
import { Inject, Injectable, Logger, Scope } from '@nestjs/common';
import { CONTEXT } from '@nestjs/graphql';
import type { GraphqlContext } from '../graphql';
import { BucketService } from './bucket';
export class LockGuard<M extends MutexService = MutexService>
implements AsyncDisposable
{
constructor(
private readonly mutex: M,
private readonly key: string
) {}
async [Symbol.asyncDispose]() {
return this.mutex.unlock(this.key);
}
}
export const MUTEX_RETRY = 5;
export const MUTEX_WAIT = 100;
@Injectable({ scope: Scope.REQUEST })
export class MutexService {
protected logger = new Logger(MutexService.name);
constructor(
@Inject(CONTEXT) private readonly context: GraphqlContext,
private readonly bucket: BucketService
) {}
protected getId() {
let id = this.context.req.headers['x-transaction-id'] as string;
if (!id) {
id = randomUUID();
this.context.req.headers['x-transaction-id'] = id;
}
return id;
}
/**
* lock an resource and return a lock guard, which will release the lock when disposed
*
* if the lock is not available, it will retry for [MUTEX_RETRY] times
*
* usage:
* ```typescript
* {
* // lock is acquired here
* await using lock = await mutex.lock('resource-key');
* if (lock) {
* // do something
* } else {
* // failed to lock
* }
* }
* // lock is released here
* ```
* @param key resource key
* @returns LockGuard
*/
async lock(key: string): Promise<LockGuard | undefined> {
const id = this.getId();
const fetchLock = async (retry: number): Promise<LockGuard | undefined> => {
if (retry === 0) {
this.logger.error(
`Failed to fetch lock ${key} after ${MUTEX_RETRY} retry`
);
return undefined;
}
const current = this.bucket.get(key);
if (current && current !== id) {
this.logger.warn(
`Failed to fetch lock ${key}, retrying in ${MUTEX_WAIT} ms`
);
await setTimeout(MUTEX_WAIT * (MUTEX_RETRY - retry + 1));
return fetchLock(retry - 1);
}
this.bucket.set(key, id);
return new LockGuard(this, key);
};
return fetchLock(MUTEX_RETRY);
}
async unlock(key: string): Promise<void> {
if (this.bucket.get(key) === this.getId()) {
this.bucket.delete(key);
}
}
}

View File

@@ -16,7 +16,3 @@ const clientProvider: Provider = {
})
export class PrismaModule {}
export { PrismaService } from './service';
export type PrismaTransaction = Parameters<
Parameters<PrismaClient['$transaction']>[0]
>[0];