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 }