mirror of
				https://github.com/bellard/quickjs.git
				synced 2025-05-29 01:49:18 +08:00 
			
		
		
		
	better promise rejection tracker heuristics (#112)
This commit is contained in:
		
							parent
							
								
									d7cdfdc8d7
								
							
						
					
					
						commit
						3c39307c22
					
				@ -136,11 +136,18 @@ typedef struct {
 | 
				
			|||||||
    JSValue on_message_func;
 | 
					    JSValue on_message_func;
 | 
				
			||||||
} JSWorkerMessageHandler;
 | 
					} JSWorkerMessageHandler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    struct list_head link;
 | 
				
			||||||
 | 
					    JSValue promise;
 | 
				
			||||||
 | 
					    JSValue reason;
 | 
				
			||||||
 | 
					} JSRejectedPromiseEntry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct JSThreadState {
 | 
					typedef struct JSThreadState {
 | 
				
			||||||
    struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */
 | 
					    struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */
 | 
				
			||||||
    struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
 | 
					    struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */
 | 
				
			||||||
    struct list_head os_timers; /* list of JSOSTimer.link */
 | 
					    struct list_head os_timers; /* list of JSOSTimer.link */
 | 
				
			||||||
    struct list_head port_list; /* list of JSWorkerMessageHandler.link */
 | 
					    struct list_head port_list; /* list of JSWorkerMessageHandler.link */
 | 
				
			||||||
 | 
					    struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */
 | 
				
			||||||
    int eval_script_recurse; /* only used in the main thread */
 | 
					    int eval_script_recurse; /* only used in the main thread */
 | 
				
			||||||
    int next_timer_id; /* for setTimeout() */
 | 
					    int next_timer_id; /* for setTimeout() */
 | 
				
			||||||
    /* not used in the main thread */
 | 
					    /* not used in the main thread */
 | 
				
			||||||
@ -3986,6 +3993,7 @@ void js_std_init_handlers(JSRuntime *rt)
 | 
				
			|||||||
    init_list_head(&ts->os_signal_handlers);
 | 
					    init_list_head(&ts->os_signal_handlers);
 | 
				
			||||||
    init_list_head(&ts->os_timers);
 | 
					    init_list_head(&ts->os_timers);
 | 
				
			||||||
    init_list_head(&ts->port_list);
 | 
					    init_list_head(&ts->port_list);
 | 
				
			||||||
 | 
					    init_list_head(&ts->rejected_promise_list);
 | 
				
			||||||
    ts->next_timer_id = 1;
 | 
					    ts->next_timer_id = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    JS_SetRuntimeOpaque(rt, ts);
 | 
					    JS_SetRuntimeOpaque(rt, ts);
 | 
				
			||||||
@ -4023,6 +4031,13 @@ void js_std_free_handlers(JSRuntime *rt)
 | 
				
			|||||||
        free_timer(rt, th);
 | 
					        free_timer(rt, th);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_for_each_safe(el, el1, &ts->rejected_promise_list) {
 | 
				
			||||||
 | 
					        JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
 | 
				
			||||||
 | 
					        JS_FreeValueRT(rt, rp->promise);
 | 
				
			||||||
 | 
					        JS_FreeValueRT(rt, rp->reason);
 | 
				
			||||||
 | 
					        free(rp);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifdef USE_WORKER
 | 
					#ifdef USE_WORKER
 | 
				
			||||||
    /* XXX: free port_list ? */
 | 
					    /* XXX: free port_list ? */
 | 
				
			||||||
    js_free_message_pipe(ts->recv_pipe);
 | 
					    js_free_message_pipe(ts->recv_pipe);
 | 
				
			||||||
@ -4048,13 +4063,66 @@ void js_std_dump_error(JSContext *ctx)
 | 
				
			|||||||
    JS_FreeValue(ctx, exception_val);
 | 
					    JS_FreeValue(ctx, exception_val);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts,
 | 
				
			||||||
 | 
					                                                     JSValueConst promise)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    struct list_head *el;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    list_for_each(el, &ts->rejected_promise_list) {
 | 
				
			||||||
 | 
					        JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
 | 
				
			||||||
 | 
					        if (JS_SameValue(ctx, rp->promise, promise))
 | 
				
			||||||
 | 
					            return rp;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return NULL;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
 | 
					void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise,
 | 
				
			||||||
                                      JSValueConst reason,
 | 
					                                      JSValueConst reason,
 | 
				
			||||||
                                      BOOL is_handled, void *opaque)
 | 
					                                      BOOL is_handled, void *opaque)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    JSRuntime *rt = JS_GetRuntime(ctx);
 | 
				
			||||||
 | 
					    JSThreadState *ts = JS_GetRuntimeOpaque(rt);
 | 
				
			||||||
 | 
					    JSRejectedPromiseEntry *rp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!is_handled) {
 | 
					    if (!is_handled) {
 | 
				
			||||||
        fprintf(stderr, "Possibly unhandled promise rejection: ");
 | 
					        /* add a new entry if needed */
 | 
				
			||||||
        js_std_dump_error1(ctx, reason);
 | 
					        rp = find_rejected_promise(ctx, ts, promise);
 | 
				
			||||||
 | 
					        if (!rp) {
 | 
				
			||||||
 | 
					            rp = malloc(sizeof(*rp));
 | 
				
			||||||
 | 
					            if (rp) {
 | 
				
			||||||
 | 
					                rp->promise = JS_DupValue(ctx, promise);
 | 
				
			||||||
 | 
					                rp->reason = JS_DupValue(ctx, reason);
 | 
				
			||||||
 | 
					                list_add_tail(&rp->link, &ts->rejected_promise_list);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        /* the rejection is handled, so the entry can be removed if present */
 | 
				
			||||||
 | 
					        rp = find_rejected_promise(ctx, ts, promise);
 | 
				
			||||||
 | 
					        if (rp) {
 | 
				
			||||||
 | 
					            JS_FreeValue(ctx, rp->promise);
 | 
				
			||||||
 | 
					            JS_FreeValue(ctx, rp->reason);
 | 
				
			||||||
 | 
					            list_del(&rp->link);
 | 
				
			||||||
 | 
					            free(rp);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* check if there are pending promise rejections. It must be done
 | 
				
			||||||
 | 
					   asynchrously in case a rejected promise is handled later. Currently
 | 
				
			||||||
 | 
					   we do it once the application is about to sleep. It could be done
 | 
				
			||||||
 | 
					   more often if needed. */
 | 
				
			||||||
 | 
					static void js_std_promise_rejection_check(JSContext *ctx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    JSRuntime *rt = JS_GetRuntime(ctx);
 | 
				
			||||||
 | 
					    JSThreadState *ts = JS_GetRuntimeOpaque(rt);
 | 
				
			||||||
 | 
					    struct list_head *el;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (unlikely(!list_empty(&ts->rejected_promise_list))) {
 | 
				
			||||||
 | 
					        list_for_each(el, &ts->rejected_promise_list) {
 | 
				
			||||||
 | 
					            JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link);
 | 
				
			||||||
 | 
					            fprintf(stderr, "Possibly unhandled promise rejection: ");
 | 
				
			||||||
 | 
					            js_std_dump_error1(ctx, rp->reason);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        exit(1);
 | 
					        exit(1);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -4077,6 +4145,8 @@ void js_std_loop(JSContext *ctx)
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_std_promise_rejection_check(ctx);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if (!os_poll_func || os_poll_func(ctx))
 | 
					        if (!os_poll_func || os_poll_func(ctx))
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -4108,6 +4178,8 @@ JSValue js_std_await(JSContext *ctx, JSValue obj)
 | 
				
			|||||||
                js_std_dump_error(ctx1);
 | 
					                js_std_dump_error(ctx1);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (err == 0) {
 | 
					            if (err == 0) {
 | 
				
			||||||
 | 
					                js_std_promise_rejection_check(ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (os_poll_func)
 | 
					                if (os_poll_func)
 | 
				
			||||||
                    os_poll_func(ctx);
 | 
					                    os_poll_func(ctx);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -294,6 +294,22 @@ function test_async_gc()
 | 
				
			|||||||
    })();
 | 
					    })();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* check that the promise async rejection handler is not invoked when
 | 
				
			||||||
 | 
					   the rejection is handled not too late after the promise
 | 
				
			||||||
 | 
					   rejection. */
 | 
				
			||||||
 | 
					function test_async_promise_rejection()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    var counter = 0;
 | 
				
			||||||
 | 
					    var p1, p2, p3;
 | 
				
			||||||
 | 
					    p1 = Promise.reject();
 | 
				
			||||||
 | 
					    p2 = Promise.reject();
 | 
				
			||||||
 | 
					    p3 = Promise.resolve();
 | 
				
			||||||
 | 
					    p1.catch(() => counter++);
 | 
				
			||||||
 | 
					    p2.catch(() => counter++);
 | 
				
			||||||
 | 
					    p3.then(() => counter++)
 | 
				
			||||||
 | 
					    os.setTimeout(() => { assert(counter, 3) }, 10);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test_printf();
 | 
					test_printf();
 | 
				
			||||||
test_file1();
 | 
					test_file1();
 | 
				
			||||||
test_file2();
 | 
					test_file2();
 | 
				
			||||||
@ -304,4 +320,5 @@ test_os_exec();
 | 
				
			|||||||
test_timer();
 | 
					test_timer();
 | 
				
			||||||
test_ext_json();
 | 
					test_ext_json();
 | 
				
			||||||
test_async_gc();
 | 
					test_async_gc();
 | 
				
			||||||
 | 
					test_async_promise_rejection();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user