summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Dolgov <fox@fakecake.org>2025-05-02 10:17:13 +0300
committerAndrew Dolgov <fox@fakecake.org>2025-05-02 10:17:13 +0300
commit6a40940ad6c6facea6c8e9d0dc1896885168c442 (patch)
tree3db3b2b1035a323e655c3ff4f011c799f3b009e6
parentf22e32a26bde89e3f3bf3ac109da1eadf6c8904c (diff)
split housekeeping jobs to separate scheduled tasks on longer cooldown intervals, add table to record task execution timestamps, bump schema
-rw-r--r--classes/Config.php2
-rw-r--r--classes/PluginHost.php90
-rw-r--r--classes/RSSUtils.php95
-rw-r--r--sql/pgsql/migrations/150.sql5
-rw-r--r--sql/pgsql/schema.sql7
5 files changed, 164 insertions, 35 deletions
diff --git a/classes/Config.php b/classes/Config.php
index c4176b7a8..1afa4f043 100644
--- a/classes/Config.php
+++ b/classes/Config.php
@@ -6,7 +6,7 @@ class Config {
const T_STRING = 2;
const T_INT = 3;
- const SCHEMA_VERSION = 149;
+ const SCHEMA_VERSION = 150;
/** override default values, defined below in _DEFAULTS[], prefixing with _ENVVAR_PREFIX:
*
diff --git a/classes/PluginHost.php b/classes/PluginHost.php
index bfc02318b..484364f26 100644
--- a/classes/PluginHost.php
+++ b/classes/PluginHost.php
@@ -32,6 +32,9 @@ class PluginHost {
/** @var array<string, array<int, array{'action': string, 'description': string, 'sender': Plugin}>> */
private array $plugin_actions = [];
+ /** @var array<string, mixed> */
+ private array $scheduled_tasks = [];
+
private ?int $owner_uid = null;
private bool $data_loaded = false;
@@ -906,4 +909,91 @@ class PluginHost {
$ref = new ReflectionClass(get_class($plugin));
return basename(dirname(dirname($ref->getFileName()))) == "plugins.local";
}
+
+ /**
+ * Adds a backend scheduled task which will be executed by updater (if due) when idle during
+ * RSSUtils::housekeeping_common().
+ *
+ * The granularity is not strictly guaranteed, housekeeping is invoked several times per hour
+ * depending on how fast feed batch was processed, but no more than once per minute.
+ *
+ * Tasks are not run in user context and are available to system plugins only. Task names may not
+ * overlap.
+ *
+ * Tasks should return an integer value (return code) which is stored in the database, a value of
+ * 0 is considered successful.
+ *
+ * @param string $task_name unique name for this task, plugins should prefix this with plugin name
+ * @param string $cron_expression schedule for this task in cron format
+ * @param Closure $callback task code that gets executed
+ */
+ function add_scheduled_task(string $task_name, string $cron_expression, Closure $callback) : bool {
+ $task_name = strtolower($task_name);
+
+ if (isset($this->scheduled_tasks[$task_name])) {
+ user_error("Attempted to override already registered scheduled task $task_name", E_USER_WARNING);
+ return false;
+ } else {
+ $cron = new Cron\CronExpression($cron_expression);
+
+ $this->scheduled_tasks[$task_name] = [
+ "cron" => $cron,
+ "callback" => $callback,
+ ];
+ return true;
+ }
+ }
+
+ /**
+ * Execute scheduled tasks which are due to run and record last run timestamps.
+ * @return void
+ */
+ function run_due_tasks() {
+ Debug::log('Processing all scheduled tasks...');
+
+ $tasks_run = 0;
+
+ foreach ($this->scheduled_tasks as $task_name => $task) {
+ $last_run = '1970-01-01 00:00';
+
+ $task_record = ORM::for_table('ttrss_scheduled_tasks')
+ ->where('task_name', $task_name)
+ ->find_one();
+
+ if ($task_record)
+ $last_run = $task_record->last_run;
+
+ Debug::log("Checking scheduled task: $task_name, last run: $last_run");
+
+ if ($task['cron']->isDue($last_run)) {
+ Debug::log("Task $task_name is due, executing...");
+
+ $rc = (int) $task['callback']();
+
+ Debug::log("Task $task_name has finished with RC=$rc, recording timestamp...");
+
+ if ($task_record) {
+ $task_record->last_run = time();
+ $task_record->last_rc = $rc;
+
+ $task_record->save();
+ } else {
+ $task_record = ORM::for_table('ttrss_scheduled_tasks')->create();
+
+ $task_record->set([
+ 'task_name' => $task_name,
+ 'last_rc' => $rc,
+ 'last_run' => Db::NOW(),
+ ]);
+
+ $task_record->save();
+ }
+ }
+ }
+
+ Debug::log("Finished with $tasks_run tasks executed.");
+ }
+
+ // TODO implement some sort of automatic cleanup for orphan task execution records
+
}
diff --git a/classes/RSSUtils.php b/classes/RSSUtils.php
index acf1d14e5..d46d73793 100644
--- a/classes/RSSUtils.php
+++ b/classes/RSSUtils.php
@@ -35,11 +35,6 @@ class RSSUtils {
return sha1(implode(",", $pluginhost->get_plugin_names()) . $tmp);
}
- static function cleanup_feed_browser(): void {
- $pdo = Db::pdo();
- $pdo->query("DELETE FROM ttrss_feedbrowser_cache");
- }
-
static function cleanup_feed_icons(): void {
$pdo = Db::pdo();
$sth = $pdo->prepare("SELECT id FROM ttrss_feeds WHERE id = ?");
@@ -81,6 +76,8 @@ class RSSUtils {
die("Schema version is wrong, please upgrade the database.\n");
}
+ self::init_housekeeping_tasks();
+
$pdo = Db::pdo();
$feeds_in_the_future = ORM::for_table('ttrss_feeds')
@@ -1432,15 +1429,6 @@ class RSSUtils {
WHERE ' . Db::past_comparison_qpart('created_at', '<', 7, 'day'));
}
- /**
- * @deprecated table not used
- */
- static function expire_feed_archive(): void {
- $pdo = Db::pdo();
-
- $pdo->query("DELETE FROM ttrss_archived_feeds");
- }
-
static function expire_lock_files(): void {
Debug::log("Removing old lock files...", Debug::LOG_VERBOSE);
@@ -1659,14 +1647,6 @@ class RSSUtils {
mb_strtolower(strip_tags($title), 'utf-8'));
}
- /* counter cache is no longer used, if called truncate leftover data */
- static function cleanup_counters_cache(): void {
- $pdo = Db::pdo();
-
- $pdo->query("DELETE FROM ttrss_counters_cache");
- $pdo->query("DELETE FROM ttrss_cat_counters_cache");
- }
-
static function disable_failed_feeds(): void {
if (Config::get(Config::DAEMON_UNSUCCESSFUL_DAYS_LIMIT) > 0) {
@@ -1735,21 +1715,68 @@ class RSSUtils {
}
}
- static function housekeeping_common(): void {
- $cache = DiskCache::instance("");
- $cache->expire_all();
+ /** Init all system tasks which are run periodically by updater in housekeeping_common() */
+ static function init_housekeeping_tasks() : void {
+ Debug::log('Registering scheduled tasks for housekeeping...');
+
+ PluginHost::getInstance()->add_scheduled_task('purge_orphans', '@daily',
+ function() {
+ Article::_purge_orphans(); return 0;
+ }
+ );
+
+ PluginHost::getInstance()->add_scheduled_task('disk_cache_expire_all', '@daily',
+ function() {
+ $cache = DiskCache::instance("");
+ $cache->expire_all();
+
+ return 0;
+ }
+ );
+
+ PluginHost::getInstance()->add_scheduled_task('expire_error_log', '@hourly',
+ function() {
+ self::expire_error_log();
+
+ return 0;
+ }
+ );
- self::migrate_feed_icons();
- self::expire_lock_files();
- self::expire_error_log();
- self::expire_feed_archive();
- self::cleanup_feed_browser();
- self::cleanup_feed_icons();
- self::disable_failed_feeds();
+ PluginHost::getInstance()->add_scheduled_task('expire_lock_files', '@hourly',
+ function() {
+ self::expire_lock_files();
- Article::_purge_orphans();
- self::cleanup_counters_cache();
+ return 0;
+ }
+ );
+
+ PluginHost::getInstance()->add_scheduled_task('disable_failed_feeds', '@daily',
+ function() {
+ self::disable_failed_feeds();
+
+ return 0;
+ }
+ );
+ PluginHost::getInstance()->add_scheduled_task('migrate_feed_icons', '@daily',
+ function() {
+ self::migrate_feed_icons();
+
+ return 0;
+ }
+ );
+
+ PluginHost::getInstance()->add_scheduled_task('cleanup_feed_icons', '@daily',
+ function() {
+ self::cleanup_feed_icons();
+
+ return 0;
+ }
+ );
+ }
+
+ static function housekeeping_common(): void {
+ PluginHost::getInstance()->run_due_tasks();
PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING);
}
diff --git a/sql/pgsql/migrations/150.sql b/sql/pgsql/migrations/150.sql
new file mode 100644
index 000000000..55d9609e9
--- /dev/null
+++ b/sql/pgsql/migrations/150.sql
@@ -0,0 +1,5 @@
+create table ttrss_scheduled_tasks(
+ id serial not null primary key,
+ task_name varchar(250) unique not null,
+ last_rc integer not null,
+ last_run timestamp not null default NOW());
diff --git a/sql/pgsql/schema.sql b/sql/pgsql/schema.sql
index acfa619c9..3ff9a6674 100644
--- a/sql/pgsql/schema.sql
+++ b/sql/pgsql/schema.sql
@@ -1,3 +1,4 @@
+drop table if exists ttrss_scheduled_tasks;
drop table if exists ttrss_error_log;
drop table if exists ttrss_plugin_storage;
drop table if exists ttrss_linked_feeds;
@@ -394,4 +395,10 @@ create table ttrss_error_log(
context text not null,
created_at timestamp not null);
+create table ttrss_scheduled_tasks(
+ id serial not null primary key,
+ task_name varchar(250) unique not null,
+ last_rc integer not null,
+ last_run timestamp not null default NOW());
+
commit;