Expected Behavior
A block whose XBlock type has no installed plugin is skipped during the block structure build. The rest of the course still renders.
Current Behavior
ContentLibraryTransformer.collect() calls XBlock.load_class() on every block during topological traversal, with nothing catching PluginMissingError:
https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/course_blocks/transformers/library_content.py#L73-L76
One unloadable block aborts the build for the entire course. Because the build fails, nothing is written to BlockStructureModel, the fallback path raises BlockStructureNotFound, and the next request repeats the identical failure. There is no cached structure to serve and no way to create one, so the course never recovers on its own.
We hit this in production on a course holding a block of type p, a bare <p> element promoted to block level by an OLX import:
PluginMissingError: p
File "lms/djangoapps/course_blocks/transformers/library_content.py", line 73, in collect
for block_key in block_structure.topological_traversal(
File "openedx/core/lib/graph_traversals.py", line 329, in _traverse_generic
should_yield_node = filter_func(current_node)
File "lms/djangoapps/course_blocks/transformers/library_content.py", line 74, in <lambda>
filter_func=lambda block_key: issubclass(XBlock.load_class(block_key.block_type), ItemBankMixin),
File "xblock/plugin.py", line 158, in load_class
raise PluginMissingError(identifier)
During handling of the above exception, another exception occurred:
BlockStructureNotFound: Block structure not found; data_usage_key: block-v1:ORG+NUMBER+RUN+type@course+block@course
Every learner-facing endpoint for that course returned 500 continuously for over 19 hours, at roughly 650 failures an hour:
- /api/course_home/outline/
- /api/courseware/course/
- /api/course_home/dates/
- /courses/…/progress
- /api/discussion/v3/course_topics/
- /api/courses/v1/blocks/
- lms.djangoapps.grades.tasks.recalculate_subsection_grade_v3
update_course_in_cache_v2 fails on the same traversal, so the CMS cannot rebuild the structure either. We have seen the same crash on a separate course with PluginMissingError: ubcpi, so it is not specific to one bad block type.
Steps to Reproduce
- Get a course into the modulestore holding a block whose block_type has no installed XBlock, e.g. p from an OLX import where a bare <p> element sits at block level.
- Clear the block structure cache for that course.
- As an enrolled learner, request /api/course_home/outline/<course_id>.
- The request 500s with PluginMissingError. Every subsequent request repeats it.
Possible Solution
Route every XBlock.load_class call in the module through a helper that returns None on PluginMissingError, and have the collect() filter treat an unloadable type as "not an ItemBankMixin".
The module's other three load_class call sites already guard on block_class is None, but load_class raises rather than returning None, so those guards cannot fire today. The fix makes them live.
I have a patch with a regression test and will open a PR referencing this issue.
Expected Behavior
A block whose XBlock type has no installed plugin is skipped during the block structure build. The rest of the course still renders.
Current Behavior
ContentLibraryTransformer.collect() calls XBlock.load_class() on every block during topological traversal, with nothing catching PluginMissingError:
https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/course_blocks/transformers/library_content.py#L73-L76
One unloadable block aborts the build for the entire course. Because the build fails, nothing is written to BlockStructureModel, the fallback path raises BlockStructureNotFound, and the next request repeats the identical failure. There is no cached structure to serve and no way to create one, so the course never recovers on its own.
We hit this in production on a course holding a block of type p, a bare <p> element promoted to block level by an OLX import:
PluginMissingError: p File "lms/djangoapps/course_blocks/transformers/library_content.py", line 73, in collect for block_key in block_structure.topological_traversal( File "openedx/core/lib/graph_traversals.py", line 329, in _traverse_generic should_yield_node = filter_func(current_node) File "lms/djangoapps/course_blocks/transformers/library_content.py", line 74, in <lambda> filter_func=lambda block_key: issubclass(XBlock.load_class(block_key.block_type), ItemBankMixin), File "xblock/plugin.py", line 158, in load_class raise PluginMissingError(identifier) During handling of the above exception, another exception occurred: BlockStructureNotFound: Block structure not found; data_usage_key: block-v1:ORG+NUMBER+RUN+type@course+block@courseEvery learner-facing endpoint for that course returned 500 continuously for over 19 hours, at roughly 650 failures an hour:
update_course_in_cache_v2 fails on the same traversal, so the CMS cannot rebuild the structure either. We have seen the same crash on a separate course with PluginMissingError: ubcpi, so it is not specific to one bad block type.
Steps to Reproduce
Possible Solution
Route every XBlock.load_class call in the module through a helper that returns None on PluginMissingError, and have the collect() filter treat an unloadable type as "not an ItemBankMixin".
The module's other three load_class call sites already guard on block_class is None, but load_class raises rather than returning None, so those guards cannot fire today. The fix makes them live.
I have a patch with a regression test and will open a PR referencing this issue.