/** * retrieve date of last value update * * @param string the id of the value to be retrieved * @return string modification date, or '0000-00-00' */ public static function get_stamp($id) { global $context; // get one attribute of the record if ($item = Values::get_record($id)) { $item = $item['edit_date']; } else { $item = NULL_DATE; } return $item; }
/** * purge idle space * * This function OPTIMIZEs tables that may create overheads because of * frequent deletions, including: cache, links, members, messages, * notifications, values, versions, visits. * * Last purge is recorded as value 'sql.tick'. * * @param boolean optional TRUE to not report on any error * @return a string to be displayed in resulting page, if any */ public static function purge($silent = FALSE) { global $context; // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $stamp = get_micro_time(); // get date of last tick include_once $context['path_to_root'] . 'shared/values.php'; $record = Values::get_record('sql.tick', NULL_DATE); // wait at least 8 hours = 24*3600 seconds between ticks if (isset($record['edit_date'])) { $target = SQL::strtotime($record['edit_date']) + 8 * 3600; } else { $target = time(); } // request to be delayed if ($target > time()) { return 'shared/sql.php: wait until ' . gmdate('r', $target) . ' GMT' . BR; } // recover unused bytes $query = 'OPTIMIZE TABLE ' . SQL::table_name('cache') . ', ' . SQL::table_name('links') . ', ' . SQL::table_name('members') . ', ' . SQL::table_name('messages') . ', ' . SQL::table_name('notifications') . ', ' . SQL::table_name('values') . ', ' . SQL::table_name('versions') . ', ' . SQL::table_name('visits'); $result = SQL::query($query, $silent); // remember tick date and resulting text Values::set('sql.tick', 'purge'); // compute execution time $time = round(get_micro_time() - $stamp, 2); // report on work achieved if ($result) { return 'shared/sql.php: unused bytes have been recovered (' . $time . ' seconds)' . BR; } else { return 'shared/sql.php: nothing to recover (' . $time . ' seconds)' . BR; } }
// do the job and provide feed-back to user $context['text'] = Hooks::include_scripts('hourly'); echo $context['text']; // remember tick date and resulting text Values::set('cron.hourly', $context['text']); // log outcome of script execution in debug mode if ($context['with_debug'] == 'Y') { Logger::remember('cron.php: hourly processing', $context['text'], 'debug'); } } // // daily jobs // echo 'Checking daily jobs...' . BR; // get date of last run $record = Values::get_record('cron.daily', NULL_DATE); // wait at least 1 day = 86400 seconds between runs if (isset($record['edit_date'])) { $target = SQL::strtotime($record['edit_date']) + 86400; } else { $target = time(); } // request to be delayed if ($target > time()) { echo 'Wait until ' . gmdate('r', $target) . ' GMT' . BR; } else { Values::set('cron.daily', 'running...'); // do the job and provide feed-back to user $context['text'] = Hooks::include_scripts('daily'); echo $context['text']; // remember tick date and resulting text
/** * process deferred messages * * Most often, the server has to stay below a given rate of messages, * for example 50 messages per hour. * * Of course, any lively community will feature bursts of activity and of * messages, therefore the need for a shaping mechanism. * * YACS implements a leaking bucket algorithm to take care of messages sent * previously: * * 1. Initially, the bucket is empty. * * 2. New messages are queued in the database, to be processed asynchronously. * * 3. On background ticks, the bucket is decremented. If the bucket becomes * empty, and if some messages have been queued, a couple of them are sent, and * the bucket is incremented accordingly. * * Bucket content is managed as value 'bucket.content' saved in the database. * * The bucket size is given by parameter $context['mail_hourly_maximum'], set * in the configuration panel for system parameters. * * This parameter has a default value of 50, meaning YACS will not send more * than 50 messages per hour. * * Background processing is either added to regular page generation or delegated * to an external sub-system (e.g., cron). In case of a large site, we recommend * to use the second solution, even if this adds additional setup steps. Your * choice will be recorded in the configuration panel for system parameters. * * @see control/configure.php * * The number of messages sent on each tick can go up to the bucket size if * background processing is external. Else it is one fourth of bucket size, to * minimize impact on watching surfer. * * @see cron.php */ public static function tick_hook() { global $context; // email services have to be activated if (!isset($context['with_email']) || $context['with_email'] != 'Y') { return; } // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $start = get_micro_time(); // get bucket size --force it if set to 0 if (!isset($context['mail_hourly_maximum']) || $context['mail_hourly_maximum'] < 5) { $context['mail_hourly_maximum'] = 50; } // get record related to last tick include_once $context['path_to_root'] . 'shared/values.php'; $bucket = Values::get_record('mailer.bucket.content', 0); $bucket['value'] = intval($bucket['value']); // some content to leak if ($bucket['value'] > 0) { // date of last stamp if (isset($bucket['edit_date'])) { $stamp = SQL::strtotime($bucket['edit_date']); } else { $stamp = time() - 3600; } // leak is maximum after one hour $leak = intval($context['mail_hourly_maximum'] * (time() - $stamp) / 3600); // preserve previous value until actual leak if ($leak < 1) { return; } // actual leak $bucket['value'] = max(0, $bucket['value'] - $leak); } // process some messages only when bucket is empty $count = 0; if ($bucket['value'] < 1) { // reduced speed if on-line processing if (isset($_SERVER['REMOTE_ADDR'])) { $slice = intval($context['mail_hourly_maximum'] / 4); } else { $slice = intval($context['mail_hourly_maximum']); } // get some messages, if any $query = "SELECT * FROM " . SQL::table_name('messages') . " ORDER BY edit_date LIMIT 0, " . $slice; if ($result = SQL::query($query)) { // process every message while ($item = SQL::fetch($result)) { Mailer::process($item['recipient'], $item['subject'], $item['message'], $item['headers']); // purge the queue $query = 'DELETE FROM ' . SQL::table_name('messages') . ' WHERE id = ' . $item['id']; SQL::query($query); // fill the bucket $bucket['value'] += 1; $count++; // take care of time if (!($count % 50)) { // ensure enough execution time Safe::set_time_limit(30); } } // close connection Mailer::close(); } } // remember new state of the bucket Values::set('mailer.bucket.content', $bucket['value']); // compute execution time $time = round(get_micro_time() - $start, 2); // report on work achieved if ($count > 1) { return 'shared/mailer.php: ' . $count . ' messages have been processed (' . $time . ' seconds)' . BR; } elseif ($count == 1) { return 'shared/mailer.php: 1 message has been processed (' . $time . ' seconds)' . BR; } elseif ($bucket['value']) { return 'shared/mailer.php: delaying messages (' . $time . ' seconds)' . BR; } else { return 'shared/mailer.php: nothing to do (' . $time . ' seconds)' . BR; } }