[PECL-CVS] [pecl-networking-gearman] fix-worker-exit-job-retry: fixes #26
[email protected] (Rasmus Lerdorf) Sat, 4 Apr 2026 13:44:28 +0000
| Newsgroups | php.pecl.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: Rasmus Lerdorf (rlerdorf)
Date: 2026-04-04T09:44:13-04:00
Commit: https://github.com/php/pecl-networking-gearman/commit/e80a98fab1ced65ebf0b8d30cb464afa3a47dc24
Raw diff: https://github.com/php/pecl-networking-gearman/commit/e80a98fab1ced65ebf0b8d30cb464afa3a47dc24.diff
fixes #26
Changed paths:
A tests/gearman_worker_integration_test_002.phpt
M php_gearman_worker.c
Diff:
diff --git a/php_gearman_worker.c b/php_gearman_worker.c
index ed75022..840b938 100644
--- a/php_gearman_worker.c
+++ b/php_gearman_worker.c
@@ -524,6 +524,25 @@ static void *_php_worker_function_callback(gearman_job_st *job,
*ret_ptr = jobj->ret;
+#if PHP_VERSION_ID >= 80000
+ /* If exit()/die() was called inside the callback, PHP sets an
+ * UnwindExit exception but returns control here. Without this
+ * check, the code below would send WORK_EXCEPTION to gearmand,
+ * marking the job as failed instead of returning it to the queue.
+ * Force a bailout so the process exits without sending any gearman
+ * protocol response — gearmand will detect the disconnect and
+ * return the job to the queue for retry. See issue #26. */
+ if (EG(exception) && zend_is_unwind_exit(EG(exception))) {
+ if (!Z_ISUNDEF(argv[0])) {
+ zval_ptr_dtor_nogc(&argv[0]);
+ }
+ if (!Z_ISUNDEF(argv[1])) {
+ zval_ptr_dtor_nogc(&argv[1]);
+ }
+ zend_bailout();
+ }
+#endif
+
if (EG(exception)) {
*ret_ptr = GEARMAN_WORK_EXCEPTION;
diff --git a/tests/gearman_worker_integration_test_002.phpt b/tests/gearman_worker_integration_test_002.phpt
new file mode 100644
index 0000000..9cae246
--- /dev/null
+++ b/tests/gearman_worker_integration_test_002.phpt
@@ -0,0 +1,60 @@
+--TEST--
+Worker exit() mid-callback returns job to queue (issue #26)
+--SKIPIF--
+<?php
+require_once('skipif.inc');
+require_once('skipifconnect.inc');
+?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+$func = 'issue26_' . getmypid() . '_' . time();
+
+/* Submit a background job */
+$client = new GearmanClient();
+$client->addServer($host, $port);
+$handle = $client->doBackground($func, 'test_payload');
+if ($client->returnCode() !== GEARMAN_SUCCESS) {
+ die("Could not submit job");
+}
+
+/* Worker 1: grabs the job and exit()s without completing it */
+$pid1 = pcntl_fork();
+if ($pid1 === 0) {
+ $w = new GearmanWorker();
+ $w->addServer($host, $port);
+ $w->addFunction($func, function($job) {
+ exit(1);
+ });
+ $w->work();
+ exit(0);
+}
+pcntl_waitpid($pid1, $status1, 0);
+
+/* Brief pause for gearmand to detect the disconnect */
+usleep(500000);
+
+/* Worker 2: should receive the retried job */
+$pid2 = pcntl_fork();
+if ($pid2 === 0) {
+ $w = new GearmanWorker();
+ $w->addServer($host, $port);
+ $w->setTimeout(10000);
+ $w->addFunction($func, function($job) {
+ echo "payload: " . $job->workload() . PHP_EOL;
+ return "done";
+ });
+ $ret = $w->work();
+ exit($ret ? 0 : 2);
+}
+pcntl_waitpid($pid2, $status2, 0);
+
+if (pcntl_wexitstatus($status2) === 0) {
+ echo "PASS" . PHP_EOL;
+} else {
+ echo "FAIL: job was not retried" . PHP_EOL;
+}
+--EXPECT--
+payload: test_payload
+PASS