Skip to content

Commit 5b0b777

Browse files
authored
Merge pull request #5 from SoftwarePunt/feat-time-schedule
feat: worker time schedule
2 parents 29ba65c + b6869f1 commit 5b0b777

File tree

6 files changed

+343
-9
lines changed

6 files changed

+343
-9
lines changed

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
**php-resque is a Redis-based library for enqueuing and running background jobs.**
44

5-
This is a lightly maintained fork of **chrisboulton/php-resque**, compatible with PHP 8.2+.
5+
This is a lightly maintained fork of **chrisboulton/php-resque**, with fixes and improvements, compatible with PHP 8.2+.
66

77
⚠️ Not recommended for new projects. We are only maintaining this for legacy projects.
88

@@ -74,14 +74,15 @@ QUEUE=default php vendor/bin/resque
7474

7575
You can set the following environment variables on the worker:
7676

77-
| Name | Description |
78-
|---------------|-------------------------------------------------------------------------------------------------------------------------|
79-
| `QUEUE` | Required. Defines one or more comma-separated queues to process tasks from. Set to `*` to process from any queue. |
80-
| `APP_INCLUDE` | Optional. Defines a bootstrap script to run before starting the worker. |
81-
| `PREFIX` | Optional. Prefix to use in Redis. |
82-
| `COUNT` | Optional. Amount of worker forks to start. If set to > 1, the process will start the workers and then exit immediately. |
83-
| `VERBOSE` | Optional. Forces verbose log output. |
84-
| `VVERBOSE` | Optional. Forces detailed verbose log output. |
77+
| Name | Description |
78+
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
79+
| `QUEUE` | Required. Defines one or more comma-separated queues to process tasks from. Set to `*` to process from any queue. |
80+
| `APP_INCLUDE` | Optional. Defines a bootstrap script to run before starting the worker. |
81+
| `PREFIX` | Optional. Prefix to use in Redis. |
82+
| `COUNT` | Optional. Amount of worker forks to start. If set to > 1, the process will start the workers and then exit immediately. |
83+
| `SCHEDULE` | Optional. An expression with a from/until time, e.g. `22:00-06:00` to only run tasks between 10pm and 6am. The worker is paused outside of the schedule. Relative to default timezone (`date_default_timezone_set`). |
84+
| `VERBOSE` | Optional. Forces verbose log output. |
85+
| `VVERBOSE` | Optional. Forces detailed verbose log output. |
8586

8687
### Events
8788

bin/resque

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ if(!empty($PREFIX)) {
9393
Resque_Redis::prefix($PREFIX);
9494
}
9595

96+
$SCHEDULE = getenv('SCHEDULE');
97+
$scheduleParsed = null;
98+
if (!empty($SCHEDULE)) {
99+
$scheduleParsed = Resque_Time_Schedule::tryParse($SCHEDULE);
100+
if (!$scheduleParsed) {
101+
die('SCHEDULE ('.$SCHEDULE.") expression invalid (parse error).\n");
102+
}
103+
}
104+
96105
if($count > 1) {
97106
for($i = 0; $i < $count; ++$i) {
98107
$pid = Resque::fork();
@@ -117,6 +126,9 @@ else {
117126
$worker = new Resque_Worker($queues);
118127
$worker->setLogger($logger);
119128

129+
if ($scheduleParsed)
130+
$worker->setSchedule($scheduleParsed);
131+
120132
$PIDFILE = getenv('PIDFILE');
121133
if ($PIDFILE) {
122134
file_put_contents($PIDFILE, getmypid()) or

lib/Resque/Time/Expression.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/**
4+
* Resque hour:minute time expression for scheduling.
5+
*
6+
* @package Resque/Time
7+
* @author Roy de Jong <roy@softwarepunt.nl>
8+
* @license http://www.opensource.org/licenses/mit-license.php
9+
*
10+
* @see Resque_Time_Schedule
11+
*/
12+
class Resque_Time_Expression
13+
{
14+
public int $hour;
15+
public int $minute;
16+
17+
public function __construct(int $hour, int $minute = 0)
18+
{
19+
$this->hour = $hour;
20+
$this->minute = $minute;
21+
}
22+
23+
// -----------------------------------------------------------------------------------------------------------------
24+
// Format
25+
26+
public function __toString(): string
27+
{
28+
return self::pad2($this->hour) . ':' . self::pad2($this->minute);
29+
}
30+
31+
public static function pad2(int $number): string
32+
{
33+
$strVal = strval($number);
34+
if (strlen($strVal) === 1)
35+
return "0{$strVal}";
36+
return $strVal;
37+
}
38+
39+
// -----------------------------------------------------------------------------------------------------------------
40+
// Parse
41+
42+
/**
43+
* Tries to parse a time expression, e.g. "12:34" into a Resque_TimeExpression.
44+
*
45+
* @param string $input Time expression with hours and minutes.
46+
* @return Resque_Time_Expression|null Parsed time expression, or NULL if parsing failed.
47+
*/
48+
public static function tryParse(string $input): ?Resque_Time_Expression
49+
{
50+
$parts = explode(':', $input);
51+
52+
if (count($parts) < 2)
53+
return null;
54+
55+
$hours = intval($parts[0]);
56+
$minutes = intval($parts[1]);
57+
58+
if ($hours < 0 || $hours >= 24 || $minutes < 0 || $minutes >= 60)
59+
return null;
60+
61+
return new Resque_Time_Expression($hours, $minutes);
62+
}
63+
}

lib/Resque/Time/Schedule.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/**
4+
* Resque time schedule expression.
5+
*
6+
* @package Resque/Time
7+
* @author Roy de Jong <roy@softwarepunt.nl>
8+
* @license http://www.opensource.org/licenses/mit-license.php
9+
*/
10+
class Resque_Time_Schedule
11+
{
12+
public Resque_Time_Expression $from;
13+
public Resque_Time_Expression $until;
14+
15+
public function __construct(Resque_Time_Expression $from, Resque_Time_Expression $until)
16+
{
17+
$this->from = $from;
18+
$this->until = $until;
19+
}
20+
21+
// -----------------------------------------------------------------------------------------------------------------
22+
// Schedule checking
23+
24+
public function isInSchedule(?DateTime $now = null): bool
25+
{
26+
if (!$now)
27+
$now = new DateTime('now');
28+
29+
$todayFrom = $this->getFromDateTime($now);
30+
31+
if ($todayFrom > $now) {
32+
// Outside of start range, check if we are in yesterday's range
33+
$yesterdayFrom = (clone $todayFrom)->modify('-1 day');
34+
$yesterdayUntil = $this->getUntilDateTime($yesterdayFrom);
35+
36+
return $now >= $yesterdayFrom && $now <= $yesterdayUntil;
37+
}
38+
39+
$todayUntil = $this->getUntilDateTime($todayFrom);
40+
return $now <= $todayUntil;
41+
}
42+
43+
public function getFromDateTime(?DateTime $now = null): DateTime
44+
{
45+
if (!$now)
46+
$now = new DateTime('now');
47+
48+
$dt = clone $now;
49+
$dt->setTime($this->from->hour, $this->from->minute, 0, 0);
50+
51+
return $dt;
52+
}
53+
54+
public function getUntilDateTime(?DateTime $fromDateTime = null): DateTime
55+
{
56+
if (!$fromDateTime)
57+
$fromDateTime = new DateTime('now');
58+
59+
$dt = clone $fromDateTime;
60+
$dt->setTime($this->until->hour, $this->until->minute, 59, 999999);
61+
62+
if ($dt < $fromDateTime)
63+
// Midnight rollover
64+
$dt->modify('+1 day');
65+
66+
return $dt;
67+
}
68+
69+
// -----------------------------------------------------------------------------------------------------------------
70+
// Expression parsing
71+
72+
public static function tryParse(string $input): ?Resque_Time_Schedule
73+
{
74+
$parts = explode('-', $input, 2);
75+
76+
if (count($parts) !== 2)
77+
return null;
78+
79+
$from = Resque_Time_Expression::tryParse(trim($parts[0]));
80+
$until = Resque_Time_Expression::tryParse(trim($parts[1]));
81+
82+
if ($from === null || $until === null)
83+
return null;
84+
85+
return new Resque_Time_Schedule($from, $until);
86+
}
87+
88+
// -----------------------------------------------------------------------------------------------------------------
89+
// Schedule formatting
90+
91+
public function __toString(): string
92+
{
93+
return "{$this->from} - {$this->until}";
94+
}
95+
}

lib/Resque/Worker.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ class Resque_Worker
5151
*/
5252
private $child = null;
5353

54+
/**
55+
* The schedule constraints for this worker.
56+
* If a schedule is set, the worker will not perform any tasks outside of it.
57+
*/
58+
private ?Resque_Time_Schedule $schedule = null;
59+
5460
/**
5561
* Instantiate a new worker, given a list of queues that it should be working
5662
* on. The list of queues should be supplied in the priority that they should
@@ -192,6 +198,28 @@ public function work($interval = Resque::DEFAULT_INTERVAL, $blocking = false)
192198
break;
193199
}
194200

201+
// Check schedule constraints
202+
if ($this->schedule) {
203+
$didScheduleDelay = false;
204+
while (!$this->schedule->isInSchedule()) {
205+
if (!$didScheduleDelay) {
206+
// Announce schedule pause
207+
$this->logger->log(Psr\Log\LogLevel::NOTICE, "Pausing, outside schedule constraint ({$this->schedule})");
208+
$this->updateProcLine("Paused for " . implode(',', $this->queues) . " with schedule constraint {$this->schedule}");
209+
$didScheduleDelay = true;
210+
}
211+
if ($this->shutdown) {
212+
// Interrupted, immediate shutdown
213+
$this->shutdownNow();
214+
return;
215+
}
216+
usleep(10000000); // 10s
217+
}
218+
if ($didScheduleDelay) {
219+
$this->logger->log(Psr\Log\LogLevel::NOTICE, "Resuming, now within schedule constraint ({$this->schedule})");
220+
}
221+
}
222+
195223
// Attempt to find and reserve a job
196224
$job = false;
197225
if(!$this->paused) {
@@ -601,4 +629,9 @@ public function setLogger(Psr\Log\LoggerInterface $logger)
601629
{
602630
$this->logger = $logger;
603631
}
632+
633+
public function setSchedule(?Resque_Time_Schedule $schedule): void
634+
{
635+
$this->schedule = $schedule;
636+
}
604637
}

0 commit comments

Comments
 (0)