diff --git a/opencti-platform/opencti-graphql/src/database/middleware.js b/opencti-platform/opencti-graphql/src/database/middleware.js index cacfcc63ed992..7ee9f6b35d8f8 100644 --- a/opencti-platform/opencti-graphql/src/database/middleware.js +++ b/opencti-platform/opencti-graphql/src/database/middleware.js @@ -203,6 +203,7 @@ import { import { buildEntityData, buildInnerRelation, buildRelationData } from './data-builder'; import { deleteAllObjectFiles, moveAllFilesFromEntityToAnother, uploadToStorage } from './file-storage-helper'; import { storeFileConverter } from './file-storage'; +import { inDraftContext } from '../utils/draftContext'; // region global variables const MAX_BATCH_SIZE = 300; @@ -1439,7 +1440,7 @@ export const mergeEntities = async (context, user, targetEntityId, sourceEntityI let lock; try { // Lock the participants that will be merged - lock = await lockResource(participantIds); + lock = await lockResource(participantIds, { draftId: inDraftContext(context, user) }); // Entities must be fully loaded with admin user to resolve/move all dependencies const initialInstance = await storeLoadByIdWithRefs(context, user, targetEntityId); const target = { ...initialInstance }; @@ -1950,7 +1951,7 @@ export const updateAttributeMetaResolved = async (context, user, initial, inputs const participantIds = R.uniq(locksIds.filter((e) => !locks.includes(e))); try { // Try to get the lock in redis - lock = await lockResource(participantIds); + lock = await lockResource(participantIds, { draftId: inDraftContext(context, user) }); // region handle attributes // Only for StixCyberObservable const lookingEntities = []; @@ -2766,7 +2767,7 @@ export const createRelationRaw = async (context, user, rawInput, opts = {}) => { const participantIds = inputIds.filter((e) => !locks.includes(e)); try { // Try to get the lock in redis - lock = await lockResource(participantIds); + lock = await lockResource(participantIds, { draftId: inDraftContext(context, user) }); // region check existing relationship const existingRelationships = await getExistingRelations(context, user, resolvedInput, opts); let existingRelationship = null; @@ -2941,7 +2942,7 @@ const createEntityRaw = async (context, user, rawInput, type, opts = {}) => { let lock; try { // Try to get the lock in redis - lock = await lockResource(participantIds); + lock = await lockResource(participantIds, { draftId: inDraftContext(context, user) }); // Generate the internal id if needed const standardId = resolvedInput.standard_id || generateStandardId(type, resolvedInput); // Check if the entity exists, must be done with SYSTEM USER to really find it. @@ -3157,7 +3158,7 @@ export const internalDeleteElementById = async (context, user, id, opts = {}) => const participantIds = [element.internal_id]; try { // Try to get the lock in redis - lock = await lockResource(participantIds); + lock = await lockResource(participantIds, { draftId: inDraftContext(context, user) }); if (isStixRefRelationship(element.entity_type)) { const referencesPromises = opts.references ? internalFindByIds(context, user, opts.references, { type: ENTITY_TYPE_EXTERNAL_REFERENCE }) : Promise.resolve([]); const references = await Promise.all(referencesPromises); diff --git a/opencti-platform/opencti-graphql/src/database/redis.ts b/opencti-platform/opencti-graphql/src/database/redis.ts index 3c6efa03cb2c7..662fe8c989dbc 100644 --- a/opencti-platform/opencti-graphql/src/database/redis.ts +++ b/opencti-platform/opencti-graphql/src/database/redis.ts @@ -355,8 +355,8 @@ export const redisFetchLatestDeletions = async () => { await getClientLock().zremrangebyscore('platform-deletions', '-inf', time - (5 * 1000)); return getClientLock().zrange('platform-deletions', 0, -1); }; -interface LockOptions { automaticExtension?: boolean, retryCount?: number } -const defaultLockOpts: LockOptions = { automaticExtension: true, retryCount: conf.get('app:concurrency:retry_count') }; +interface LockOptions { automaticExtension?: boolean, retryCount?: number, draftId?: string } +const defaultLockOpts: LockOptions = { automaticExtension: true, retryCount: conf.get('app:concurrency:retry_count'), draftId: '' }; const getStackTrace = () => { const obj: any = {}; Error.captureStackTrace(obj, getStackTrace); @@ -366,7 +366,8 @@ export const lockResource = async (resources: Array, opts: LockOptions = let timeout: NodeJS.Timeout | undefined; let extension: undefined | Promise; const initialCallStack = getStackTrace(); - const locks = R.uniq(resources).map((id) => `{locks}:${id}`); + const draftId = opts.draftId ? opts.draftId : ''; + const locks = R.uniq(resources).map((id) => `{locks}:${id}${draftId}`); const automaticExtensionThreshold = conf.get('app:concurrency:extension_threshold'); const retryDelay = conf.get('app:concurrency:retry_delay'); const retryJitter = conf.get('app:concurrency:retry_jitter');