Skip to content

Commit

Permalink
MDL-29847 question preview: garbage collect old preview data.
Browse files Browse the repository at this point in the history
This adds cron code which looks for question previews that have not been
touched for more than 24 hours, and deletes them.

We try to delete previews immediately. For example if the user clicks
start again, then we immediately delete their previous preview. However,
we can't do that if they just close the preview window. Hence we need
some cron code to clean up old preview that have got left lying around.

Normally, this code will not have much to do, so it will be very fast,
so we can afford to run it every cron.

This has been implemented in such a way that in future it will be easy
to add other cron code to the question bank.

Sadly, to make this work on MySQL, we require a horrible hack in the
already hacky delete_usage_records_for_mysql function.
  • Loading branch information
timhunt committed Aug 17, 2012
1 parent 7033316 commit 3e6b276
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 1 deletion.
7 changes: 7 additions & 0 deletions lib/cronlib.php
Expand Up @@ -373,6 +373,13 @@ function cron_run() {
}


// Run question bank clean-up.
mtrace("Starting the question bank cron...", '');
require_once($CFG->libdir . '/questionlib.php');
question_bank::cron();
mtrace('done.');


//Run registration updated cron
mtrace(get_string('siteupdatesstart', 'hub'));
require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
Expand Down
11 changes: 11 additions & 0 deletions question/engine/bank.php
Expand Up @@ -398,6 +398,17 @@ public static function fraction_options_full() {
self::ensure_fraction_options_initialised();
return self::$fractionoptionsfull;
}

/**
* Perform scheduled maintenance tasks relating to the question bank.
*/
public static function cron() {
global $CFG;

// Delete any old question preview that got left in the database.
require_once($CFG->dirroot . '/question/previewlib.php');
question_preview_cron();
}
}


Expand Down
10 changes: 9 additions & 1 deletion question/engine/datalib.php
Expand Up @@ -758,14 +758,22 @@ public function delete_questions_usage_by_activities(qubaid_condition $qubaids)
* @param qubaid_condition $qubaids identifies which question useages to delete.
*/
protected function delete_usage_records_for_mysql(qubaid_condition $qubaids) {
$qubaidtest = $qubaids->usage_id_in();
if (strpos($qubaidtest, 'question_usages') !== false &&
strpos($qubaidtest, 'IN (SELECT') === 0) {
// This horrible hack is required by MDL-29847. It comes from
// http://www.xaprb.com/blog/2006/06/23/how-to-select-from-an-update-target-in-mysql/
$qubaidtest = 'IN (SELECT * FROM ' . substr($qubaidtest, 3) . ' AS hack_subquery_alias)';
}

// TODO once MDL-29589 is fixed, eliminate this method, and instead use the new $DB API.
$this->db->execute('
DELETE qu, qa, qas, qasd
FROM {question_usages} qu
JOIN {question_attempts} qa ON qa.questionusageid = qu.id
LEFT JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
WHERE qu.id ' . $qubaids->usage_id_in(),
WHERE qu.id ' . $qubaidtest,
$qubaids->usage_id_in_params());
}

Expand Down
27 changes: 27 additions & 0 deletions question/previewlib.php
Expand Up @@ -316,3 +316,30 @@ function restart_preview($previewid, $questionid, $displayoptions, $context) {
redirect(question_preview_url($questionid, $displayoptions->behaviour,
$displayoptions->maxmark, $displayoptions, $displayoptions->variant, $context));
}

/**
* Scheduled tasks relating to question preview. Specifically, delete any old
* previews that are left over in the database.
*/
function question_preview_cron() {
$maxage = 24*60*60; // We delete previews that have not been touched for 24 hours.
$lastmodifiedcutoff = time() - $maxage;

mtrace("\n Cleaning up old question previews...", '');
$oldpreviews = new qubaid_join('{question_usages} quba', 'quba.id',
'quba.component = :qubacomponent
AND NOT EXISTS (
SELECT 1
FROM {question_attempts} qa
JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
WHERE qa.questionusageid = quba.id
AND (qa.timemodified > :qamodifiedcutoff
OR qas.timecreated > :stepcreatedcutoff)
)
',
array('qubacomponent' => 'core_question_preview',
'qamodifiedcutoff' => $lastmodifiedcutoff, 'stepcreatedcutoff' => $lastmodifiedcutoff));

question_engine::delete_questions_usage_by_activities($oldpreviews);
mtrace('done.');
}

0 comments on commit 3e6b276

Please sign in to comment.