Skip to content

Commit b05b35d

Browse files
committed
+ async_spawn_and_throw
1 parent 70d68f3 commit b05b35d

File tree

7 files changed

+125
-77
lines changed

7 files changed

+125
-77
lines changed

async_API.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@ void async_api_register(void)
885885
async_scheduler_coroutine_enqueue,
886886
async_coroutine_resume,
887887
async_coroutine_cancel,
888+
async_spawn_and_throw,
888889
graceful_shutdown,
889890
get_coroutines,
890891
add_microtask,

coroutine.c

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -379,26 +379,28 @@ static zend_result finally_handlers_iterator_handler(async_iterator_t *iterator,
379379
return SUCCESS;
380380
}
381381

382-
static void finally_handlers_iterator_dtor(zend_async_microtask_t *microtask)
382+
static void finally_handlers_iterator_dtor(zend_async_iterator_t *zend_iterator)
383383
{
384-
async_iterator_t * iterator = (async_iterator_t *) microtask;
384+
async_iterator_t * iterator = (async_iterator_t *) zend_iterator;
385385

386-
if (iterator->extended_data != NULL) {
387-
coroutine_finally_handlers_context_t *context = (coroutine_finally_handlers_context_t *) iterator->extended_data;
388-
389-
// Throw CompositeException if any exceptions were collected
390-
if (context->composite_exception != NULL) {
391-
zend_throw_exception_internal(context->composite_exception);
392-
context->composite_exception = NULL;
393-
}
394-
395-
// Release coroutine reference
396-
OBJ_RELEASE(&context->coroutine->std);
397-
398-
// Free the context
399-
efree(context);
400-
iterator->extended_data = NULL;
386+
if (UNEXPECTED(iterator->extended_data == NULL)) {
387+
return;
401388
}
389+
390+
coroutine_finally_handlers_context_t *context = iterator->extended_data;
391+
392+
// Throw CompositeException if any exceptions were collected
393+
if (context->composite_exception != NULL) {
394+
zend_throw_exception_internal(context->composite_exception);
395+
context->composite_exception = NULL;
396+
}
397+
398+
// Release coroutine reference
399+
OBJ_RELEASE(&context->coroutine->std);
400+
401+
// Free the context
402+
efree(context);
403+
iterator->extended_data = NULL;
402404
}
403405

404406
static void async_coroutine_call_finally_handlers(async_coroutine_t *coroutine)
@@ -407,6 +409,12 @@ static void async_coroutine_call_finally_handlers(async_coroutine_t *coroutine)
407409
return;
408410
}
409411

412+
// Create a special child scope for finally handlers
413+
zend_async_scope_t *child_scope = ZEND_ASYNC_NEW_SCOPE(coroutine->coroutine.scope);
414+
if (UNEXPECTED(child_scope == NULL)) {
415+
return;
416+
}
417+
410418
zval handlers;
411419
ZVAL_ARR(&handlers, coroutine->finally_handlers);
412420
coroutine->finally_handlers = NULL;
@@ -416,7 +424,7 @@ static void async_coroutine_call_finally_handlers(async_coroutine_t *coroutine)
416424
NULL,
417425
NULL,
418426
finally_handlers_iterator_handler,
419-
coroutine->coroutine.scope,
427+
child_scope,
420428
0,
421429
0,
422430
0

exceptions.c

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "php.h"
2121

2222
#include "exceptions_arginfo.h"
23+
#include "zend_common.h"
2324

2425
zend_class_entry * async_ce_async_exception = NULL;
2526
zend_class_entry * async_ce_cancellation_exception = NULL;
@@ -197,4 +198,80 @@ ZEND_API ZEND_COLD void async_composite_exception_add_exception(zend_object *com
197198
} else if (transfer) {
198199
OBJ_RELEASE(exception);
199200
}
201+
}
202+
203+
static void exception_coroutine_finally_callback(
204+
zend_async_event_t *event,
205+
zend_async_event_callback_t *callback,
206+
void * result,
207+
zend_object *exception
208+
)
209+
{
210+
zend_coroutine_t *coroutine = (zend_coroutine_t *) event;
211+
212+
if (coroutine->extended_data != NULL) {
213+
zend_object *exception_obj = coroutine->extended_data;
214+
coroutine->extended_data = NULL;
215+
zend_throw_exception_internal(exception_obj);
216+
}
217+
}
218+
219+
static void exception_coroutine_entry(void)
220+
{
221+
zend_coroutine_t *coroutine = ZEND_ASYNC_CURRENT_COROUTINE;
222+
223+
if (UNEXPECTED(coroutine == NULL || coroutine->extended_data == NULL)) {
224+
return;
225+
}
226+
227+
zend_object *exception = coroutine->extended_data;
228+
coroutine->extended_data = NULL;
229+
230+
zend_throw_exception_internal(exception);
231+
}
232+
233+
bool async_spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority)
234+
{
235+
bool need_new_scope = false;
236+
237+
if (scope == NULL) {
238+
scope = ZEND_ASYNC_CURRENT_SCOPE;
239+
}
240+
241+
if (ZEND_ASYNC_SCOPE_IS_CANCELLED(scope) || ZEND_ASYNC_SCOPE_IS_CLOSED(scope)) {
242+
scope = scope->parent_scope;
243+
need_new_scope = true;
244+
}
245+
246+
if (scope == NULL || ZEND_ASYNC_SCOPE_IS_CLOSED(scope)) {
247+
async_warning("To throw an exception in a coroutine, "
248+
"the Scope must be active or have a parent that is not closed.");
249+
return false;
250+
}
251+
252+
if (need_new_scope) {
253+
scope = ZEND_ASYNC_NEW_SCOPE(scope);
254+
if (UNEXPECTED(scope == NULL)) {
255+
async_warning("Failed to create a new Scope for throwing an exception in a coroutine.");
256+
return false;
257+
}
258+
}
259+
260+
zend_coroutine_t *coroutine = ZEND_ASYNC_SPAWN_WITH_SCOPE_EX(scope, priority);
261+
if (UNEXPECTED(coroutine == NULL)) {
262+
async_warning("Failed to spawn a coroutine for throwing an exception.");
263+
return false;
264+
}
265+
266+
coroutine->event.add_callback(&coroutine->event, ZEND_ASYNC_EVENT_CALLBACK(exception_coroutine_finally_callback));
267+
if (UNEXPECTED(EG(exception))) {
268+
coroutine->event.dispose(&coroutine->event);
269+
return false;
270+
}
271+
272+
coroutine->internal_entry = exception_coroutine_entry;
273+
coroutine->extended_data = exception;
274+
GC_ADDREF(exception);
275+
276+
return true;
200277
}

exceptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <zend_portability.h>
2020
#include <zend_property_hooks.h>
21+
#include <Zend/zend_async_API.h>
2122

2223
BEGIN_EXTERN_C()
2324

@@ -38,6 +39,7 @@ ZEND_API ZEND_COLD zend_object * async_throw_timeout(const char *format, const z
3839
ZEND_API ZEND_COLD zend_object * async_throw_poll(const char *format, ...);
3940
ZEND_API ZEND_COLD zend_object * async_new_composite_exception(void);
4041
ZEND_API void async_composite_exception_add_exception(zend_object *composite, zend_object *exception, bool transfer);
42+
bool async_spawn_and_throw(zend_object *exception, zend_async_scope_t *scope, int32_t priority);
4143

4244
END_EXTERN_C()
4345

iterator.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ void iterator_dtor(zend_async_microtask_t *microtask)
7070
// Call the extended destructor if it exists
7171
ASYNC_ITERATOR_DTOR extended_dtor = iterator->extended_dtor;
7272
iterator->extended_dtor = NULL;
73-
extended_dtor(microtask);
73+
extended_dtor((zend_async_iterator_t *)iterator);
7474
}
7575

7676
// Free hash iterator if it was allocated

iterator.h

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,17 @@ async_iterator_t * async_iterator_new(
4242
size_t iterator_size
4343
);
4444

45-
#define ASYNC_ITERATOR_DTOR zend_async_microtask_handler_t
45+
#define ASYNC_ITERATOR_DTOR zend_async_iterator_method_t
4646

4747
void async_iterator_run(async_iterator_t *iterator);
4848
void async_iterator_run_in_coroutine(async_iterator_t *iterator, int32_t priority);
4949

5050
struct _async_iterator_t {
51-
zend_async_microtask_t microtask;
52-
void (*run)(zend_async_iterator_t *iterator);
53-
void (*run_in_coroutine)(zend_async_iterator_t *iterator, int32_t priority);
51+
ZEND_ASYNC_ITERATOR_FIELDS
5452
/* The current state of the iterator. See async_iterator_state_t */
5553
async_iterator_state_t state;
56-
/* The maximum number of concurrent tasks that can be executed at the same time */
57-
unsigned int concurrency;
5854
/* The number of active coroutines that are currently executing */
5955
unsigned int active_coroutines;
60-
/* Priority for coroutines created by this iterator */
61-
int32_t priority;
62-
/* The coroutine scope */
63-
zend_async_scope_t *scope;
6456
/* The internal handler */
6557
async_iterator_handler_t handler;
6658
/* Callback and info / cache to be used when coroutine is started. */
@@ -72,10 +64,6 @@ struct _async_iterator_t {
7264
uint32_t hash_iterator;
7365
/* The iterator object, which may be NULL if there is no iterator. */
7466
zend_object_iterator *zend_iterator;
75-
/* NULLABLE. Custom data for the iterator, can be used to store additional information. */
76-
void *extended_data;
77-
/* NULLABLE. An additional destructor that will be called. */
78-
ASYNC_ITERATOR_DTOR extended_dtor;
7967
};
8068

8169
#endif //ITERATOR_H

scope.c

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,24 +1402,26 @@ static zend_result scope_finally_handlers_iterator_handler(async_iterator_t *ite
14021402
return SUCCESS;
14031403
}
14041404

1405-
static void scope_finally_handlers_iterator_dtor(zend_async_microtask_t *microtask)
1405+
static void scope_finally_handlers_iterator_dtor(zend_async_iterator_t *z_iterator)
14061406
{
1407-
async_iterator_t *iterator = (async_iterator_t *) microtask;
1407+
async_iterator_t *iterator = (async_iterator_t *) z_iterator;
14081408

1409-
if (iterator->extended_data != NULL) {
1410-
scope_finally_handlers_context_t *context = (scope_finally_handlers_context_t *) iterator->extended_data;
1411-
1412-
// Throw CompositeException if any exceptions were collected
1413-
if (context->composite_exception != NULL) {
1414-
zend_throw_exception_internal(context->composite_exception);
1415-
context->composite_exception = NULL;
1416-
}
1417-
1418-
// Free the context
1419-
efree(context);
1420-
iterator->extended_data = NULL;
1409+
if (UNEXPECTED(iterator->extended_data == NULL)) {
1410+
return;
1411+
}
1412+
1413+
scope_finally_handlers_context_t *context = iterator->extended_data;
1414+
1415+
// Throw CompositeException if any exceptions were collected
1416+
if (context->composite_exception != NULL) {
1417+
zend_throw_exception_internal(context->composite_exception);
1418+
context->composite_exception = NULL;
14211419
}
14221420

1421+
// Free the context
1422+
efree(context);
1423+
iterator->extended_data = NULL;
1424+
14231425
//
14241426
// If everything is correct,
14251427
// the Scope will destroy itself as soon as the coroutine created within it completes execution.
@@ -1433,36 +1435,6 @@ static void async_scope_call_finally_handlers(async_scope_t *scope)
14331435
return;
14341436
}
14351437

1436-
if (false == ZEND_ASYNC_IS_ACTIVE) {
1437-
// Run handlers immediately if not in async context
1438-
zval *callable;
1439-
zval result, param;
1440-
ZVAL_UNDEF(&result);
1441-
if (scope->scope.scope_object) {
1442-
ZVAL_OBJ(&param, scope->scope.scope_object);
1443-
} else {
1444-
ZVAL_NULL(&param);
1445-
}
1446-
1447-
ZEND_HASH_FOREACH_VAL(scope->finally_handlers, callable) {
1448-
1449-
ZVAL_UNDEF(&result);
1450-
1451-
if (UNEXPECTED(call_user_function(NULL, NULL, callable, &result, 1, &param) == FAILURE)) {
1452-
zend_throw_error(NULL, "Failed to call finally handler in finished scope");
1453-
zval_ptr_dtor(&result);
1454-
return;
1455-
}
1456-
1457-
zval_ptr_dtor(&result);
1458-
} ZEND_HASH_FOREACH_END();
1459-
1460-
zend_array_destroy(scope->finally_handlers);
1461-
scope->finally_handlers = NULL;
1462-
1463-
return;
1464-
}
1465-
14661438
// Create special child scope for finally handlers
14671439
//
14681440
// We don't increase the reference count of the Scope

0 commit comments

Comments
 (0)