Background Tasks: Heartbeat and EMPS_Service
EMPS has a simple but effective system for running periodic background tasks. It consists of two classes:
EMPS_Heartbeat— a dispatcher that calls a list of internal URLs in parallel using cURL multi-handle. It is called once per minute (or other interval) by an external cron job.EMPS_Service— a throttle wrapper used inside individual task scripts. It ensures each task runs no more often than its configured interval, regardless of how often the heartbeat fires.
How It Works
cron (every minute)
→ GET /heartbeat/
→ EMPS_Heartbeat::execute()
→ parallel cURL: GET /do-work/
→ parallel cURL: GET /send-notifications/
→ parallel cURL: GET /sqlsync/ (auto-sync)
each URL:
EMPS_Service::is_runnable() → run or skip
The heartbeat URL is a regular EMPS module that you set up in your project.
Setting Up the Heartbeat Module
Create modules/heartbeat/project.php in your project:
<?php
$emps->no_smarty = true;
set_time_limit(0);
ignore_user_abort(true);
require_once $emps->core_module("heartbeat.class");
$hb = new EMPS_Heartbeat();
if ($emps->website_ctx == $emps->default_ctx) {
// Tasks that run on the main website context
$hb->add_url("/do-work/");
$hb->add_url("/send-notifications/");
$hb->execute();
} else {
// Tasks specific to a sub-website (if using multi-site setup)
}
The built-in EMPS heartbeat (modules/heartbeat/heartbeat.php in the EMPS core)
automatically looks for and includes your project.php after running its own
built-in tasks (mail queue, session purge, Smarty cache purge).
Register the heartbeat in cron (runs every minute):
* * * * * curl -s https://your-site.example/heartbeat/ > /dev/null
Or, if EMPS Factory manages your server, it can call the heartbeat automatically.
Writing a Service Script
Each task is its own EMPS module. Create modules/do/work/work.php
(accessible at URL /do-work/):
<?php
$emps->plaintext_response();
require_once $emps->core_module("service.class");
$srv = new EMPS_Service();
$srv->init("_task_do_work", 60 * 60); // run at most once per hour
if ($srv->is_runnable()) {
// your task logic here
echo "Running task...\n";
// example: process pending items
$r = $emps->db->query("select * from " . TP . "items where status = 0 limit 50");
while ($ra = $emps->db->fetch_named($r)) {
// process $ra
echo "Processed item {$ra['id']}\n";
}
}
EMPS_Service::init($varname, $interval)
| Parameter | Description |
|---|---|
$varname |
Unique string key used to store the last-run timestamp in EMPS settings. Use a descriptive name prefixed with _ to avoid collisions, e.g. _task_send_notifications. |
$interval |
Minimum seconds between runs. 3600 = once per hour, 86400 = once per day. |
EMPS_Service::is_runnable()
Returns true if enough time has passed since the last run (or if ?runnow=1
is appended to the URL for manual triggering during development). When it
returns true, it immediately saves the current timestamp so concurrent
requests cannot double-trigger the task.
Running a Task Manually
During development, append ?runnow=1 to force the task to run regardless of
the interval:
https://your-site.example/do-work/?runnow=1
?runnow=1 is only honoured when the request comes from localhost or from a
logged-in admin. This check is built into is_runnable() — no extra code
needed in your service scripts.
Auto-Syncing Database Structure
To have SQLSync run automatically as part of the heartbeat, list your module
names in modules/_common/config/sqlsync.txt:
news,projects,items,notifications
The heartbeat will call /sqlsync/news/, /sqlsync/projects/, etc. in turn.
See SQLSync for details.
EMPS_Heartbeat Reference
$hb = new EMPS_Heartbeat();
// Add a URL relative to the current website root
$hb->add_url("/do-work/");
// Add an absolute URL (e.g. to trigger a task on another server)
$hb->add_full_url("https://other-site.example/sync/");
// Execute all queued URLs in parallel (default timeout: 20s)
$hb->execute();
// Or with custom timeouts
$hb->execute($timeout = 30, $conn_timeout = 5);
All URLs are fetched in parallel using curl_multi, so ten tasks with a 20-second
timeout will still finish in under 20 seconds total.