Uploaded image for project: 'Moodle'
  1. Moodle
  2. MDL-76716

unhandled dml_exception on grade_grade deletion results in hanging transaction

XMLWordPrintable

    • MOODLE_311_STABLE, MOODLE_400_STABLE, MOODLE_401_STABLE
    • MOODLE_402_STABLE, MOODLE_403_STABLE
    • MDL-76716-m403
    • MDL-76716-master
    • Hide

      (difficulty: moderate - knowledgeable administrator should be able to test it - debugging needs to be turned on)

      Pre-requisites

      1. Login as admin.
      2. Navigate to Site administrator > Plugins > Manage enrol plugins.
      3. Enable the Course meta link plugin.
      4. There must be two courses to test with.
      5. One course will be the "parent" course and must have a quiz.
      6. The quiz must generate a grade when completed.

      Steps

      1. Navigate to Site administration > Development > Debugging
        1. Set Debug message value to DEVELOPER: extra Moodle debug messages for developers.
      2. Navigate to Site Administration > Courses > Manage courses and categories.
        1. Navigate to the Course that will be the "parent" course.
        2. Navigate to Enrolled Users.
        3. In the dropdown next to "Enrol users", select "Enrolment methods".
        4. On the Enrolment methods page, next to "Add method", select the "Course meta link" option.
        5. Wait for the page to refresh, then select the target child course from the "Link Course" dropdown.
        6. Click "Add Method"
        7. Navigate to the Child course
        8. Enrol the student as Student on the child course.
        9. Verify that the student was also enrolled on the parent course by Meta link plugin.
      3. Log out with administrator.
      4. Log in with the student account.
        1. Navigate to My courses.
        2. Select the parent course.
        3. Complete the quiz.
        4. Verify that a grade was allocated.
      5. Log out with the student account.
      6. Log in to Moodle as administrator.
        1. Navigate to the child course.
        2. Unenrol the student.
        3. Verify that the user no longer has an enrolment on the child course.
        4. Verify that the user no longer has an enrolment on the parent course.
        5. Verify that the grade for the user is not visible in the gradebook on the parent course.
      Show
      (difficulty: moderate - knowledgeable administrator should be able to test it - debugging needs to be turned on) Pre-requisites Login as admin. Navigate to Site administrator > Plugins > Manage enrol plugins. Enable the Course meta link plugin. There must be two courses to test with. One course will be the "parent" course and must have a quiz. The quiz must generate a grade when completed. Steps Navigate to Site administration > Development > Debugging Set Debug message value to DEVELOPER: extra Moodle debug messages for developers. Navigate to Site Administration > Courses > Manage courses and categories. Navigate to the Course that will be the "parent" course. Navigate to Enrolled Users. In the dropdown next to "Enrol users", select "Enrolment methods". On the Enrolment methods page, next to "Add method", select the "Course meta link" option. Wait for the page to refresh, then select the target child course from the "Link Course" dropdown. Click "Add Method" Navigate to the Child course Enrol the student as Student on the child course. Verify that the student was also enrolled on the parent course by Meta link plugin. Log out with administrator. Log in with the student account. Navigate to My courses. Select the parent course. Complete the quiz. Verify that a grade was allocated. Log out with the student account. Log in to Moodle as administrator. Navigate to the child course. Unenrol the student. Verify that the user no longer has an enrolment on the child course. Verify that the user no longer has an enrolment on the parent course. Verify that the grade for the user is not visible in the gradebook on the parent course.

      When bulk deleting users there is a situation where a dmlexception can be thrown if a bad record is hit.

       

      public function delete($source = null) {        
              global $DB;
              $transaction = $DB->start_delegated_transaction();        
              $success = parent::delete($source);
              // If the grade was deleted successfully trigger a grade_deleted event.            if ($success && !empty($this->grade_item)) {                                     
                 \core\event\grade_deleted::create_from_grade($this)->trigger();        
              }
        
              $transaction->allow_commit();        
              return $success;    
      } 

      Because the exception is thrown from the parent::delete($source) the allow_commit() is never executed resulting in the rest of the bulk process falling under the transaction that will never be resolved and thus rolled back on process exit. For us, this prevented all future user deletions from occurring.

       

      This is our current solution in case anyone else is running into the same issue. I haven't got the time at the moment to write a test for this change. But I'll try to put something together if I find time in the next few weeks.

       

      public function delete($source = null) {
              global $DB;        
              $transaction = $DB->start_delegated_transaction();
              $success = false;
              try {
                  $success = parent::delete($source);
                  // If the grade was deleted successfully trigger a grade_deleted event.
                  if ($success && !empty($this->grade_item)) {
                      $this->load_grade_item();
                      \core\event\grade_deleted::create_from_grade($this)->trigger();
                  }
                  $transaction->allow_commit();
              } catch(Exception $e) {
                  $DB->rollback_delegated_transaction($transaction, $e);
              }
              return $success;
          } 

      Technically, given that the dml exception is almost always going to be due to a missing record $transaction->allow_commit(); could be called in the catch, but I felt it was safer in the case where some other cause is to blame to simply unroll the delete. Would love feedback if anyone is more familiar with this area of the code.

            wireman Stefan van der Vyver
            tdjones Trevor Jones
            Neill Magill Neill Magill
            Huong Nguyen Huong Nguyen
            Ron Carl Alfon Yu Ron Carl Alfon Yu
            Votes:
            1 Vote for this issue
            Watchers:
            22 Start watching this issue

              Created:
              Updated:
              Resolved:

                Estimated:
                Original Estimate - 0 minutes
                0m
                Remaining:
                Remaining Estimate - 0 minutes
                0m
                Logged:
                Time Spent - 3 hours, 6 minutes
                3h 6m

                  Error rendering 'clockify-timesheets-time-tracking-reports:timer-sidebar'. Please contact your Jira administrators.