Skip to content

Commit 28b3f42

Browse files
[SE-4101] fix: address VisibleBlocks caching race condition (openedx#27359) (#267)
* fix: address VisibleBlocks caching race condition * sets visual block creation in an atomic transaction * refactor: add logging statement to bulk create Co-authored-by: Raul Gallegos <[email protected]> (cherry picked from commit 6ccdaca) Co-authored-by: Gábor Boros <[email protected]>
1 parent 1c6eac2 commit 28b3f42

File tree

1 file changed

+34
-5
lines changed

1 file changed

+34
-5
lines changed

lms/djangoapps/grades/models.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import six
1919
from django.apps import apps
2020
from django.contrib.auth.models import User
21-
from django.db import models
21+
from django.db import models, IntegrityError, transaction
2222
from django.utils.encoding import python_2_unicode_compatible
2323
from django.utils.timezone import now
2424
from lazy import lazy
@@ -212,16 +212,45 @@ def bulk_create(cls, user_id, course_key, block_record_lists):
212212
for the block records' course with the new VisibleBlocks.
213213
Returns the newly created visible blocks.
214214
"""
215-
created = cls.objects.bulk_create([
215+
visual_blocks = [
216216
VisibleBlocks(
217217
blocks_json=brl.json_value,
218218
hashed=brl.hash_value,
219219
course_id=course_key,
220220
)
221221
for brl in block_record_lists
222-
])
223-
cls._update_cache(user_id, course_key, created)
224-
return created
222+
]
223+
224+
created_visual_blocks = []
225+
existing_visual_blocks = []
226+
227+
try:
228+
# Try to bulk create the blocks assuming all blocks are new
229+
with transaction.atomic():
230+
created_visual_blocks = cls.objects.bulk_create(visual_blocks)
231+
except IntegrityError:
232+
log.warning('Falling back to create VisualBlocks one by one for user {} in course {}'.format(
233+
user_id,
234+
course_key
235+
))
236+
237+
# Try to create blocks one by one and mark newly created blocks
238+
for visual_block in visual_blocks:
239+
existing_blocks = cls.objects.filter(hashed=visual_block.hashed)
240+
241+
if existing_blocks.exists():
242+
# As only one record has a matching hash it is safe to use first
243+
existing_visual_blocks.append(existing_blocks.first())
244+
else:
245+
# Create the visual block and add mark as newly created
246+
visual_block.save()
247+
created_visual_blocks.append(visual_block)
248+
249+
# Update the cache with the conjunction of created and existing blocks
250+
cls._update_cache(user_id, course_key, existing_visual_blocks + created_visual_blocks)
251+
252+
# Return the new visual blocks
253+
return created_visual_blocks
225254

226255
@classmethod
227256
def bulk_get_or_create(cls, user_id, course_key, block_record_lists):

0 commit comments

Comments
 (0)