Issues (1270)

update_daemon2.php (11 issues)

1
#!/usr/bin/env php
2
<?php
3
set_include_path(dirname(__FILE__).DIRECTORY_SEPARATOR."include".PATH_SEPARATOR.
4
    get_include_path());
5
6
declare(ticks=1);
7
chdir(dirname(__FILE__));
8
9
define('DISABLE_SESSIONS', true);
10
11
require_once "autoload.php";
12
require_once "functions.php";
13
require_once "config.php";
14
15
// defaults
16
define_default('PURGE_INTERVAL', 3600); // seconds
17
define_default('MAX_CHILD_RUNTIME', 1800); // seconds
18
define_default('MAX_JOBS', 2);
19
define_default('SPAWN_INTERVAL', DAEMON_SLEEP_INTERVAL); // seconds
0 ignored issues
show
The constant DAEMON_SLEEP_INTERVAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
20
21
require_once "sanity_check.php";
22
require_once "db.php";
23
require_once "db-prefs.php";
24
25
if (!function_exists('pcntl_fork')) {
26
    die("error: This script requires PHP compiled with PCNTL module.\n");
27
}
28
29
$options = getopt("");
30
31
if (!is_array($options)) {
0 ignored issues
show
The condition is_array($options) is always true.
Loading history...
32
    die("error: getopt() failed. ".
33
        "Most probably you are using PHP CGI to run this script ".
34
        "instead of required PHP CLI. Check tt-rss wiki page on updating feeds for ".
35
        "additional information.\n");
36
}
37
38
39
$master_handlers_installed = false;
40
41
$children = array();
42
$ctimes = array();
43
44
$last_checkpoint = -1;
45
46
function reap_children() {
47
    global $children;
48
    global $ctimes;
49
50
    $tmp = array();
51
52
    foreach ($children as $pid) {
53
        if (pcntl_waitpid($pid, $status, WNOHANG) != $pid) {
54
55
            if (file_is_locked("update_daemon-$pid.lock")) {
56
                array_push($tmp, $pid);
57
            } else {
58
                Debug::log("[reap_children] child $pid seems active but lockfile is unlocked.");
59
                unset($ctimes[$pid]);
60
61
            }
62
        } else {
63
            Debug::log("[reap_children] child $pid reaped.");
64
            unset($ctimes[$pid]);
65
        }
66
    }
67
68
    $children = $tmp;
69
70
    return count($tmp);
71
}
72
73
function check_ctimes() {
74
    global $ctimes;
75
76
    foreach (array_keys($ctimes) as $pid) {
77
        $started = $ctimes[$pid];
78
79
        if (time() - $started > MAX_CHILD_RUNTIME) {
0 ignored issues
show
The constant MAX_CHILD_RUNTIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
80
            Debug::log("[MASTER] child process $pid seems to be stuck, aborting...");
81
            posix_kill($pid, SIGKILL);
82
        }
83
    }
84
}
85
86
function sigchld_handler($signal) {
0 ignored issues
show
The parameter $signal is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

86
function sigchld_handler(/** @scrutinizer ignore-unused */ $signal) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
87
    $running_jobs = reap_children();
88
89
    Debug::log("[SIGCHLD] jobs left: $running_jobs");
90
91
    pcntl_waitpid(-1, $status, WNOHANG);
92
}
93
94
function shutdown($caller_pid) {
95
    if ($caller_pid == posix_getpid()) {
96
        if (file_exists(LOCK_DIRECTORY."/update_daemon.lock")) {
0 ignored issues
show
The constant LOCK_DIRECTORY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
97
            Debug::log("removing lockfile (master)...");
98
            unlink(LOCK_DIRECTORY."/update_daemon.lock");
99
        }
100
    }
101
}
102
103
function task_shutdown() {
104
    $pid = posix_getpid();
105
106
    if (file_exists(LOCK_DIRECTORY."/update_daemon-$pid.lock")) {
0 ignored issues
show
The constant LOCK_DIRECTORY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
107
        Debug::log("removing lockfile ($pid)...");
108
        unlink(LOCK_DIRECTORY."/update_daemon-$pid.lock");
109
    }
110
}
111
112
function sigint_handler() {
113
    Debug::log("[MASTER] SIG_INT received.\n");
114
    shutdown(posix_getpid());
115
    die;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
116
}
117
118
function task_sigint_handler() {
119
    Debug::log("[TASK] SIG_INT received.\n");
120
    task_shutdown();
121
    die;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
122
}
123
124
pcntl_signal(SIGCHLD, 'sigchld_handler');
125
126
$longopts = array(
127
    "log:",
128
    "log-level:",
129
    "tasks:",
130
    "interval:",
131
    "quiet",
132
    "help"
133
);
134
135
$options = getopt("", $longopts);
136
137
if (isset($options["help"])) {
138
    print "Tiny Tiny RSS update daemon.\n\n";
139
    print "Options:\n";
140
    print "  --log FILE           - log messages to FILE\n";
141
    print "  --log-level N        - log verbosity level\n";
142
    print "  --tasks N            - amount of update tasks to spawn\n";
143
    print "                         default: ".MAX_JOBS."\n";
0 ignored issues
show
The constant MAX_JOBS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
144
    print "  --interval N         - task spawn interval\n";
145
    print "                         default: ".SPAWN_INTERVAL." seconds.\n";
0 ignored issues
show
The constant SPAWN_INTERVAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
146
    print "  --quiet              - don't output messages to stdout\n";
147
    return;
148
}
149
150
Debug::set_enabled(true);
151
152
if (isset($options["log-level"])) {
153
    Debug::set_loglevel((int) $options["log-level"]);
154
}
155
156
if (isset($options["log"])) {
157
    Debug::set_quiet(isset($options['quiet']));
158
    Debug::set_logfile($options["log"]);
159
    Debug::log("Logging to ".$options["log"]);
160
} else {
161
    if (isset($options['quiet'])) {
162
        Debug::set_loglevel(Debug::$LOG_DISABLED);
163
    }
164
}
165
166
if (isset($options["tasks"])) {
167
    Debug::log("Set to spawn ".$options["tasks"]." children.");
168
    $max_jobs = $options["tasks"];
169
} else {
170
    $max_jobs = MAX_JOBS;
171
}
172
173
if (isset($options["interval"])) {
174
    Debug::log("Spawn interval: ".$options["interval"]." seconds.");
175
    $spawn_interval = $options["interval"];
176
} else {
177
    $spawn_interval = SPAWN_INTERVAL;
178
}
179
180
// let's enforce a minimum spawn interval as to not forkbomb the host
181
$spawn_interval = max(60, $spawn_interval);
182
Debug::log("Spawn interval: $spawn_interval sec");
183
184
if (file_is_locked("update_daemon.lock")) {
185
    die("error: Can't create lockfile. ".
186
        "Maybe another daemon is already running.\n");
187
}
188
189
// Try to lock a file in order to avoid concurrent update.
190
$lock_handle = make_lockfile("update_daemon.lock");
191
192
if (!$lock_handle) {
0 ignored issues
show
The condition $lock_handle is always false.
Loading history...
193
    die("error: Can't create lockfile. ".
194
        "Maybe another daemon is already running.\n");
195
}
196
197
$schema_version = get_schema_version();
198
199
if ($schema_version != SCHEMA_VERSION) {
200
    die("Schema version is wrong, please upgrade the database.\n");
201
}
202
203
// Protip: children close shared database handle when terminating, it's a bad idea to
204
// do database stuff on main process from now on.
205
206
while (true) {
207
208
    // Since sleep is interupted by SIGCHLD, we need another way to
209
    // respect the spawn interval
210
    $next_spawn = $last_checkpoint + $spawn_interval - time();
211
212
    if ($next_spawn % 60 == 0) {
213
        $running_jobs = count($children);
214
        Debug::log("[MASTER] active jobs: $running_jobs, next spawn at $next_spawn sec.");
215
    }
216
217
    if ($last_checkpoint + $spawn_interval < time()) {
218
        check_ctimes();
219
        reap_children();
220
221
        for ($j = count($children); $j < $max_jobs; $j++) {
222
            $pid = pcntl_fork();
223
            if ($pid == -1) {
224
                die("fork failed!\n");
225
            } else if ($pid) {
226
227
                if (!$master_handlers_installed) {
228
                    Debug::log("[MASTER] installing shutdown handlers");
229
                    pcntl_signal(SIGINT, 'sigint_handler');
230
                    pcntl_signal(SIGTERM, 'sigint_handler');
231
                    register_shutdown_function('shutdown', posix_getpid());
232
                    $master_handlers_installed = true;
233
                }
234
235
                Debug::log("[MASTER] spawned client $j [PID:$pid]...");
236
                array_push($children, $pid);
237
                $ctimes[$pid] = time();
238
            } else {
239
                pcntl_signal(SIGCHLD, SIG_IGN);
240
                pcntl_signal(SIGINT, 'task_sigint_handler');
241
242
                register_shutdown_function('task_shutdown');
243
244
                $quiet = (isset($options["quiet"])) ? "--quiet" : "";
245
                $log = function_exists("flock") && isset($options['log']) ? '--log '.$options['log'] : '';
246
247
                $my_pid = posix_getpid();
248
249
                passthru(PHP_EXECUTABLE." update.php --daemon-loop $quiet $log --task $j --pidlock $my_pid");
250
251
                sleep(1);
252
253
                // We exit in order to avoid fork bombing.
254
                exit(0);
255
            }
256
        }
257
        $last_checkpoint = time();
258
    }
259
    sleep(1);
260
}
261