From a2ee080ed5ba287e7bb8bca56866d1fb037baf50 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner Date: Thu, 20 Nov 2025 02:33:49 +0100 Subject: [PATCH] introduce redis-semaphore along with a running docker redis server --- .../src/graphql/resolver/ContributionResolver.ts | 6 +----- .../resolver/TransactionLinkResolver.test.ts | 6 ++---- .../src/graphql/resolver/TransactionResolver.ts | 3 +-- backend/src/graphql/resolver/semaphore.test.ts | 16 ++++++++++++---- backend/src/server/createServer.ts | 5 +++-- backend/test/helpers.ts | 2 +- .../logic/settlePendingSenderTransaction.ts | 7 +++---- database/src/AppDatabase.ts | 16 ++++++++++++++++ database/src/config/index.ts | 3 ++- 9 files changed, 41 insertions(+), 23 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 5af152090..cf85683f5 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -43,7 +43,6 @@ import { import { UpdateUnconfirmedContributionContext } from '@/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context' import { LogError } from '@/server/LogError' import { Context, getClientTimezoneOffset, getUser } from '@/server/context' -// import { TRANSACTIONS_LOCK } from 'database' import { fullName } from 'core' import { calculateDecay, Decay } from 'shared' @@ -61,11 +60,9 @@ import { extractGraphQLFields } from './util/extractGraphQLFields' import { findContributions } from './util/findContributions' import { getLastTransaction } from 'database' import { contributionTransaction } from '@/apis/dltConnector' -import { Redis } from 'ioredis' import { Mutex } from 'redis-semaphore' const db = AppDatabase.getInstance() -const redisClient = new Redis() const createLogger = () => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.ContributionResolver`) @Resolver(() => Contribution) @@ -440,8 +437,7 @@ export class ContributionResolver { const logger = createLogger() logger.addContext('contribution', id) // acquire lock - // const releaseLock = await TRANSACTIONS_LOCK.acquire() - const mutex = new Mutex(redisClient, 'TRANSACTIONS_LOCK') + const mutex = new Mutex (db.getRedisClient(), 'TRANSACTIONS_LOCK') await mutex.acquire() try { diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index b6abcb0b2..475d47150 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -32,8 +32,6 @@ import { listTransactionLinksAdmin } from '@/seeds/graphql/queries' import { transactionLinks } from '@/seeds/transactionLink/index' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' -import { TRANSACTIONS_LOCK } from 'database' - import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { getLogger } from 'config-schema/test/testSetup' import { transactionLinkCode } from './TransactionLinkResolver' @@ -46,8 +44,8 @@ jest.mock('@/password/EncryptorUtils') CONFIG.DLT_CONNECTOR = false // mock semaphore to allow use fake timers -jest.mock('database/src/util/TRANSACTIONS_LOCK') -TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn()) +// jest.mock('database/src/util/TRANSACTIONS_LOCK') +// TRANSACTIONS_LOCK.acquire = jest.fn().mockResolvedValue(jest.fn()) let mutate: ApolloServerTestClient['mutate'] let query: ApolloServerTestClient['query'] diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 679d0f644..4103a8a7b 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -50,7 +50,6 @@ import { Redis } from 'ioredis' import { Mutex } from 'redis-semaphore' const db = AppDatabase.getInstance() -const redisClient = new Redis() const createLogger = () => getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.graphql.resolver.TransactionResolver`) export const executeTransaction = async ( @@ -63,7 +62,7 @@ export const executeTransaction = async ( ): Promise => { // acquire lock // const releaseLock = await TRANSACTIONS_LOCK.acquire() - const mutex = new Mutex(redisClient, 'TRANSACTIONS_LOCK') + const mutex = new Mutex(db.getRedisClient(), 'TRANSACTIONS_LOCK') await mutex.acquire() const receivedCallDate = new Date() diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index d0bf08b7c..54adfcee3 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -22,7 +22,9 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { bobBaumeister } from '@/seeds/users/bob-baumeister' import { peterLustig } from '@/seeds/users/peter-lustig' import { CONFIG } from '@/config' -import { TRANSACTIONS_LOCK } from 'database' +// import { TRANSACTIONS_LOCK } from 'database' +import { Mutex } from 'redis-semaphore' +import { AppDatabase } from 'database' jest.mock('@/password/EncryptorUtils') @@ -35,28 +37,34 @@ let testEnv: { mutate: ApolloServerTestClient['mutate'] query: ApolloServerTestClient['query'] con: DataSource + db: AppDatabase } - +let mutex: Mutex beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate con = testEnv.con + mutex = new Mutex(testEnv.db.getRedisClient(), 'TRANSACTIONS_LOCK') await cleanDB() }) afterAll(async () => { await cleanDB() await con.destroy() + await testEnv.db.getRedisClient().quit() }) type RunOrder = { [key: number]: { start: number, end: number } } async function fakeWork(runOrder: RunOrder, index: number) { - const releaseLock = await TRANSACTIONS_LOCK.acquire() + // const releaseLock = await TRANSACTIONS_LOCK.acquire() + await mutex.acquire() + const startDate = new Date() await new Promise((resolve) => setTimeout(resolve, Math.random() * 50)) const endDate = new Date() runOrder[index] = { start: startDate.getTime(), end: endDate.getTime() } - releaseLock() + // releaseLock() + await mutex.release() } describe('semaphore', () => { diff --git a/backend/src/server/createServer.ts b/backend/src/server/createServer.ts index 5f3bb02ef..72b5a9a89 100644 --- a/backend/src/server/createServer.ts +++ b/backend/src/server/createServer.ts @@ -8,7 +8,7 @@ import { slowDown } from 'express-slow-down' import helmet from 'helmet' import { Logger, getLogger } from 'log4js' import { DataSource } from 'typeorm' - +import { Redis } from 'ioredis' import { GRADIDO_REALM, LOG4JS_BASE_CATEGORY_NAME } from '@/config/const' import { AppDatabase } from 'database' import { context as serverContext } from './context' @@ -23,6 +23,7 @@ interface ServerDef { apollo: ApolloServer app: Express con: DataSource + db: AppDatabase } export const createServer = async ( @@ -104,5 +105,5 @@ export const createServer = async ( ) logger.debug('createServer...successful') - return { apollo, app, con: db.getDataSource() } + return { apollo, app, con: db.getDataSource(), db } } diff --git a/backend/test/helpers.ts b/backend/test/helpers.ts index c7f533931..687f5e331 100644 --- a/backend/test/helpers.ts +++ b/backend/test/helpers.ts @@ -35,7 +35,7 @@ export const testEnvironment = async (testLogger = getLogger('apollo'), testI18n const testClient = createTestClient(server.apollo) const mutate = testClient.mutate const query = testClient.query - return { mutate, query, con } + return { mutate, query, con, db: server.db } } export const resetEntity = async (entity: any) => { diff --git a/core/src/graphql/logic/settlePendingSenderTransaction.ts b/core/src/graphql/logic/settlePendingSenderTransaction.ts index c8ef678ac..2069892fb 100644 --- a/core/src/graphql/logic/settlePendingSenderTransaction.ts +++ b/core/src/graphql/logic/settlePendingSenderTransaction.ts @@ -14,13 +14,12 @@ import { LOG4JS_BASE_CATEGORY_NAME } from '../../config/const' import { PendingTransactionState } from 'shared' // import { LogError } from '@/server/LogError' import { calculateSenderBalance } from '../../util/calculateSenderBalance' -import { TRANSACTIONS_LOCK, getLastTransaction } from 'database' +// import { TRANSACTIONS_LOCK, getLastTransaction } from 'database' +import { getLastTransaction } from 'database' import { getLogger } from 'log4js' -import { Redis } from 'ioredis' import { Mutex } from 'redis-semaphore' const db = AppDatabase.getInstance() -const redisClient = new Redis() const logger = getLogger( `${LOG4JS_BASE_CATEGORY_NAME}.graphql.logic.settlePendingSenderTransaction`, ) @@ -33,7 +32,7 @@ export async function settlePendingSenderTransaction( // TODO: synchronisation with TRANSACTION_LOCK of federation-modul necessary!!! // acquire lock // const releaseLock = await TRANSACTIONS_LOCK.acquire() - const mutex = new Mutex(redisClient, 'TRANSACTIONS_LOCK') + const mutex = new Mutex(db.getRedisClient(), 'TRANSACTIONS_LOCK') await mutex.acquire() const queryRunner = db.getDataSource().createQueryRunner() diff --git a/database/src/AppDatabase.ts b/database/src/AppDatabase.ts index 3096aaecd..13e33a8d3 100644 --- a/database/src/AppDatabase.ts +++ b/database/src/AppDatabase.ts @@ -5,12 +5,14 @@ import { getLogger } from 'log4js' import { latestDbVersion } from '.' import { CONFIG } from './config' import { LOG4JS_BASE_CATEGORY_NAME } from './config/const' +import Redis from 'ioredis' const logger = getLogger(`${LOG4JS_BASE_CATEGORY_NAME}.AppDatabase`) export class AppDatabase { private static instance: AppDatabase private dataSource: DBDataSource | undefined + private redisClient: Redis /** * The Singleton's constructor should always be private to prevent direct @@ -88,10 +90,24 @@ export class AppDatabase { } // check for correct database version await this.checkDBVersion() + + this.redisClient = new Redis(CONFIG.REDIS_URL) + console.log('Redis status=', this.redisClient.status) + logger.info('Redis status=', this.redisClient.status) } public async destroy(): Promise { await this.dataSource?.destroy() + if (this.redisClient) { + this.redisClient.quit() + } + } + + public getRedisClient(): Redis { + if (!this.redisClient) { + throw new Error('Redis client not initialized') + } + return this.redisClient } // ###################################### diff --git a/database/src/config/index.ts b/database/src/config/index.ts index fc6a4bb04..cac1f8e44 100644 --- a/database/src/config/index.ts +++ b/database/src/config/index.ts @@ -24,5 +24,6 @@ const database = { } const PRODUCTION = process.env.NODE_ENV === 'production' || false const nodeEnv = process.env.NODE_ENV || 'development' +const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379' -export const CONFIG = { ...database, NODE_ENV: nodeEnv, PRODUCTION, ...defaults } +export const CONFIG = { ...database, NODE_ENV: nodeEnv, PRODUCTION, REDIS_URL, ...defaults }