Cron split into Cronish and OpportunisticQM

/main/cron changed to /main/runqueue

The key-required functionality is not throughly tested yet.
This commit is contained in:
Mikael Nordfeldth 2013-11-20 21:20:42 +01:00
parent 0cd93c2761
commit 9400795a5f
9 changed files with 388 additions and 3 deletions

View File

@ -294,8 +294,9 @@ $default =
'plugins' => 'plugins' =>
array('core' => array( array('core' => array(
'AuthCrypt' => array(), 'AuthCrypt' => array(),
'Cron' => array(), 'Cronish' => array(),
'LRDD' => array(), 'LRDD' => array(),
'OpportunisticQM' => array(),
'StrictTransportSecurity' => array(), 'StrictTransportSecurity' => array(),
), ),
'default' => array( 'default' => array(

View File

@ -67,8 +67,8 @@ abstract class QueueManager extends IoManager
self::$qm = new UnQueueManager(); self::$qm = new UnQueueManager();
} else { } else {
switch ($type) { switch ($type) {
case 'cron': case 'opportunistic':
self::$qm = new CronQueueManager(); self::$qm = new OpportunisticQueueManager();
break; break;
case 'db': case 'db':
self::$qm = new DBQueueManager(); self::$qm = new DBQueueManager();

View File

@ -0,0 +1,42 @@
<?php
class CronishPlugin extends Plugin {
public function onCronHourly()
{
common_debug('CRON: Running hourly cron job!');
}
public function onCronDaily()
{
common_debug('CRON: Running daily cron job!');
}
public function onCronWeekly()
{
common_debug('CRON: Running weekly cron job!');
}
/**
* When the page has finished rendering, let's do some cron jobs
* if we have the time.
*/
public function onEndActionExecute($status, Action $action)
{
$cron = new Cronish();
$cron->callTimedEvents();
return true;
}
public function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'Cronish',
'version' => GNUSOCIAL_VERSION,
'author' => 'Mikael Nordfeldth',
'homepage' => 'http://www.gnu.org/software/social/',
'description' =>
// TRANS: Plugin description.
_m('Cronish plugin that executes events on a near-hour/day/week basis.'));
return true;
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* GNU social cron-on-visit class
*
* Keeps track, through Config dataobject class, of relative time since the
* last run in order to to run event handlers with certain intervals.
*
* @category Cron
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class Cronish
{
/**
* Will call events as close as it gets to one hour. Event handlers
* which use this MUST be as quick as possible, maybe only adding a
* queue item to be handled later or something. Otherwise execution
* will timeout for PHP - or at least cause unnecessary delays for
* the unlucky user who visits the site exactly at one of these events.
*/
public function callTimedEvents()
{
$timers = array('hourly' => 3600,
'daily' => 86400,
'weekly' => 604800);
foreach($timers as $name=>$interval) {
$run = false;
$lastrun = new Config();
$lastrun->section = 'cron';
$lastrun->setting = 'last_' . $name;
$found = $lastrun->find(true);
if (!$found) {
$lastrun->value = time();
if ($lastrun->insert() === false) {
common_log(LOG_WARNING, "Could not save 'cron' setting '{$name}'");
continue;
}
$run = true;
} elseif ($lastrun->value < time() - $interval) {
$orig = clone($lastrun);
$lastrun->value = time();
$lastrun->update($orig);
$run = true;
}
if ($run === true) {
// such as CronHourly, CronDaily, CronWeekly
Event::handle('Cron' . ucfirst($name));
}
}
}
}

View File

@ -0,0 +1,46 @@
<?php
class OpportunisticQMPlugin extends Plugin {
public $qmkey = false;
public $secs_per_action = 1; // total seconds to run script per action
public $rel_to_pageload = true; // relative to pageload or queue start
public function onRouterInitialized($m)
{
$m->connect('main/runqueue', array('action' => 'runqueue'));
}
/**
* When the page has finished rendering, let's do some cron jobs
* if we have the time.
*/
public function onEndActionExecute($status, Action $action)
{
if ($action instanceof RunqueueAction) {
return true;
}
global $_startTime;
$args = array(
'qmkey' => common_config('opportunisticqm', 'qmkey'),
'max_execution_time' => $this->secs_per_action,
'started_at' => $this->rel_to_pageload ? $_startTime : null,
);
$qm = new OpportunisticQueueManager($args);
$qm->runQueue();
return true;
}
public function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'OpportunisticQM',
'version' => GNUSOCIAL_VERSION,
'author' => 'Mikael Nordfeldth',
'homepage' => 'http://www.gnu.org/software/social/',
'description' =>
// TRANS: Plugin description.
_m('Opportunistic queue manager plugin for background processing.'));
return true;
}
}

View File

@ -0,0 +1,61 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('GNUSOCIAL')) { exit(1); }
class RunqueueAction extends Action
{
protected $qm = null;
protected function prepare(array $args=array())
{
parent::prepare($args);
$args = array();
foreach (array('qmkey') as $key) {
if ($this->arg($key) !== null) {
$args[$key] = $this->arg($key);
}
}
try {
$this->qm = new OpportunisticQueueManager($args);
} catch (RunQueueBadKeyException $e) {
return false;
}
header('Content-type: text/plain; charset=utf-8');
return true;
}
protected function handle() {
// We don't need any of the parent functionality from parent::handle() here.
// runQueue is a loop that works until limits have passed or there is no more work
if ($this->qm->runQueue() === true) {
// We don't have any more work
$this->text('0');
} else {
// There were still items left in queue when we aborted
$this->text('1');
}
}
}

View File

@ -0,0 +1,102 @@
<?php
/**
* GNU social queue-manager-on-visit class
*
* Will run events for a certain time, or until finished.
*
* Configure remote key if wanted with $config['opportunisticqm']['qmkey'] and
* use with /main/runqueue?qmkey=abc123
*
* @category Cron
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class OpportunisticQueueManager extends DBQueueManager
{
protected $qmkey = false;
protected $max_execution_time = null;
protected $max_queue_items = null;
protected $started_at = null;
protected $handled_items = 0;
const MAXEXECTIME = 30; // typically just used for the /main/cron action
public function __construct(array $args=array()) {
foreach (get_class_vars(get_class($this)) as $key=>$val) {
if (array_key_exists($key, $args)) {
$this->$key = $args[$key];
}
}
$this->verifyKey();
if ($this->started_at === null) {
$this->started_at = time();
}
if ($this->max_execution_time === null) {
$this->max_execution_time = ini_get('max_execution_time') ?: self::MAXEXECTIME;
}
return parent::__construct();
}
protected function verifyKey()
{
if ($this->qmkey !== common_config('opportunisticqm', 'qmkey')) {
throw new RunQueueBadKeyException($this->qmkey);
}
}
public function canContinue()
{
$time_passed = time() - $this->started_at;
// Only continue if limit values are sane
if ($time_passed <= 0 && (!is_null($this->max_queue_items) && $this->max_queue_items <= 0)) {
return false;
}
// If too much time has passed, stop
if ($time_passed >= $this->max_execution_time) {
return false;
}
// If we have a max-item-limit, check if it has been passed
if (!is_null($this->max_queue_items) && $this->handled_items >= $this->max_queue_items) {
return false;
}
return true;
}
public function poll()
{
$this->handled_items++;
if (!parent::poll()) {
throw new RunQueueOutOfWorkException();
}
return true;
}
/**
* Takes care of running through the queue items, returning when
* the limits setup in __construct are met.
*
* @return true on workqueue finished, false if there are still items in the queue
*/
public function runQueue()
{
while ($this->canContinue()) {
try {
$this->poll();
} catch (RunQueueOutOfWorkException $e) {
common_debug('Opportunistic queue manager finished.');
return true;
}
}
common_debug('Opportunistic queue manager passed execution time/item handling limit without being out of work.');
return false;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Class for the GNU social cron exception when a bad key is used
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Exception
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) { exit(1); }
class RunQueueBadKeyException extends ClientException
{
public $qmkey;
public function __construct($qmkey)
{
$this->qmkey = $qmkey;
parent::__construct(_('Bad queue manager key was used.'));
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Class for the GNU social cron exception when there is no more work to be done
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Exception
* @package GNUsocial
* @author Mikael Nordfeldth <mmn@hethane.se>
* @copyright 2013 Free Software Foundation, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
* @link http://www.gnu.org/software/social/
*/
if (!defined('GNUSOCIAL')) { exit(1); }
class RunQueueOutOfWorkException extends ServerException
{
public function __construct()
{
parent::__construct(_('Opportunistic queue manager is out of work (no more items).'));
}
}