summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sapi/fpm/fpm/fpm_events.c15
-rw-r--r--sapi/fpm/tests/bug77185-reload-robustness.phpt46
2 files changed, 60 insertions, 1 deletions
diff --git a/sapi/fpm/fpm/fpm_events.c b/sapi/fpm/fpm/fpm_events.c
index 1750fab103..81db9f8867 100644
--- a/sapi/fpm/fpm/fpm_events.c
+++ b/sapi/fpm/fpm/fpm_events.c
@@ -34,6 +34,7 @@
#define fpm_event_set_timeout(ev, now) timeradd(&(now), &(ev)->frequency, &(ev)->timeout);
static void fpm_event_cleanup(int which, void *arg);
+static void fpm_postponed_children_bury(struct fpm_event_s *ev, short which, void *arg);
static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg);
static struct fpm_event_s *fpm_event_queue_isset(struct fpm_event_queue_s *queue, struct fpm_event_s *ev);
static int fpm_event_queue_add(struct fpm_event_queue_s **queue, struct fpm_event_s *ev);
@@ -43,6 +44,7 @@ static void fpm_event_queue_destroy(struct fpm_event_queue_s **queue);
static struct fpm_event_module_s *module;
static struct fpm_event_queue_s *fpm_event_queue_timer = NULL;
static struct fpm_event_queue_s *fpm_event_queue_fd = NULL;
+static struct fpm_event_s children_bury_timer;
static void fpm_event_cleanup(int which, void *arg) /* {{{ */
{
@@ -51,6 +53,12 @@ static void fpm_event_cleanup(int which, void *arg) /* {{{ */
}
/* }}} */
+static void fpm_postponed_children_bury(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
+{
+ fpm_children_bury();
+}
+/* }}} */
+
static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
char c;
@@ -72,7 +80,12 @@ static void fpm_got_signal(struct fpm_event_s *ev, short which, void *arg) /* {{
switch (c) {
case 'C' : /* SIGCHLD */
zlog(ZLOG_DEBUG, "received SIGCHLD");
- fpm_children_bury();
+ /* epoll_wait() may report signal fd before read events for a finished child
+ * in the same bunch of events. Prevent immediate free of the child structure
+ * and so the fpm_event_s instance. Otherwise use after free happens during
+ * attemp to process following read event. */
+ fpm_event_set_timer(&children_bury_timer, 0, &fpm_postponed_children_bury, NULL);
+ fpm_event_add(&children_bury_timer, 0);
break;
case 'I' : /* SIGINT */
zlog(ZLOG_DEBUG, "received SIGINT");
diff --git a/sapi/fpm/tests/bug77185-reload-robustness.phpt b/sapi/fpm/tests/bug77185-reload-robustness.phpt
new file mode 100644
index 0000000000..3780176416
--- /dev/null
+++ b/sapi/fpm/tests/bug77185-reload-robustness.phpt
@@ -0,0 +1,46 @@
+--TEST--
+FPM: bug77185 - Reload robustness
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$workers = 10;
+$loops = 10;
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+pid = {{FILE:PID}}
+[unconfined]
+listen = {{ADDR}}
+pm = static
+pm.max_children = $workers
+catch_workers_output = true
+EOT;
+
+$tester = new FPM\Tester($cfg);
+$tester->start();
+$tester->expectLogStartNotices();
+for ($i = 0; $i < $loops; $i++) {
+ $tester->signal('USR2');
+ $tester->expectLogNotice('Reloading in progress ...');
+ $tester->expectLogNotice('reloading: .*');
+ $tester->expectLogNotice('using inherited socket fd=\d+, "127.0.0.1:\d+"');
+ $tester->expectLogStartNotices();
+}
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>