Skip to content

Commit 192d920

Browse files
authored
Merge pull request #20 from true-async/19-test-true-async-using-valgrind
19 test true async using valgrind
2 parents 9108cea + 7dde7cf commit 192d920

22 files changed

+308
-180
lines changed

.github/workflows/build.yml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,26 @@ jobs:
6161
libxml2-dev libxslt1-dev libpq-dev libreadline-dev libldap2-dev libsodium-dev \
6262
libargon2-dev \
6363
firebird-dev \
64-
libuv1-dev
64+
valgrind cmake
65+
66+
- name: Install LibUV >= 1.44.0
67+
run: |
68+
# Check if system libuv meets requirements
69+
if pkg-config --exists libuv && pkg-config --atleast-version=1.44.0 libuv; then
70+
echo "System libuv version: $(pkg-config --modversion libuv)"
71+
sudo apt-get install -y libuv1-dev
72+
else
73+
echo "Installing LibUV 1.44.0 from source"
74+
wget https://github.com/libuv/libuv/archive/v1.44.0.tar.gz
75+
tar -xzf v1.44.0.tar.gz
76+
cd libuv-1.44.0
77+
mkdir build && cd build
78+
cmake .. -DCMAKE_BUILD_TYPE=Release
79+
make -j$(nproc)
80+
sudo make install
81+
sudo ldconfig
82+
cd ../..
83+
fi
6584
6685
- name: Configure PHP
6786
working-directory: php-src
@@ -140,11 +159,11 @@ jobs:
140159
-d opcache.jit=tracing \
141160
-d zend_test.observer.enabled=1 \
142161
-d zend_test.observer.show_output=0 \
143-
-P -q -x -j2 \
162+
-P -q -x -j4 \
144163
-g FAIL,BORK,LEAK,XLEAK \
145164
--no-progress \
146165
--offline \
147166
--show-diff \
148-
--show-slow 1000 \
167+
--show-slow 2000 \
149168
--set-timeout 120 \
150-
--repeat 2
169+
--repeat 2

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- Memory management improvements for long-running async applications
2323
- Proper cleanup of coroutine and scope objects during garbage collection cycles
2424

25-
## [0.2.0] - TBD
25+
### Changed
26+
- **LibUV requirement increased to ≥ 1.44.0** - Requires libuv version 1.44.0 or later to ensure proper UV_RUN_ONCE behavior and prevent busy loop issues that could cause high CPU usage
27+
28+
29+
## [0.2.0] - 2025-07-01
2630

2731
### Added
2832
- **Async-aware destructor handling (PHP Core)**: Implemented `async_shutdown_destructors()` function to properly

README.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ tightly integrated at the core level.
3131
PHP TRUE ASYNC is supported for PHP 8.5.0 and later.
3232
`LibUV` is the primary reactor implementation for this extension.
3333

34+
### Requirements
35+
36+
- **PHP 8.5.0+**
37+
- **LibUV ≥ 1.44.0** (required) - Fixes critical `UV_RUN_ONCE` busy loop issue that could cause high CPU usage
38+
39+
### Why LibUV 1.44.0+ is Required
40+
41+
Prior to libuv 1.44, there was a critical issue in `uv__io_poll()`/`uv__run_pending` logic that could cause the event loop to "stick" after the first callback when running in `UV_RUN_ONCE` mode, especially when new ready events appeared within callbacks. This resulted in:
42+
43+
- **High CPU usage** due to busy loops
44+
- **Performance degradation** in async applications
45+
- **Inconsistent event loop behavior** affecting TrueAsync API reliability
46+
47+
The fix in libuv 1.44 ensures that `UV_RUN_ONCE` properly returns after processing all ready callbacks in the current iteration, meeting the "forward progress" specification requirements. This is essential for TrueAsync's performance and reliability.
48+
3449
---
3550

3651
### Unix / macOS
@@ -67,7 +82,31 @@ PHP TRUE ASYNC is supported for PHP 8.5.0 and later.
6782
6883
4. **Install LibUV:**:
6984
70-
Please see the [LibUV installation guide](https://github.com/libuv/libuv)
85+
**IMPORTANT:** LibUV version 1.44.0 or later is required.
86+
87+
For Debian/Ubuntu:
88+
```bash
89+
# Check if system libuv meets requirements (≥1.44.0)
90+
pkg-config --modversion libuv
91+
92+
# If version is too old, install from source:
93+
wget https://github.com/libuv/libuv/archive/v1.44.0.tar.gz
94+
tar -xzf v1.44.0.tar.gz
95+
cd libuv-1.44.0
96+
mkdir build && cd build
97+
cmake .. -DCMAKE_BUILD_TYPE=Release
98+
make -j$(nproc)
99+
sudo make install
100+
sudo ldconfig
101+
```
102+
103+
For macOS:
104+
```bash
105+
# Homebrew usually has recent versions
106+
brew install libuv
107+
```
108+
109+
Please see the [LibUV installation guide](https://github.com/libuv/libuv) for more details.
71110

72111
5. **Configure and build:**
73112

async_API.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,10 @@ void async_waiting_callback_dispose(zend_async_event_callback_t *callback, zend_
299299

300300
await_callback->await_context = NULL;
301301

302-
if (await_context == NULL) {
303-
return;
302+
if (await_context != NULL) {
303+
await_context->dtor(await_context);
304304
}
305305

306-
await_context->dtor(await_context);
307306
await_callback->prev_dispose(callback, event);
308307
}
309308

@@ -322,7 +321,9 @@ void async_waiting_callback(
322321
// remove the callback from the event
323322
// We remove the callback because we treat all events
324323
// as FUTURE-type objects, where the trigger can be activated only once.
324+
ZEND_ASYNC_EVENT_CALLBACK_ADD_REF(callback);
325325
event->del_callback(event, callback);
326+
ZEND_ASYNC_EVENT_CALLBACK_DEC_REF(callback);
326327

327328
if (exception != NULL) {
328329
ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(event);
@@ -356,6 +357,7 @@ void async_waiting_callback(
356357
false
357358
);
358359

360+
callback->dispose(callback, NULL);
359361
return;
360362
}
361363

@@ -366,6 +368,7 @@ void async_waiting_callback(
366368
ZEND_ASYNC_RESUME(await_callback->callback.coroutine);
367369
}
368370

371+
callback->dispose(callback, NULL);
369372
return;
370373
}
371374

@@ -393,6 +396,8 @@ void async_waiting_callback(
393396
if (UNEXPECTED(ITERATOR_IS_FINISHED(await_context))) {
394397
ZEND_ASYNC_RESUME(await_callback->callback.coroutine);
395398
}
399+
400+
callback->dispose(callback, NULL);
396401
}
397402

398403
/**
@@ -415,7 +420,9 @@ void async_waiting_cancellation_callback(
415420
async_await_context_t * await_context = await_callback->await_context;
416421

417422
await_context->resolved_count++;
423+
ZEND_ASYNC_EVENT_CALLBACK_ADD_REF(callback);
418424
event->del_callback(event, callback);
425+
ZEND_ASYNC_EVENT_CALLBACK_DEC_REF(callback);
419426

420427
if (exception != NULL) {
421428
ZEND_ASYNC_EVENT_SET_EXCEPTION_HANDLED(event);
@@ -445,6 +452,8 @@ void async_waiting_cancellation_callback(
445452
if (await_context->total != 0 && await_context->resolved_count >= await_context->total) {
446453
ZEND_ASYNC_RESUME(await_callback->callback.coroutine);
447454
}
455+
456+
callback->dispose(callback, NULL);
448457
}
449458

450459
zend_result await_iterator_handler(async_iterator_t *iterator, zval *current, zval *key)

config.m4

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ if test "$PHP_ASYNC" = "yes"; then
2626
AC_MSG_CHECKING(for libuv)
2727

2828
if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists libuv; then
29-
if $PKG_CONFIG libuv --atleast-version 1.40.0; then
29+
dnl Require libuv >= 1.44.0 for UV_RUN_ONCE busy loop fix
30+
if $PKG_CONFIG libuv --atleast-version 1.44.0; then
3031
LIBUV_INCLINE=`$PKG_CONFIG libuv --cflags`
3132
LIBUV_LIBLINE=`$PKG_CONFIG libuv --libs`
3233
LIBUV_VERSION=`$PKG_CONFIG libuv --modversion`
3334
AC_MSG_RESULT(from pkgconfig: found version $LIBUV_VERSION)
3435
AC_DEFINE(PHP_ASYNC_LIBUV,1,[ ])
3536
else
36-
AC_MSG_ERROR(system libuv must be upgraded to version >= 1.40.0)
37+
AC_MSG_ERROR(system libuv must be upgraded to version >= 1.44.0 (fixes UV_RUN_ONCE busy loop issue))
3738
fi
3839
PHP_EVAL_LIBLINE($LIBUV_LIBLINE, UV_SHARED_LIBADD)
3940
PHP_EVAL_INCLINE($LIBUV_INCLINE)

config.w32

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ if (PHP_ASYNC == "yes") {
3030
if (CHECK_HEADER_ADD_INCLUDE("libuv/uv.h", "CFLAGS_UV", PHP_PHP_BUILD + "\\include")
3131
&& CHECK_LIB("libuv.lib", "libuv")) {
3232

33+
// Note: libuv >= 1.44.0 is required for UV_RUN_ONCE busy loop fix
34+
// For Windows builds, manually verify libuv version meets requirements
35+
3336
PHP_INSTALL_HEADERS("ext/async", "libuv_reactor.h");
3437

3538
ADD_SOURCES("ext/async", "libuv_reactor.c");
@@ -39,6 +42,7 @@ if (PHP_ASYNC == "yes") {
3942
ERROR("Libuv components are not found. The search was performed in the directory: '" + PHP_PHP_BUILD +
4043
"'.\nTo compile PHP TRUE ASYNC with LibUV:\n" +
4144
"1. Copy files from 'libuv\\include' to '" + PHP_PHP_BUILD + "\\include\\libuv\\'\n" +
42-
"2. Build libuv.lib and copy it to '" + PHP_PHP_BUILD + "\\lib\\'");
45+
"2. Build libuv.lib and copy it to '" + PHP_PHP_BUILD + "\\lib\\'\n" +
46+
"3. IMPORTANT: Use libuv >= 1.44.0 (fixes UV_RUN_ONCE busy loop issue)");
4347
}
4448
}

run-tests.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
BASE_PATH="$(cd "$(dirname "$0")/tests" && pwd)"
44
RUN_TESTS_PATH="$(cd "$(dirname "$0")/../../" && pwd)/run-tests.php"
55
PHP_EXECUTABLE="$(cd "$(dirname "$0")/../../" && pwd)/sapi/cli/php"
6+
export VALGRIND_OPTS="--leak-check=full --track-origins=yes"
67

78
if [ -z "$1" ]; then
89
TEST_PATH="$BASE_PATH"
910
else
1011
TEST_PATH="$BASE_PATH/$1"
1112
fi
1213

13-
"$PHP_EXECUTABLE" "$RUN_TESTS_PATH" --show-diff -p "$PHP_EXECUTABLE" "$TEST_PATH"
14+
"$PHP_EXECUTABLE" "$RUN_TESTS_PATH" --show-diff -m -p "$PHP_EXECUTABLE" "$TEST_PATH"

tests/await/006-awaitAny_empty.phpt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ use function Async\awaitAny;
88
echo "start\n";
99

1010
$result = awaitAny([]);
11-
var_dump($result);
11+
12+
$resultCheck = $result === null ? "OK" : "FALSE: " . var_export($result, true);
13+
echo "Result is null: $resultCheck\n";
1214

1315
echo "end\n";
1416
?>
1517
--EXPECT--
1618
start
17-
NULL
19+
Result is null: OK
1820
end

tests/await/007-awaitAny_exception.phpt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ awaitAny() - coroutine throws exception
66
use function Async\spawn;
77
use function Async\awaitAny;
88
use function Async\delay;
9+
use function Async\suspend;
910

1011
echo "start\n";
1112

@@ -15,7 +16,7 @@ $coroutines = [
1516
return "first";
1617
}),
1718
spawn(function() {
18-
delay(20);
19+
suspend();
1920
throw new RuntimeException("test exception");
2021
}),
2122
];

tests/await/008-awaitFirstSuccess_basic.phpt

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,36 +25,10 @@ $coroutines = [
2525
];
2626

2727
$result = awaitFirstSuccess($coroutines);
28-
var_dump($result);
29-
28+
echo "Result: {$result[0]}\n";
3029
echo "end\n";
3130
?>
3231
--EXPECTF--
3332
start
34-
array(2) {
35-
[0]=>
36-
string(7) "success"
37-
[1]=>
38-
array(1) {
39-
[0]=>
40-
object(RuntimeException)#%d (7) {
41-
["message":protected]=>
42-
string(11) "first error"
43-
["string":"Exception":private]=>
44-
string(0) ""
45-
["code":protected]=>
46-
int(0)
47-
["file":protected]=>
48-
string(%d) "%s"
49-
["line":protected]=>
50-
int(%d)
51-
["trace":"Exception":private]=>
52-
array(%d) {
53-
%a
54-
}
55-
["previous":"Exception":private]=>
56-
NULL
57-
}
58-
}
59-
}
33+
Result: success
6034
end

0 commit comments

Comments
 (0)