Skip to content

Commit 093623a

Browse files
committed
Add non-blocking flock() and zend_async_task_new() factory
flock() now offloads to libuv thread pool inside coroutines, preventing event loop blocking. Added libuv_new_task() factory registered through reactor API, replacing manual task initialization.
1 parent dce8704 commit 093623a

3 files changed

Lines changed: 88 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to the Async extension for PHP will be documented in this fi
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.6.2] - 2026-03-24
9+
10+
### Added
11+
- **Non-blocking `flock()`**: `flock()` no longer blocks the event loop. The lock operation is offloaded to the libuv thread pool via `zend_async_task_t`, allowing other coroutines to continue executing while waiting for a file lock.
12+
- **`zend_async_task_new()` API**: New factory function for creating thread pool tasks, registered through the reactor like timer and IO events. Replaces manual `pecalloc` + field initialization.
13+
814
## [0.6.1] - 2026-03-15
915

1016
### Fixed

libuv_reactor.c

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,35 @@ static bool libuv_task_dispose(zend_async_event_t *event)
19551955

19561956
/* }}} */
19571957

1958+
/* {{{ libuv_new_task */
1959+
static zend_async_task_t *libuv_new_task(zend_async_task_run_t run, void *data, size_t extra_size)
1960+
{
1961+
if (UNEXPECTED(run == NULL)) {
1962+
async_throw_error("Cannot create a task without a run function");
1963+
return NULL;
1964+
}
1965+
1966+
const size_t total_size = sizeof(zend_async_task_t) + extra_size;
1967+
zend_async_task_t *task = pecalloc(1, total_size, 0);
1968+
1969+
zend_async_event_t *event = &task->base;
1970+
event->ref_count = 1;
1971+
event->extra_offset = extra_size > 0 ? sizeof(zend_async_task_t) : 0;
1972+
event->add_callback = libuv_add_callback;
1973+
event->del_callback = libuv_remove_callback;
1974+
event->start = libuv_task_start;
1975+
event->stop = libuv_task_stop;
1976+
event->dispose = libuv_task_dispose;
1977+
event->info = libuv_task_info;
1978+
1979+
task->run = run;
1980+
task->data = data;
1981+
1982+
return task;
1983+
}
1984+
1985+
/* }}} */
1986+
19581987
/* {{{ libuv_queue_task */
19591988
static bool libuv_queue_task(zend_async_task_t *task)
19601989
{
@@ -1975,16 +2004,6 @@ static bool libuv_queue_task(zend_async_task_t *task)
19752004
}
19762005
}
19772006

1978-
/* Initialize the event methods on the task */
1979-
zend_async_event_t *event = &task->base;
1980-
event->ref_count = 1;
1981-
event->add_callback = libuv_add_callback;
1982-
event->del_callback = libuv_remove_callback;
1983-
event->start = libuv_task_start;
1984-
event->stop = libuv_task_stop;
1985-
event->dispose = libuv_task_dispose;
1986-
event->info = libuv_task_info;
1987-
19882007
/* Allocate the libuv work wrapper */
19892008
libuv_work_wrapper_t *wrapper = pecalloc(1, sizeof(libuv_work_wrapper_t), 0);
19902009
wrapper->task = task;
@@ -4530,5 +4549,5 @@ void async_libuv_reactor_register(void)
45304549
libuv_io_set_option,
45314550
libuv_udp_set_membership);
45324551

4533-
zend_async_thread_pool_register(LIBUV_REACTOR_NAME, false, libuv_queue_task);
4552+
zend_async_thread_pool_register(LIBUV_REACTOR_NAME, false, libuv_new_task, libuv_queue_task);
45344553
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
flock() does not block the event loop in coroutines
3+
--FILE--
4+
<?php
5+
6+
use function Async\spawn;
7+
use function Async\await;
8+
9+
echo "Start\n";
10+
11+
$tmpfile = tempnam(sys_get_temp_dir(), 'async_flock_test_');
12+
file_put_contents($tmpfile, "test");
13+
14+
// Take the lock in the main context before spawning coroutines
15+
$main_fp = fopen($tmpfile, 'r');
16+
flock($main_fp, LOCK_EX);
17+
echo "main: locked\n";
18+
19+
$locker = spawn(function() use ($tmpfile) {
20+
echo "locker: waiting for lock\n";
21+
$fp = fopen($tmpfile, 'r');
22+
flock($fp, LOCK_EX);
23+
echo "locker: acquired\n";
24+
flock($fp, LOCK_UN);
25+
fclose($fp);
26+
});
27+
28+
$worker = spawn(function() {
29+
echo "worker: running\n";
30+
});
31+
32+
// Let coroutines run — worker should complete, locker should be blocked in thread pool
33+
Async\delay(50);
34+
35+
echo "main: unlocking\n";
36+
flock($main_fp, LOCK_UN);
37+
fclose($main_fp);
38+
39+
await($locker);
40+
41+
unlink($tmpfile);
42+
echo "End\n";
43+
44+
?>
45+
--EXPECT--
46+
Start
47+
main: locked
48+
locker: waiting for lock
49+
worker: running
50+
main: unlocking
51+
locker: acquired
52+
End

0 commit comments

Comments
 (0)