Skip to content

Commit ea75a0b

Browse files
CopilotEdmondDantes
andcommitted
Fix getTrace() to use ZEND_COROUTINE_SUSPENDED macro, return null instead of empty array, and fix tests to use suspend()
Co-authored-by: EdmondDantes <1571649+EdmondDantes@users.noreply.github.com>
1 parent 43eb4e7 commit ea75a0b

File tree

5 files changed

+82
-59
lines changed

5 files changed

+82
-59
lines changed

coroutine.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,13 +1344,17 @@ METHOD(getTrace)
13441344
ZEND_PARSE_PARAMETERS_END();
13451345

13461346
async_coroutine_t *coroutine = THIS_COROUTINE;
1347+
1348+
// Return null if coroutine is not suspended
1349+
if (!ZEND_COROUTINE_SUSPENDED(&coroutine->coroutine)) {
1350+
RETURN_NULL();
1351+
}
1352+
13471353
async_fiber_context_t *fiber_context = coroutine->fiber_context;
13481354

1349-
// Return empty array if coroutine is not suspended or has no fiber context
1350-
if (fiber_context == NULL ||
1351-
(fiber_context->context.status != ZEND_FIBER_STATUS_SUSPENDED || !fiber_context->execute_data)) {
1352-
array_init(return_value);
1353-
return;
1355+
// Additional safety check for fiber context
1356+
if (fiber_context == NULL || !fiber_context->execute_data) {
1357+
RETURN_NULL();
13541358
}
13551359

13561360
// Switch to the coroutine's VM stack to generate the backtrace

coroutine.stub.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ public function getException(): mixed {}
4343
/**
4444
* Returns the Coroutine debug trace.
4545
* If the coroutine is in the suspended state, returns a backtrace array.
46-
* Otherwise, returns an empty array.
46+
* Otherwise, returns null.
4747
*
4848
* @param int $options Options for the backtrace (DEBUG_BACKTRACE_PROVIDE_OBJECT, DEBUG_BACKTRACE_IGNORE_ARGS)
4949
* @param int $limit Maximum number of stack frames to return (0 for no limit)
5050
*/
51-
public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): array {}
51+
public function getTrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): ?array {}
5252

5353
/**
5454
* Return spawn file and line.

coroutine_arginfo.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 35d332163bf92ed3cad35c83017aaa67d650aa9d */
2+
* Stub hash: 2469ab708f75ecce11e559548c2462562fd39c2a */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Async_Coroutine_getId, 0, 0, IS_LONG, 0)
55
ZEND_END_ARG_INFO()
@@ -15,7 +15,7 @@ ZEND_END_ARG_INFO()
1515

1616
#define arginfo_class_Async_Coroutine_getException arginfo_class_Async_Coroutine_getResult
1717

18-
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Async_Coroutine_getTrace, 0, 0, IS_ARRAY, 0)
18+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Async_Coroutine_getTrace, 0, 0, IS_ARRAY, 1)
1919
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "DEBUG_BACKTRACE_PROVIDE_OBJECT")
2020
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, limit, IS_LONG, 0, "0")
2121
ZEND_END_ARG_INFO()
Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
Coroutine: getTrace() - returns empty array for non-suspended coroutine
2+
Coroutine: getTrace() - returns null for non-suspended coroutine
33
--FILE--
44
<?php
55

@@ -10,30 +10,28 @@ $coroutine = spawn(function() {
1010
return "test";
1111
});
1212

13+
// Before the coroutine starts, trace should be null
14+
$traceBeforeStart = $coroutine->getTrace();
15+
echo "Trace before start is null: " . ($traceBeforeStart === null ? "yes" : "no") . "\n";
16+
1317
// Wait for coroutine to complete
1418
await($coroutine);
1519

16-
// After completion, trace should be empty
20+
// After completion, trace should be null
1721
$trace = $coroutine->getTrace();
22+
echo "Trace after completion is null: " . ($trace === null ? "yes" : "no") . "\n";
1823

19-
var_dump(is_array($trace));
20-
var_dump(count($trace));
21-
22-
// Test with options parameter
24+
// Test with options parameter - should still return null
2325
$trace2 = $coroutine->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS);
24-
var_dump(is_array($trace2));
25-
var_dump(count($trace2));
26+
echo "Trace with IGNORE_ARGS is null: " . ($trace2 === null ? "yes" : "no") . "\n";
2627

27-
// Test with limit parameter
28+
// Test with limit parameter - should still return null
2829
$trace3 = $coroutine->getTrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 5);
29-
var_dump(is_array($trace3));
30-
var_dump(count($trace3));
30+
echo "Trace with limit is null: " . ($trace3 === null ? "yes" : "no") . "\n";
3131

3232
?>
3333
--EXPECT--
34-
bool(true)
35-
int(0)
36-
bool(true)
37-
int(0)
38-
bool(true)
39-
int(0)
34+
Trace before start is null: yes
35+
Trace after completion is null: yes
36+
Trace with IGNORE_ARGS is null: yes
37+
Trace with limit is null: yes

tests/coroutine/037-coroutine_getTrace_suspended.phpt

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,62 @@ Coroutine: getTrace() - returns backtrace for suspended coroutine
44
<?php
55

66
use function Async\spawn;
7+
use function Async\suspend;
78
use function Async\await;
89

9-
$coroutine = spawn(function() {
10-
// This function will suspend
11-
Async\sleep(0.1);
12-
return "test";
13-
});
14-
15-
// Get trace while coroutine is suspended
16-
$trace = $coroutine->getTrace();
17-
18-
var_dump(is_array($trace));
19-
echo "Trace has entries: " . (count($trace) > 0 ? "yes" : "no") . "\n";
20-
21-
// Test with DEBUG_BACKTRACE_IGNORE_ARGS
22-
$traceNoArgs = $coroutine->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS);
23-
var_dump(is_array($traceNoArgs));
10+
$parentCoroutine = null;
11+
$childCoroutine = null;
2412

25-
// Test with limit
26-
$traceLimited = $coroutine->getTrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
27-
var_dump(is_array($traceLimited));
28-
29-
// Wait for coroutine to complete
30-
await($coroutine);
31-
32-
// After completion, trace should be empty
33-
$traceAfter = $coroutine->getTrace();
34-
var_dump(is_array($traceAfter));
35-
var_dump(count($traceAfter) === 0);
13+
// Spawn a parent coroutine
14+
$parentCoroutine = spawn(function() use (&$childCoroutine) {
15+
echo "Parent: Starting\n";
16+
17+
// Spawn a child coroutine that will suspend
18+
$childCoroutine = spawn(function() {
19+
echo "Child: Before suspend\n";
20+
suspend();
21+
echo "Child: After suspend\n";
22+
});
23+
24+
// Suspend to let child start and suspend
25+
suspend();
26+
27+
echo "Parent: Back from suspend\n";
28+
29+
// Now check child's trace while it's suspended
30+
$trace = $childCoroutine->getTrace();
31+
32+
if ($trace !== null) {
33+
echo "Child trace is array: " . (is_array($trace) ? "yes" : "no") . "\n";
34+
echo "Child trace has entries: " . (count($trace) > 0 ? "yes" : "no") . "\n";
35+
36+
// Test with DEBUG_BACKTRACE_IGNORE_ARGS
37+
$traceNoArgs = $childCoroutine->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS);
38+
echo "Trace with IGNORE_ARGS is array: " . (is_array($traceNoArgs) ? "yes" : "no") . "\n";
39+
40+
// Test with limit
41+
$traceLimited = $childCoroutine->getTrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2);
42+
echo "Trace with limit is array: " . (is_array($traceLimited) ? "yes" : "no") . "\n";
43+
} else {
44+
echo "Child trace is null\n";
45+
}
46+
47+
// Resume to let child finish
48+
suspend();
49+
50+
// After completion, trace should be null
51+
$traceAfter = $childCoroutine->getTrace();
52+
echo "Child trace after completion is null: " . ($traceAfter === null ? "yes" : "no") . "\n";
53+
});
3654

3755
?>
38-
--EXPECTF--
39-
bool(true)
40-
Trace has entries: %s
41-
bool(true)
42-
bool(true)
43-
bool(true)
44-
bool(true)
56+
--EXPECT--
57+
Parent: Starting
58+
Child: Before suspend
59+
Parent: Back from suspend
60+
Child trace is array: yes
61+
Child trace has entries: yes
62+
Trace with IGNORE_ARGS is array: yes
63+
Trace with limit is array: yes
64+
Child: After suspend
65+
Child trace after completion is null: yes

0 commit comments

Comments
 (0)