fix: address CodeRabbit review issues
- redis-handler: add TLS_CA and USERNAME env support matching core client - connection.ts: fix Boolean() truthy bug (CI="false" was disabling Redis) - channel.ts: add in-memory lock for single-instance fallback mode - channel-subscription-tracker: lazy-init subscriber client - board.ts: invalidate user cache on board delete for home-board users - .env.example: document REDIS_CACHE_DB vs DATABASE_INDEX collision risk - CLI docs: fix docker exec command (remove /bin/bash prefix) - Docker docs: fix docker rm -f and docker ps -a for stopped containers
This commit is contained in:
8 files changed
+39
-12
No files matched your search
@@ -19,7 +19,11 @@ type SubscriptionCallback = (message: string) => MaybePromise<void>;
|
||||
*/
|
||||
export class ChannelSubscriptionTracker {
|
||||
private static subscriptions = new Map<string, Map<string, SubscriptionCallback>>();
|
||||
private static redis = getSubscriberClient();
|
||||
private static redis: ReturnType<typeof getSubscriberClient> | undefined;
|
||||
private static getRedis() {
|
||||
this.redis ??= getSubscriberClient();
|
||||
return this.redis;
|
||||
}
|
||||
private static memoryUnsubscribers = new Map<string, () => void>();
|
||||
private static listenerActive = false;
|
||||
|
||||
@@ -48,7 +52,7 @@ export class ChannelSubscriptionTracker {
|
||||
this.activateListener();
|
||||
this.listenerActive = true;
|
||||
}
|
||||
const redis = this.redis;
|
||||
const redis = this.getRedis();
|
||||
if (redis) {
|
||||
void redis.subscribe(channelName);
|
||||
}
|
||||
@@ -79,7 +83,7 @@ export class ChannelSubscriptionTracker {
|
||||
this.memoryUnsubscribers.get(channelName)?.();
|
||||
this.memoryUnsubscribers.delete(channelName);
|
||||
} else {
|
||||
const redis = this.redis;
|
||||
const redis = this.getRedis();
|
||||
if (redis) {
|
||||
void redis.unsubscribe(channelName);
|
||||
}
|
||||
@@ -109,7 +113,7 @@ export class ChannelSubscriptionTracker {
|
||||
*/
|
||||
private static activateListener() {
|
||||
logger.debug("Activating listener");
|
||||
const redis = this.redis;
|
||||
const redis = this.getRedis();
|
||||
if (!redis) return;
|
||||
redis.on("message", (channel, message) => {
|
||||
this.dispatchMessage(channel, message);
|
||||
|
||||
@@ -159,17 +159,29 @@ export const createGetSetChannel = <TData>(name: string) => {
|
||||
* The token prevents a process from releasing a lock that expired and was
|
||||
* acquired by another process in the meantime.
|
||||
*/
|
||||
// ponytail: in-memory lock map for single-instance fallback; upgrade to redis-based lock when Redis is available
|
||||
const memoryLocks = new Map<string, { token: string; expiresAt: number }>();
|
||||
|
||||
export const createLockChannel = (name: string) => {
|
||||
return {
|
||||
acquireAsync: async (ttlSeconds: number) => {
|
||||
const token = createId();
|
||||
if (!dataClient) return token;
|
||||
if (!dataClient) {
|
||||
const existing = memoryLocks.get(name);
|
||||
if (existing && existing.expiresAt > Date.now()) return null;
|
||||
memoryLocks.set(name, { token, expiresAt: Date.now() + ttlSeconds * 1000 });
|
||||
return token;
|
||||
}
|
||||
|
||||
const result = await dataClient.set(name, token, "EX", ttlSeconds, "NX");
|
||||
return result === "OK" ? token : null;
|
||||
},
|
||||
releaseAsync: async (token: string) => {
|
||||
if (!dataClient) return;
|
||||
if (!dataClient) {
|
||||
const existing = memoryLocks.get(name);
|
||||
if (existing?.token === token) memoryLocks.delete(name);
|
||||
return;
|
||||
}
|
||||
|
||||
await dataClient.eval(
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createRedisClient } from "@homarr/core/infrastructure/redis";
|
||||
let dataClient: RedisClient | undefined;
|
||||
let subscriberClient: RedisClient | undefined;
|
||||
|
||||
const isRedisExplicitlyDisabled = () => Boolean(process.env.CI) || Boolean(process.env.DISABLE_REDIS_LOGS);
|
||||
const isRedisExplicitlyDisabled = () => process.env.CI === "true" || process.env.CI === "1" || process.env.DISABLE_REDIS_LOGS === "true" || process.env.DISABLE_REDIS_LOGS === "1";
|
||||
|
||||
/**
|
||||
* In-process pub/sub fallback for single-instance deployments when Redis is unavailable.
|
||||
|
||||
Reference in new issue
Block a user