From 96a7c8e216f3962c7a42fb1941adf655622758b2 Mon Sep 17 00:00:00 2001 From: Unnati1007 Date: Mon, 8 Jun 2026 21:41:14 +0530 Subject: [PATCH 1/2] fix: resolve race condition in /generate-blog using mongodb locks --- backend/main.py | 166 ++++++++++++++++++++++++------------------- backend/test_race.py | 22 ++++++ 2 files changed, 113 insertions(+), 75 deletions(-) create mode 100644 backend/test_race.py diff --git a/backend/main.py b/backend/main.py index 6156417..0fca466 100644 --- a/backend/main.py +++ b/backend/main.py @@ -458,90 +458,106 @@ async def create_blog( user_settings = await _settings_for_user(user_id) + # --- Atomic Lock to prevent Race Conditions --- + lock_id = f"generate_blog_{problem.title}_{problem.author}_{user_email}" try: - blog_content = await run_in_threadpool(generate_blog, problem, credentials=user_settings) - efficiency = rate_code_efficiency( - problem.title, - problem.code, - problem.language or "python" - ) - except Exception as e: - return {"status": "error", "message": f"AI provider failure: {str(e)}"} - - # Resolve platform-specific credentials from database securely at runtime - devto_creds = await resolve_user_credentials(db, user_id, "devto") + await db.locks.insert_one({"_id": lock_id, "timestamp": datetime.now(timezone.utc)}) + except PyMongoError: + return { + "status": "error", + "message": f"Solution for '{problem.title}' has already been published!", + } try: - suggested_tags = await run_in_threadpool( - generate_tags, - problem, - blog_content, - credentials=user_settings, - ) - except Exception: - suggested_tags = "" - try: - platform_results = await publish_to_platforms( - problem.title, - blog_content, - platforms=problem.platforms or user_settings.get("publish_platforms"), - published=not problem.publish_as_draft, - tags=problem.tags, - credentials=devto_creds, # Using user specific keys - ) - successful = [r for r in platform_results if r.get("status") == "success"] - overall_status = ( - "success" - if len(successful) == len(platform_results) - else "partial_success" if successful else "error" - ) - except Exception as e: - return {"status": "error", "message": f"Publishing failure: {str(e)}"} - try: - record = PublishRecord( - title=problem.title, - date=datetime.now(timezone.utc).isoformat(), - platforms=[r["platform"] for r in successful], - status=overall_status, - author=problem.author, - user_email=user_email, - ) + try: + blog_content = await run_in_threadpool(generate_blog, problem, credentials=user_settings) + efficiency = rate_code_efficiency( + problem.title, + problem.code, + problem.language or "python" + ) + except Exception as e: + return {"status": "error", "message": f"AI provider failure: {str(e)}"} - await db.problem_info.update_one( - {"title": problem.title, "author": problem.author, "user_email": user_email}, - {"$set": record.model_dump()}, - upsert=True, - ) + # Resolve platform-specific credentials from database securely at runtime + devto_creds = await resolve_user_credentials(db, user_id, "devto") - except Exception as e: - print(f"Database logging failed: {e}") + try: + suggested_tags = await run_in_threadpool( + generate_tags, + problem, + blog_content, + credentials=user_settings, + ) + except Exception: + suggested_tags = "" - social_results = [] - if problem.share_to_social and successful: - post_url = next((res["url"] for res in successful if res.get("url")), None) + try: + platform_results = await publish_to_platforms( + problem.title, + blog_content, + platforms=problem.platforms or user_settings.get("publish_platforms"), + published=not problem.publish_as_draft, + tags=problem.tags, + credentials=devto_creds, # Using user specific keys + ) + successful = [r for r in platform_results if r.get("status") == "success"] + overall_status = ( + "success" + if len(successful) == len(platform_results) + else "partial_success" if successful else "error" + ) + except Exception as e: + return {"status": "error", "message": f"Publishing failure: {str(e)}"} - if post_url: - try: - # Dynamically fetch encrypted LinkedIn credentials - linkedin_creds = await resolve_user_credentials(db, user_id, "linkedin") - social_results = share_to_platforms( - title=problem.title, - post_url=post_url, - tags=problem.tags, - credentials=linkedin_creds, # Decrypted user scope profile object - ) - except Exception as e: - print(f"Social sharing failed: {e}") - return { - "status": overall_status, - "data": { - "blog_content": blog_content,"efficiency": efficiency, - "platforms": platform_results, - "social": social_results, - }, - } + try: + record = PublishRecord( + title=problem.title, + date=datetime.now(timezone.utc).isoformat(), + platforms=[r["platform"] for r in successful], + status=overall_status, + author=problem.author, + user_email=user_email, + ) + + await db.problem_info.update_one( + {"title": problem.title, "author": problem.author, "user_email": user_email}, + {"$set": record.model_dump()}, + upsert=True, + ) + + except Exception as e: + print(f"Database logging failed: {e}") + + social_results = [] + if problem.share_to_social and successful: + post_url = next((res["url"] for res in successful if res.get("url")), None) + + if post_url: + try: + # Dynamically fetch encrypted LinkedIn credentials + linkedin_creds = await resolve_user_credentials(db, user_id, "linkedin") + social_results = share_to_platforms( + title=problem.title, + post_url=post_url, + tags=problem.tags, + credentials=linkedin_creds, # Decrypted user scope profile object + ) + except Exception as e: + print(f"Social sharing failed: {e}") + return { + "status": overall_status, + "data": { + "blog_content": blog_content,"efficiency": efficiency, + "platforms": platform_results, + "social": social_results, + }, + } + finally: + # Release the lock so future attempts can proceed if this failed + await db.locks.delete_one({"_id": lock_id}) # ----------------------------- diff --git a/backend/test_race.py b/backend/test_race.py new file mode 100644 index 0000000..c74713c --- /dev/null +++ b/backend/test_race.py @@ -0,0 +1,22 @@ +import threading +import requests + +def send_request(): + payload = { + "title": "Race Condition Test", + "code": "print('hello')", + "author": "Anonymous Developer", + "publish_as_draft": True + } + # You might need to add authentication headers depending on your setup + response = requests.post("http://127.0.0.1:10000/generate-blog", json=payload) + print(f"Response: {response.json()}") + +threads = [] +for _ in range(5): + t = threading.Thread(target=send_request) + threads.append(t) + t.start() + +for t in threads: + t.join() From 68bc12641a0295b8b8c9ed1fb7fa58ac73c82a90 Mon Sep 17 00:00:00 2001 From: Unnati1007 Date: Mon, 8 Jun 2026 22:01:49 +0530 Subject: [PATCH 2/2] fix: resolve duplicate blog generation race condition --- backend/main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/main.py b/backend/main.py index 0fca466..2280f3c 100644 --- a/backend/main.py +++ b/backend/main.py @@ -37,6 +37,9 @@ from services.reminder_scheduler import start_scheduler from services.complexity_analyzer import analyze_code from social import share_to_platforms +from services.credential_service import resolve_user_credentials +from utils.crypto import encrypt +from models.user import PlatformCredential load_dotenv()