diff --git a/src/backend/src/clients/redis/deleteRedisKeys.ts b/src/backend/src/clients/redis/deleteRedisKeys.ts new file mode 100644 index 000000000..fc2033434 --- /dev/null +++ b/src/backend/src/clients/redis/deleteRedisKeys.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024-present Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { redisClient } from './redisSingleton.js'; + +export const deleteRedisKeys = async (...keysInput: string[]) => { + const keys = keysInput + .map(key => key === null || key === undefined ? '' : String(key)) + .filter(Boolean); + + if ( keys.length === 0 ) { + return 0; + } + + let deleted = 0; + for ( const key of new Set(keys) ) { + deleted += await redisClient.del(key); + } + return deleted; +}; diff --git a/src/backend/src/modules/apps/AppInformationService.js b/src/backend/src/modules/apps/AppInformationService.js index 847c254c3..9bb706788 100644 --- a/src/backend/src/modules/apps/AppInformationService.js +++ b/src/backend/src/modules/apps/AppInformationService.js @@ -20,6 +20,7 @@ const { origin_from_url } = require('../../util/urlutil'); const { DB_READ } = require('../../services/database/consts'); const BaseService = require('../../services/BaseService'); const { redisClient } = require('../../clients/redis/redisSingleton'); +const { deleteRedisKeys } = require('../../clients/redis/deleteRedisKeys.js'); const { AppRedisCacheSpace } = require('./AppRedisCacheSpace.js'); // Currently leaks memory (not sure why yet, but icons are a factor) @@ -796,7 +797,7 @@ class AppInformationService extends BaseService { .filter(Boolean) .map(ext => AppRedisCacheSpace.associationAppsKey(ext)); if ( associationKeys.length ) { - await redisClient.del(...associationKeys); + await deleteRedisKeys(associationKeys); } // remove from recent diff --git a/src/backend/src/modules/apps/AppRedisCacheSpace.js b/src/backend/src/modules/apps/AppRedisCacheSpace.js index fbc5904b7..0b44faf29 100644 --- a/src/backend/src/modules/apps/AppRedisCacheSpace.js +++ b/src/backend/src/modules/apps/AppRedisCacheSpace.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ import { redisClient } from '../../clients/redis/redisSingleton.js'; +import { deleteRedisKeys } from '../../clients/redis/deleteRedisKeys.js'; const appFullNamespace = 'apps'; const appLiteNamespace = 'apps:lite'; @@ -101,7 +102,7 @@ export const AppRedisCacheSpace = { keys.push(...AppRedisCacheSpace.statsKeys(app.uid)); } if ( keys.length ) { - await redisClient.del(...keys); + await deleteRedisKeys(keys); } }, invalidateCachedAppName: async (name, { rawIconVariants = [true, false] } = {}) => { @@ -111,10 +112,10 @@ export const AppRedisCacheSpace = { value: name, rawIcon, })); - await redisClient.del(...keys); + await deleteRedisKeys(keys); }, invalidateAppStats: async (uid) => { if ( ! uid ) return; - await redisClient.del(...AppRedisCacheSpace.statsKeys(uid)); + await deleteRedisKeys(AppRedisCacheSpace.statsKeys(uid)); }, -}; \ No newline at end of file +}; diff --git a/src/backend/src/modules/data-access/AppService.js b/src/backend/src/modules/data-access/AppService.js index 899728e74..bca8060f7 100644 --- a/src/backend/src/modules/data-access/AppService.js +++ b/src/backend/src/modules/data-access/AppService.js @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; import APIError from '../../api/APIError.js'; -import { redisClient } from '../../clients/redis/redisSingleton.js'; +import { deleteRedisKeys } from '../../clients/redis/deleteRedisKeys.js'; import config from '../../config.js'; import { AppRedisCacheSpace } from '../apps/AppRedisCacheSpace.js'; import { NodeInternalIDSelector } from '../../filesystem/node/selectors.js'; @@ -1166,7 +1166,7 @@ export default class AppService extends BaseService { if ( ! normalizedNew.length ) { const affectedExtensions = new Set(normalizedOld); if ( affectedExtensions.size ) { - await redisClient.del(...Array.from(affectedExtensions) + await deleteRedisKeys(Array.from(affectedExtensions) .map(ext => AppRedisCacheSpace.associationAppsKey(ext))); } return; @@ -1180,7 +1180,7 @@ export default class AppService extends BaseService { const affectedExtensions = new Set([...normalizedOld, ...normalizedNew]); if ( affectedExtensions.size ) { - await redisClient.del(...Array.from(affectedExtensions) + await deleteRedisKeys(Array.from(affectedExtensions) .map(ext => AppRedisCacheSpace.associationAppsKey(ext))); } } diff --git a/src/backend/src/om/entitystorage/AppES.js b/src/backend/src/om/entitystorage/AppES.js index 9134574a6..311d966ec 100644 --- a/src/backend/src/om/entitystorage/AppES.js +++ b/src/backend/src/om/entitystorage/AppES.js @@ -18,7 +18,7 @@ */ const APIError = require('../../api/APIError'); const { AppRedisCacheSpace } = require('../../modules/apps/AppRedisCacheSpace.js'); -const { redisClient } = require('../../clients/redis/redisSingleton'); +const { deleteRedisKeys } = require('../../clients/redis/deleteRedisKeys.js'); const config = require('../../config'); const { app_name_exists } = require('../../helpers'); const { AppUnderUserActorType } = require('../../services/auth/Actor'); @@ -181,7 +181,7 @@ class AppES extends BaseES { ...normalizedNewAssociations, ]); if ( affectedAssociationExtensions.size ) { - await redisClient.del(...Array.from(affectedAssociationExtensions) + await deleteRedisKeys(Array.from(affectedAssociationExtensions) .map(ext => AppRedisCacheSpace.associationAppsKey(ext))); } diff --git a/src/backend/src/services/UserRedisCacheSpace.js b/src/backend/src/services/UserRedisCacheSpace.js index d0c6283b6..1195a5912 100644 --- a/src/backend/src/services/UserRedisCacheSpace.js +++ b/src/backend/src/services/UserRedisCacheSpace.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ import { redisClient } from '../clients/redis/redisSingleton.js'; +import { deleteRedisKeys } from '../clients/redis/deleteRedisKeys.js'; const userKeyPrefix = 'users'; const defaultUserIdProperties = ['username', 'uuid', 'email', 'id', 'referral_code']; @@ -65,7 +66,7 @@ const UserRedisCacheSpace = { invalidateUser: async (user, props = defaultUserIdProperties) => { const keys = UserRedisCacheSpace.keysForUser(user, props); if ( keys.length ) { - await redisClient.del(...keys); + await deleteRedisKeys(keys); } }, invalidateById: async (id, props = defaultUserIdProperties) => { diff --git a/src/backend/src/services/auth/PermissionService.js b/src/backend/src/services/auth/PermissionService.js index 38e939cca..b0372fc5d 100644 --- a/src/backend/src/services/auth/PermissionService.js +++ b/src/backend/src/services/auth/PermissionService.js @@ -28,6 +28,7 @@ const { UserActorType, Actor, AppUnderUserActorType } = require('./Actor'); const { PERM_KEY_PREFIX, MANAGE_PERM_PREFIX } = require('./permissionConts.mjs'); const { PermissionUtil, PermissionExploder, PermissionImplicator, PermissionRewriter } = require('./permissionUtils.mjs'); const { spanify } = require('../../util/otelutil'); +const { deleteRedisKeys } = require('../../clients/redis/deleteRedisKeys.js'); const { redisClient } = require('../../clients/redis/redisSingleton'); const { PermissionScanRedisCacheSpace } = require('./PermissionScanRedisCacheSpace.js'); const { Context } = require('../../util/context'); @@ -277,7 +278,7 @@ class PermissionService extends BaseService { cursor = next_cursor; if ( keys?.length ) toDelete.push(...keys); } while ( cursor !== '0' ); - if ( toDelete.length ) await redisClient.del(...toDelete); + if ( toDelete.length ) await deleteRedisKeys(toDelete); } async validateUserPerms ({ actor, permissions }) {