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
Bug
introduced
by
![]() |
|||||
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
|
|||||
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
|
|||||
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
This check looks for parameters that have been defined for a function or method, but which are not used in the method body. ![]() |
|||||
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
|
|||||
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
|
|||||
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
|
|||||
116 | } |
||||
117 | |||||
118 | function task_sigint_handler() { |
||||
119 | Debug::log("[TASK] SIG_INT received.\n"); |
||||
120 | task_shutdown(); |
||||
121 | die; |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
144 | print " --interval N - task spawn interval\n"; |
||||
145 | print " default: ".SPAWN_INTERVAL." seconds.\n"; |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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 |