Skip to content

Conversation

@ChrisChV
Copy link
Contributor

@ChrisChV ChrisChV commented Oct 14, 2025

Description

Fixes the error described in openedx/frontend-app-authoring#1977 (comment)

  • handle_update_xblock_upstream_link is called asynchronously with celery. In update_upstream_downstream_link_handler, the xblock has the updated version, but when calling handle_update_xblock_upstream_link inside Celery, the xblock is outdated in a previous version, which is why the error occurs. This happens because on_commit_changes_to is executed before the MySQL transaction ends.
  • Added ImmediateOnCommitMixin to be used in tests that need to call on_commit_changes_to. See fix: Update on_commit_changes_to of modulestore to check MySQL transaction [FC-0097] #37485 (comment) for more info

Supporting information

Github issue: openedx/frontend-app-authoring#2484 (comment)
Internal ticket: FAL-4258

Testing instructions

Deadline

ASAP, before the Ulmo cut

Other information

N/A

`handle_update_xblock_upstream_link` is called asynchronously with celery.
In `update_upstream_downstream_link_handler`, the xblock has the updated version, but when calling `handle_update_xblock_upstream_link`
inside Celry, the xblock is outdated in a previous version, that is why the error occurs.
The immediate fix is ​​to call handle_update_xblock_upstream_link synchronously.
@openedx-webhooks
Copy link

Thanks for the pull request, @ChrisChV!

This repository is currently maintained by @openedx/wg-maintenance-edx-platform.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@openedx-webhooks openedx-webhooks added the open-source-contribution PR author is not from Axim or 2U label Oct 14, 2025
@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Oct 14, 2025
@ChrisChV
Copy link
Contributor Author

@navinkarkera @bradenmacdonald The error occurs because Celery is getting an outdated version of the xblock in handle_update_xblock_upstream_link. I think it's more due to a cache error or something like that. It shouldn't be a race condition with the xblock update (or maybe a very rare one), because I got the xblock in update_upstream_downstream_link_handler, and it is the updated version.
The simplest solution is to run it synchronously, avoiding Celery, which has outdated data. But do you know of any other solution I can try?

@bradenmacdonald
Copy link
Contributor

@ChrisChV The XBLOCK_UPDATED event is sent by this code and it uses store.on_commit_changes_to(course_key, send_updated_event) so it should only send the event after the changes have been committed to the database. Except, that code in on_commit_changes_to only waits for the MongoDB bulk ops record to be completed, but the new version isn't fully saved until the MySQL transaction is also completed, and the new version is written to the SplitModulestoreCourseIndex table in MySQL.

Maybe something like this would help?

+from django.db import transaction

...

    def on_commit_changes_to(self, course_key, fn):
        """
        Call some callback when the currently active bulk operation has saved
        """
+        # If we're in a MySQL transaction, so the new version will only be committed to the
+        # SplitModulestoreCourseIndex table after the MySQL transaction is closed.
+        def wrapped_fn():
+            transaction.on_commit(fn)

        # Check if a bulk op is active. If so, defer fn(); otherwise call it immediately.
        # Note: calling _get_bulk_ops_record() here and then checking .active can have side-effects in some cases
        # because it creates an entry in the defaultdict if none exists, so we check if the record is active using
        # the same code as _clear_bulk_ops_record(), which doesn't modify the defaultdict.
        # so we check it this way:
        if course_key and course_key.for_branch(None) in self._active_bulk_ops.records:
            bulk_ops_record = self._active_bulk_ops.records[course_key.for_branch(None)]
-            bulk_ops_record.defer_until_commit(fn)
+            bulk_ops_record.defer_until_commit(wrapped_fn)
        else:
-            fn()  # There is no active bulk operation - call fn() now.
+            wrapped_fn()  # There is no active bulk operation - call fn() now.

@mphilbrick211 mphilbrick211 added the FC Relates to an Axim Funded Contribution project label Oct 15, 2025
@mphilbrick211 mphilbrick211 moved this from Needs Triage to Waiting on Author in Contributions Oct 15, 2025
@ChrisChV ChrisChV changed the title fix: Update course library page after sync with celery fix: Update course library page after sync with celery [FC-0097] Oct 15, 2025
@navinkarkera
Copy link
Contributor

@ChrisChV @bradenmacdonald Do we have a case where we are not in a mysql transaction?

@bradenmacdonald
Copy link
Contributor

@ChrisChV Did that change help at all?

@navinkarkera It's possible to be in autocommit mode, with only implicit transactions, yes. But this code should still work fine, just trigger the event immediately in that case.

@ChrisChV
Copy link
Contributor Author

Did that change help at all?

Yes, it worked. But I need to get it ready to pass some tests. Using the last change, there are tests that show that the function passed to on_commit_changes_to is not executed. I'll try checking if there is a transaction in progress; if not, call it directly

@bradenmacdonald
Copy link
Contributor

@ChrisChV The docs say:

If you call on_commit() while there isn’t an open transaction, the callback will be executed immediately.

So that really shouldn't be necessary.

Maybe it's one of these other things the docs mention?

If the transaction is instead rolled back (typically when an unhandled exception is raised in an atomic() block), the callback will be discarded, and never called.

Passing robust=True allows the next callbacks to be executed even if the current one throws an exception. All errors derived from Python’s Exception class are caught and logged to the django.db.backends.base logger.

Or maybe this would help debug?

You can use TestCase.captureOnCommitCallbacks() to test callbacks registered with on_commit().

@ChrisChV
Copy link
Contributor Author

@bradenmacdonald This is the issue (ref):

Django’s TestCase class wraps each test in a transaction and rolls back that transaction after each test, in order to provide test isolation. This means that no transaction is ever actually committed, thus your on_commit() callbacks will never be run.

I have two options. Change each failed test so that it actually calls on_commit(): Using captureOnCommitCallbacks or TransactionTestCase; or check if on_commit_changes_to is running in a test: maybe using if 'test' in settings.SETTINGS_MODULE.lower():
What do you think?

@bradenmacdonald
Copy link
Contributor

@ChrisChV Yep, either of those sounds fine to me.

Copy link
Contributor

@navinkarkera navinkarkera left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ChrisChV Looks good 👍

  • I tested this: (Verified that the bug is fixed in sandbox)
  • I read through the code
  • I checked for accessibility issues
  • Includes documentation

@ChrisChV ChrisChV changed the title fix: Update course library page after sync with celery [FC-0097] fix: Update on_commit_changes_to of modulestore to check MySQL transaction [FC-0097] Oct 20, 2025
@ChrisChV ChrisChV merged commit 3f5ac6d into openedx:master Oct 20, 2025
49 checks passed
@ChrisChV ChrisChV deleted the chris/FAL-4258-fix-sync-with-celery branch October 20, 2025 22:02
@github-project-automation github-project-automation bot moved this from Waiting on Author to Done in Contributions Oct 20, 2025
naincy128 pushed a commit to edx/edx-platform that referenced this pull request Oct 27, 2025
…saction [FC-0097] (openedx#37485)

- `handle_update_xblock_upstream_link` is called asynchronously with celery. In `update_upstream_downstream_link_handler`, the xblock has the updated version, but when calling `handle_update_xblock_upstream_link` inside Celery, the xblock is outdated in a previous version, which is why the error occurs. This happens because `on_commit_changes_to` is executed before the MySQL transaction ends.
- Added `ImmediateOnCommitMixin` to be used in tests that need to call `on_commit_changes_to`. See openedx#37485 (comment)  for more info
naincy128 pushed a commit to edx/edx-platform that referenced this pull request Oct 27, 2025
…saction [FC-0097] (openedx#37485)

- `handle_update_xblock_upstream_link` is called asynchronously with celery. In `update_upstream_downstream_link_handler`, the xblock has the updated version, but when calling `handle_update_xblock_upstream_link` inside Celery, the xblock is outdated in a previous version, which is why the error occurs. This happens because `on_commit_changes_to` is executed before the MySQL transaction ends.
- Added `ImmediateOnCommitMixin` to be used in tests that need to call `on_commit_changes_to`. See openedx#37485 (comment)  for more info
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

FC Relates to an Axim Funded Contribution project open-source-contribution PR author is not from Axim or 2U

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

5 participants