From c0f6a53223bdf25baee8754c7e76fba295f216e9 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Fri, 23 Jun 2023 17:27:25 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20race=20condition=20betwee?= =?UTF-8?q?n=20workers=20when=20creating=20note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #10345 Discovered here: https://codeberg.org/calckey/calckey/issues/10345#issuecomment-950475 --- packages/backend/src/services/note/create.ts | 54 ++++++++++++-------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index 9696c3ccaa..3a545b4872 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -465,26 +465,40 @@ export default async ( if (!note.uri) { // Publish if the post is local publishNotesStream(note); - } else if ( - boostedByRelay && - data.renote?.uri && - (await redisClient.exists(`publishedNote:${data.renote.uri}`)) === 0 - ) { - // Publish if the post was boosted by a relay and not yet published. - publishNotesStream(data.renote); - const key = `publishedNote:${data.renote.uri}`; - await redisClient.set(key, 1, "EX", 30); - } else if ( - !boostedByRelay && - note.uri && - (await redisClient.exists(`publishedNote:${note.uri}`)) === 0 - ) { - // Publish if the post came directly from a remote server, or from a - // relay that doesn't boost the post (e.g, YUKIMOCHI Activity-Relay), - // and not yet published. - const key = `publishedNote:${note.uri}`; - publishNotesStream(note); - await redisClient.set(key, 1, "EX", 30); + } else if (boostedByRelay && data.renote?.uri) { + // Use Redis transaction for atomicity + await redisClient.watch(`publishedNote:${data.renote.uri}`); + const exists = await redisClient.exists( + `publishedNote:${data.renote.uri}`, + ); + if (exists === 0) { + // Start the transaction + redisClient.multi(); + publishNotesStream(data.renote); + const key = `publishedNote:${data.renote.uri}`; + await redisClient.set(key, 1, "EX", 30); + // Execute the transaction + redisClient.exec(); + } else { + // Abort the transaction + redisClient.unwatch(); + } + } else if (!boostedByRelay && note.uri) { + // Use Redis transaction for atomicity + await redisClient.watch(`publishedNote:${note.uri}`); + const exists = await redisClient.exists(`publishedNote:${note.uri}`); + if (exists === 0) { + // Start the transaction + redisClient.multi(); + publishNotesStream(note); + const key = `publishedNote:${note.uri}`; + await redisClient.set(key, 1, "EX", 30); + // Execute the transaction + redisClient.exec(); + } else { + // Abort the transaction + redisClient.unwatch(); + } } } if (note.replyId != null) {