[PECL-CVS] [pecl-networking-gearman] master: Merge pull request #54 from php/fix-fork-premature-job-completion
[email protected] (Rasmus Lerdorf via GitHub) Sat, 4 Apr 2026 15:00:21 +0000
| Newsgroups | php.pecl.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: Rasmus Lerdorf (rlerdorf)
Committer: GitHub (web-flow)
Pusher: rlerdorf
Date: 2026-04-04T16:00:18+01:00
Commit: https://github.com/php/pecl-networking-gearman/commit/3929944249d545f2fd65abd8e05fd50e57c47bd3
Raw diff: https://github.com/php/pecl-networking-gearman/commit/3929944249d545f2fd65abd8e05fd50e57c47bd3.diff
Merge pull request #54 from php/fix-fork-premature-job-completion
Fixes #40
Changed paths:
A tests/gearman_worker_integration_test_003.phpt
M php_gearman_client.c
M php_gearman_client.h
M php_gearman_worker.c
M php_gearman_worker.h
Diff:
diff --git a/php_gearman_client.c b/php_gearman_client.c
index 006bb47..5a30ed1 100644
--- a/php_gearman_client.c
+++ b/php_gearman_client.c
@@ -30,6 +30,7 @@ static void gearman_client_ctor(INTERNAL_FUNCTION_PARAMETERS) {
}
client->flags |= GEARMAN_CLIENT_OBJ_CREATED;
+ client->created_pid = (zend_long)getpid();
gearman_client_add_options(&(client->client), GEARMAN_CLIENT_FREE_TASKS);
gearman_client_set_workload_malloc_fn(&(client->client), _php_malloc, NULL);
gearman_client_set_workload_free_fn(&(client->client), _php_free, NULL);
@@ -82,12 +83,13 @@ PHP_METHOD(GearmanClient, __destruct)
}
if (intern->flags & GEARMAN_CLIENT_OBJ_CREATED) {
- context = gearman_client_context(&(intern->client));
- if (context) {
- efree(context);
+ if ((zend_long)getpid() == intern->created_pid) {
+ context = gearman_client_context(&(intern->client));
+ if (context) {
+ efree(context);
+ }
+ gearman_client_free(&intern->client);
}
-
- gearman_client_free(&intern->client);
intern->flags &= ~GEARMAN_CLIENT_OBJ_CREATED;
}
}
diff --git a/php_gearman_client.h b/php_gearman_client.h
index 334282c..c621041 100644
--- a/php_gearman_client.h
+++ b/php_gearman_client.h
@@ -16,6 +16,7 @@
#include "php_ini.h"
#include "ext/standard/info.h"
+
#include "zend_exceptions.h"
#include "zend_interfaces.h"
@@ -51,6 +52,7 @@ typedef struct {
zend_ulong created_tasks;
zval task_list;
+ zend_long created_pid;
zend_object std;
} gearman_client_obj;
diff --git a/php_gearman_worker.c b/php_gearman_worker.c
index 840b938..1842d80 100644
--- a/php_gearman_worker.c
+++ b/php_gearman_worker.c
@@ -31,6 +31,7 @@ static void gearman_worker_ctor(INTERNAL_FUNCTION_PARAMETERS) {
}
worker->flags |= GEARMAN_WORKER_OBJ_CREATED;
+ worker->created_pid = (zend_long)getpid();
gearman_worker_set_workload_malloc_fn(&(worker->worker), _php_malloc, NULL);
gearman_worker_set_workload_free_fn(&(worker->worker), _php_free, NULL);
}
@@ -61,7 +62,14 @@ void gearman_worker_free_obj(zend_object *object) {
gearman_worker_obj *intern = gearman_worker_fetch_object(object);
if (intern->flags & GEARMAN_WORKER_OBJ_CREATED) {
- gearman_worker_free(&(intern->worker));
+ /* In forked children, skip gearman_worker_free() to avoid
+ * sending protocol messages over the parent's connection,
+ * which would cause gearmand to prematurely mark the
+ * parent's in-progress job as complete. The inherited fds
+ * will be closed when the child process exits. See #40. */
+ if ((zend_long)getpid() == intern->created_pid) {
+ gearman_worker_free(&(intern->worker));
+ }
intern->flags &= ~GEARMAN_WORKER_OBJ_CREATED;
}
@@ -80,7 +88,9 @@ PHP_METHOD(GearmanWorker, __destruct) {
}
if (intern->flags & GEARMAN_WORKER_OBJ_CREATED) {
- gearman_worker_free(&(intern->worker));
+ if ((zend_long)getpid() == intern->created_pid) {
+ gearman_worker_free(&(intern->worker));
+ }
intern->flags &= ~GEARMAN_WORKER_OBJ_CREATED;
}
}
diff --git a/php_gearman_worker.h b/php_gearman_worker.h
index 27f94cc..f619001 100644
--- a/php_gearman_worker.h
+++ b/php_gearman_worker.h
@@ -16,6 +16,7 @@
#include "php_ini.h"
#include "ext/standard/info.h"
+
#include "zend_exceptions.h"
#include "zend_interfaces.h"
@@ -48,6 +49,7 @@ typedef struct {
gearman_worker_obj_flags_t flags;
gearman_worker_st worker;
zval cb_list;
+ zend_long created_pid;
zend_object std;
} gearman_worker_obj;
diff --git a/tests/gearman_worker_integration_test_003.phpt b/tests/gearman_worker_integration_test_003.phpt
new file mode 100644
index 0000000..21970aa
--- /dev/null
+++ b/tests/gearman_worker_integration_test_003.phpt
@@ -0,0 +1,65 @@
+--TEST--
+Worker forking children mid-callback does not cause premature job completion (issue #40)
+--SKIPIF--
+<?php
+require_once('skipif.inc');
+require_once('skipifconnect.inc');
+?>
+--FILE--
+<?php
+require_once('connect.inc');
+
+$func = 'issue40_' . getmypid() . '_' . time();
+
+/* Worker in a child so we can be the client in the parent */
+$wpid = pcntl_fork();
+if ($wpid === -1) {
+ die("FAIL: could not fork worker");
+}
+if ($wpid === 0) {
+ $w = new GearmanWorker();
+ if ($w->addServer($host, $port) !== true) exit(2);
+ $w->setTimeout(10000);
+ if ($w->addFunction($func, function($job) {
+ /* Fork two children inside the callback */
+ $children = [];
+ for ($i = 0; $i < 2; $i++) {
+ $pid = pcntl_fork();
+ if ($pid === -1) {
+ continue;
+ } elseif ($pid === 0) {
+ usleep(100000);
+ exit(0);
+ }
+ $children[] = $pid;
+ }
+ foreach ($children as $pid) {
+ pcntl_waitpid($pid, $s, 0);
+ }
+ return "completed";
+ }) !== true) exit(2);
+ $w->work();
+ exit(0);
+}
+
+/* Give the worker time to register */
+usleep(200000);
+
+/* Submit a foreground job and check the result */
+$client = new GearmanClient();
+if ($client->addServer($host, $port) !== true) {
+ die("FAIL: could not add server");
+}
+$client->setTimeout(15000);
+$result = $client->doNormal($func, 'test_payload');
+$rc = $client->returnCode();
+
+pcntl_waitpid($wpid, $ws, 0);
+
+if ($rc === GEARMAN_SUCCESS && $result === "completed") {
+ echo "PASS" . PHP_EOL;
+} else {
+ echo "FAIL: rc=$rc result=" . var_export($result, true) . PHP_EOL;
+}
+--EXPECT--
+PASS