diff --git a/BUILD.gn b/BUILD.gn index 7a3af7342f..f90702e8d4 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -3330,6 +3330,8 @@ if (is_component_build) { v8_executable("d8") { sources = [ "$target_gen_dir/d8-js.cc", + "src/async-hooks-wrapper.cc", + "src/async-hooks-wrapper.h", "src/d8-console.cc", "src/d8-console.h", "src/d8.cc", diff --git a/src/async-hooks-wrapper.cc b/src/async-hooks-wrapper.cc new file mode 100644 index 0000000000..6501e0388d --- /dev/null +++ b/src/async-hooks-wrapper.cc @@ -0,0 +1,245 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/async-hooks-wrapper.h" +#include "src/d8.h" + +namespace v8 { + +void AsyncHooksWrap::Enable() { enabled_ = true; } + +void AsyncHooksWrap::Disable() { enabled_ = false; } + +v8::Local AsyncHooksWrap::init_function() const { + return init_function_.Get(isolate_); +} +void AsyncHooksWrap::set_init_function(v8::Local value) { + init_function_.Reset(isolate_, value); +} +v8::Local AsyncHooksWrap::before_function() const { + return before_function_.Get(isolate_); +} +void AsyncHooksWrap::set_before_function(v8::Local value) { + before_function_.Reset(isolate_, value); +} +v8::Local AsyncHooksWrap::after_function() const { + return after_function_.Get(isolate_); +} +void AsyncHooksWrap::set_after_function(v8::Local value) { + after_function_.Reset(isolate_, value); +} +v8::Local AsyncHooksWrap::promiseResolve_function() const { + return promiseResolve_function_.Get(isolate_); +} +void AsyncHooksWrap::set_promiseResolve_function( + v8::Local value) { + promiseResolve_function_.Reset(isolate_, value); +} + +static AsyncHooksWrap* UnwrapHook( + const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope scope(isolate); + Local hook = args.This(); + Local wrap = Local::Cast(hook->GetInternalField(0)); + void* ptr = wrap->Value(); + return static_cast(ptr); +} + +static void EnableHook(const v8::FunctionCallbackInfo& args) { + AsyncHooksWrap* wrap = UnwrapHook(args); + wrap->Enable(); +} + +static void DisableHook(const v8::FunctionCallbackInfo& args) { + AsyncHooksWrap* wrap = UnwrapHook(args); + wrap->Disable(); +} + +async_id_t AsyncHooks::GetExecutionAsyncId() const { + return asyncContexts.top().execution_async_id; +} + +async_id_t AsyncHooks::GetTriggerAsyncId() const { + return asyncContexts.top().trigger_async_id; +} + +Local AsyncHooks::CreateHook( + const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + EscapableHandleScope handle_scope(isolate); + + Local currentContext = isolate->GetCurrentContext(); + + AsyncHooksWrap* wrap = new AsyncHooksWrap(isolate); + + CHECK(args[0]->IsObject()); + + Local fn_obj = args[0].As(); + +#define SET_HOOK_FN(name) \ + Local name##_v = \ + fn_obj \ + ->Get(currentContext, \ + String::NewFromUtf8(isolate, #name, NewStringType::kNormal) \ + .ToLocalChecked()) \ + .ToLocalChecked(); \ + if (name##_v->IsFunction()) { \ + wrap->set_##name##_function(name##_v.As()); \ + } + + SET_HOOK_FN(init); + SET_HOOK_FN(before); + SET_HOOK_FN(after); + SET_HOOK_FN(promiseResolve); +#undef SET_HOOK_FN + + async_wraps_.push_back(wrap); + + Local obj = async_hooks_templ.Get(isolate) + ->NewInstance(currentContext) + .ToLocalChecked(); + obj->SetInternalField(0, External::New(isolate, wrap)); + + return handle_scope.Escape(obj); +} + +void AsyncHooks::ShellPromiseHook(PromiseHookType type, Local promise, + Local parent) { + AsyncHooks* hooks = Shell::GetAsyncHooks(); + + HandleScope handle_scope(hooks->isolate_); + + Local currentContext = hooks->isolate_->GetCurrentContext(); + + if (type == PromiseHookType::kInit) { + ++hooks->current_async_id; + Local async_id = + Integer::New(hooks->isolate_, hooks->current_async_id); + + promise->SetPrivate(currentContext, + hooks->async_id_smb.Get(hooks->isolate_), async_id); + if (parent->IsPromise()) { + Local parent_promise = parent.As(); + Local parent_async_id = + parent_promise + ->GetPrivate(hooks->isolate_->GetCurrentContext(), + hooks->async_id_smb.Get(hooks->isolate_)) + .ToLocalChecked(); + promise->SetPrivate(currentContext, + hooks->trigger_id_smb.Get(hooks->isolate_), + parent_async_id); + } else { + CHECK(parent->IsUndefined()); + Local trigger_id = Integer::New(hooks->isolate_, 0); + promise->SetPrivate(currentContext, + hooks->trigger_id_smb.Get(hooks->isolate_), + trigger_id); + } + } else if (type == PromiseHookType::kBefore) { + AsyncContext ctx; + ctx.execution_async_id = + promise + ->GetPrivate(hooks->isolate_->GetCurrentContext(), + hooks->async_id_smb.Get(hooks->isolate_)) + .ToLocalChecked() + .As() + ->Value(); + ctx.trigger_async_id = + promise + ->GetPrivate(hooks->isolate_->GetCurrentContext(), + hooks->trigger_id_smb.Get(hooks->isolate_)) + .ToLocalChecked() + .As() + ->Value(); + hooks->asyncContexts.push(ctx); + } else if (type == PromiseHookType::kAfter) { + hooks->asyncContexts.pop(); + } + + for (AsyncHooksWrap* wrap : hooks->async_wraps_) { + PromiseHookDispatch(type, promise, parent, wrap, hooks); + } +} + +void AsyncHooks::Initialize() { + HandleScope handle_scope(isolate_); + + async_hook_ctor.Reset(isolate_, FunctionTemplate::New(isolate_)); + async_hook_ctor.Get(isolate_)->SetClassName( + String::NewFromUtf8(isolate_, "AsyncHook", NewStringType::kNormal) + .ToLocalChecked()); + + async_hooks_templ.Reset(isolate_, + async_hook_ctor.Get(isolate_)->InstanceTemplate()); + async_hooks_templ.Get(isolate_)->SetInternalFieldCount(1); + async_hooks_templ.Get(isolate_)->Set( + String::NewFromUtf8(isolate_, "enable"), + FunctionTemplate::New(isolate_, EnableHook)); + async_hooks_templ.Get(isolate_)->Set( + String::NewFromUtf8(isolate_, "disable"), + FunctionTemplate::New(isolate_, DisableHook)); + + async_id_smb.Reset(isolate_, Private::New(isolate_)); + trigger_id_smb.Reset(isolate_, Private::New(isolate_)); + + isolate_->SetPromiseHook(ShellPromiseHook); +} + +void AsyncHooks::Deinitialize() { + isolate_->SetPromiseHook(nullptr); + for (AsyncHooksWrap* wrap : async_wraps_) { + delete wrap; + } +} + +void AsyncHooks::PromiseHookDispatch(PromiseHookType type, + Local promise, + Local parent, AsyncHooksWrap* wrap, + AsyncHooks* hooks) { + if (!wrap->IsEnabled()) { + return; + } + + HandleScope handle_scope(hooks->isolate_); + + Local rcv = Undefined(hooks->isolate_); + Local async_id = + promise + ->GetPrivate(hooks->isolate_->GetCurrentContext(), + hooks->async_id_smb.Get(hooks->isolate_)) + .ToLocalChecked(); + Local args[1] = {async_id}; + + // Sacrifice the brevity for readability and debugfulness + if (type == PromiseHookType::kInit) { + if (!wrap->init_function().IsEmpty()) { + Local initArgs[4] = { + async_id, + String::NewFromUtf8(hooks->isolate_, "PROMISE", + NewStringType::kNormal) + .ToLocalChecked(), + promise + ->GetPrivate(hooks->isolate_->GetCurrentContext(), + hooks->trigger_id_smb.Get(hooks->isolate_)) + .ToLocalChecked(), + promise}; + wrap->init_function()->Call(rcv, 4, initArgs); + } + } else if (type == PromiseHookType::kBefore) { + if (!wrap->before_function().IsEmpty()) { + wrap->before_function()->Call(rcv, 1, args); + } + } else if (type == PromiseHookType::kAfter) { + if (!wrap->after_function().IsEmpty()) { + wrap->after_function()->Call(rcv, 1, args); + } + } else if (type == PromiseHookType::kResolve) { + if (!wrap->promiseResolve_function().IsEmpty()) { + wrap->promiseResolve_function()->Call(rcv, 1, args); + } + } +} + +} // namespace v8 diff --git a/src/async-hooks-wrapper.h b/src/async-hooks-wrapper.h new file mode 100644 index 0000000000..c0c72373e0 --- /dev/null +++ b/src/async-hooks-wrapper.h @@ -0,0 +1,95 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_ASYNC_HOOKS_WRAPPER_H_ +#define V8_ASYNC_HOOKS_WRAPPER_H_ + +#include + +#include "include/v8.h" +#include "src/objects.h" + +namespace v8 { + +typedef double async_id_t; + +struct AsyncContext { + async_id_t execution_async_id; + async_id_t trigger_async_id; +}; + +class AsyncHooksWrap { + public: + explicit AsyncHooksWrap(Isolate* isolate) { + enabled_ = false; + isolate_ = isolate; + } + void Enable(); + void Disable(); + bool IsEnabled() const { return enabled_; } + + inline v8::Local init_function() const; + inline void set_init_function(v8::Local value); + inline v8::Local before_function() const; + inline void set_before_function(v8::Local value); + inline v8::Local after_function() const; + inline void set_after_function(v8::Local value); + inline v8::Local promiseResolve_function() const; + inline void set_promiseResolve_function(v8::Local value); + + private: + Isolate* isolate_; + + Persistent init_function_; + Persistent before_function_; + Persistent after_function_; + Persistent promiseResolve_function_; + + bool enabled_; +}; + +class AsyncHooks { + public: + explicit AsyncHooks(Isolate* isolate) { + isolate_ = isolate; + + AsyncContext ctx; + ctx.execution_async_id = 1; + ctx.trigger_async_id = 0; + asyncContexts.push(ctx); + current_async_id = 1; + + Initialize(); + } + ~AsyncHooks() { Deinitialize(); } + + async_id_t GetExecutionAsyncId() const; + async_id_t GetTriggerAsyncId() const; + + Local CreateHook(const v8::FunctionCallbackInfo& args); + + private: + std::vector async_wraps_; + Isolate* isolate_; + Persistent async_hook_ctor; + Persistent async_hooks_templ; + Persistent async_id_smb; + Persistent trigger_id_smb; + + void Initialize(); + void Deinitialize(); + + static void ShellPromiseHook(PromiseHookType type, Local promise, + Local parent); + static void PromiseHookDispatch(PromiseHookType type, Local promise, + Local parent, AsyncHooksWrap* wrap, + AsyncHooks* hooks); + + std::stack asyncContexts; + async_id_t current_async_id; +}; + +} // namespace v8 + +#endif // V8_ASYNC_HOOKS_WRAPPER_H_ diff --git a/src/d8.cc b/src/d8.cc index b38547115a..bc1d1648e1 100644 --- a/src/d8.cc +++ b/src/d8.cc @@ -469,6 +469,7 @@ base::LazyMutex Shell::workers_mutex_; bool Shell::allow_new_workers_ = true; std::vector Shell::workers_; std::vector Shell::externalized_contents_; +AsyncHooks* Shell::async_hooks_wrapper_; base::LazyMutex Shell::isolate_status_lock_; std::map Shell::isolate_status_; base::LazyMutex Shell::cached_code_mutex_; @@ -1244,6 +1245,32 @@ void Shell::RealmSharedSet(Local property, data->realm_shared_.Reset(isolate, value); } +// async_hooks.createHook() registers functions to be called for different +// lifetime events of each async operation. +void Shell::AsyncHooksCreateHook( + const v8::FunctionCallbackInfo& args) { + Local wrap = async_hooks_wrapper_->CreateHook(args); + args.GetReturnValue().Set(wrap); +} + +// async_hooks.executionAsyncId() returns the asyncId of the current execution +// context. +void Shell::AsyncHooksExecutionAsyncId( + const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + args.GetReturnValue().Set( + v8::Number::New(isolate, async_hooks_wrapper_->GetExecutionAsyncId())); +} + +void Shell::AsyncHooksTriggerAsyncId( + const v8::FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + HandleScope handle_scope(isolate); + args.GetReturnValue().Set( + v8::Number::New(isolate, async_hooks_wrapper_->GetTriggerAsyncId())); +} + void WriteToFile(FILE* file, const v8::FunctionCallbackInfo& args) { for (int i = 0; i < args.Length(); i++) { HandleScope handle_scope(args.GetIsolate()); @@ -1857,6 +1884,26 @@ Local Shell::CreateGlobalTemplate(Isolate* isolate) { .ToLocalChecked(), os_templ); + if (i::FLAG_expose_async_hooks) { + Local async_hooks_templ = ObjectTemplate::New(isolate); + async_hooks_templ->Set( + String::NewFromUtf8(isolate, "createHook", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, AsyncHooksCreateHook)); + async_hooks_templ->Set( + String::NewFromUtf8(isolate, "executionAsyncId", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, AsyncHooksExecutionAsyncId)); + async_hooks_templ->Set( + String::NewFromUtf8(isolate, "triggerAsyncId", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, AsyncHooksTriggerAsyncId)); + global_template->Set( + String::NewFromUtf8(isolate, "async_hooks", NewStringType::kNormal) + .ToLocalChecked(), + async_hooks_templ); + } + return global_template; } @@ -1910,6 +1957,10 @@ void Shell::Initialize(Isolate* isolate) { v8::Isolate::kMessageError | v8::Isolate::kMessageWarning | v8::Isolate::kMessageInfo | v8::Isolate::kMessageDebug | v8::Isolate::kMessageLog); + + if (i::FLAG_expose_async_hooks) { + async_hooks_wrapper_ = new AsyncHooks(isolate); + } } @@ -2058,6 +2109,10 @@ void Shell::WriteLcovData(v8::Isolate* isolate, const char* file) { } void Shell::OnExit(v8::Isolate* isolate) { + if (i::FLAG_expose_async_hooks) { + delete async_hooks_wrapper_; // This uses the isolate + } + // Dump basic block profiling data. if (i::BasicBlockProfiler* profiler = reinterpret_cast(isolate)->basic_block_profiler()) { diff --git a/src/d8.h b/src/d8.h index e1c9f928d3..1ea062ed83 100644 --- a/src/d8.h +++ b/src/d8.h @@ -13,6 +13,7 @@ #include #include "src/allocation.h" +#include "src/async-hooks-wrapper.h" #include "src/base/platform/time.h" #include "src/string-hasher.h" #include "src/utils.h" @@ -400,6 +401,14 @@ class Shell : public i::AllStatic { Local value, const PropertyCallbackInfo& info); + static void AsyncHooksCreateHook( + const v8::FunctionCallbackInfo& args); + static void AsyncHooksExecutionAsyncId( + const v8::FunctionCallbackInfo& args); + static void AsyncHooksTriggerAsyncId( + const v8::FunctionCallbackInfo& args); + static AsyncHooks* GetAsyncHooks() { return async_hooks_wrapper_; } + static void Print(const v8::FunctionCallbackInfo& args); static void PrintErr(const v8::FunctionCallbackInfo& args); static void Write(const v8::FunctionCallbackInfo& args); @@ -494,6 +503,8 @@ class Shell : public i::AllStatic { static std::vector workers_; static std::vector externalized_contents_; + static AsyncHooks* async_hooks_wrapper_; + static void WriteIgnitionDispatchCountersFile(v8::Isolate* isolate); // Append LCOV coverage data to file. static void WriteLcovData(v8::Isolate* isolate, const char* file); diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 7eb47cd26a..69d1c33f97 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -863,6 +863,7 @@ DEFINE_BOOL(enable_experimental_builtins, false, "enable new csa-based experimental builtins") DEFINE_BOOL(disallow_code_generation_from_strings, false, "disallow eval and friends") +DEFINE_BOOL(expose_async_hooks, false, "expose async_hooks object") // builtins.cc DEFINE_BOOL(allow_unsafe_function_constructor, false, diff --git a/test/mjsunit/allocation-site-info.js b/test/mjsunit/allocation-site-info.js index 7a5f2c70bf..66ebe49369 100644 --- a/test/mjsunit/allocation-site-info.js +++ b/test/mjsunit/allocation-site-info.js @@ -258,6 +258,12 @@ assertKind(elements_kind.fast, obj); obj = newarraycase_list_smiobj(2); assertKind(elements_kind.fast, obj); +// Perform a gc because without it the test below can experience an +// allocation failure at an inconvenient point. Allocation mementos get +// cleared on gc, and they can't deliver elements kind feedback when that +// happens. +gc(); + // Case: array constructor calls with out of date feedback. // The boilerplate should incorporate all feedback, but the input array // should be minimally transitioned based on immediate need. diff --git a/test/mjsunit/async-hooks/api-methods.js b/test/mjsunit/async-hooks/api-methods.js new file mode 100644 index 0000000000..19ff9c0064 --- /dev/null +++ b/test/mjsunit/async-hooks/api-methods.js @@ -0,0 +1,68 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --expose-async-hooks + +// Check for correct API methods +(function() { + assertTrue(async_hooks.hasOwnProperty('createHook'), + 'Async hooks missing createHook method'); + assertTrue(async_hooks.hasOwnProperty('executionAsyncId'), + 'Async hooks missing executionAsyncId method'); + assertTrue(async_hooks.hasOwnProperty('triggerAsyncId'), + 'Async hooks missing triggerAsyncId method'); + + let ah = async_hooks.createHook({}); + assertTrue(ah.hasOwnProperty('enable'), 'Async hooks missing enable method'); + assertTrue(ah.hasOwnProperty('disable'), + 'Async hooks missing disable method'); +})(); + +// Check for correct enabling/disabling of async hooks +(function() { + let storedPromise; + let ah = async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + storedPromise = resource.promise || resource; + } + }); + ah.enable(); + + let createdPromise = new Promise(function(resolve) { + resolve(42); + }); + assertSame(storedPromise, createdPromise, + "Async hooks weren't enabled correctly"); + ah.disable(); + createdPromise = Promise.resolve(52); + assertNotSame(storedPromise, createdPromise, + "Async hooks weren't disabled correctly"); + ah.enable(); + createdPromise = Promise.resolve(62); + assertSame(storedPromise, createdPromise, + "Async hooks weren't enabled correctly"); +})(); diff --git a/test/mjsunit/async-hooks/async-await-tree.js b/test/mjsunit/async-hooks/async-await-tree.js new file mode 100644 index 0000000000..955355cf31 --- /dev/null +++ b/test/mjsunit/async-hooks/async-await-tree.js @@ -0,0 +1,74 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --expose-async-hooks + +// Check for async/await asyncIds relation +(function() { + let asyncIds = [], triggerIds = []; + let ah = async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + if (type !== 'PROMISE') { + return; + } + asyncIds.push(asyncId); + triggerIds.push(triggerAsyncId); + }, + }); + ah.enable(); + + // Simplified version of Node.js util.promisify(setTimeout) + function sleep(callback, timeout) { + const promise = new Promise(function(resolve, reject) { + try { + setTimeout((err, ...values) => { + if (err) { + reject(err); + } else { + resolve(values[0]); + } + }, timeout); + } catch (err) { + reject(err); + } + }); + return promise; + } + + async function foo() { + await sleep(10); + } + + foo().then(function() { + assertEquals(asyncIds.length, 6); + assertEquals(triggerIds.length, 6); + assertEquals(triggerIds[2], asyncIds[0]); + assertEquals(triggerIds[3], asyncIds[2]); + assertEquals(triggerIds[4], asyncIds[0]); + assertEquals(triggerIds[5], asyncIds[1]); + }); +})(); diff --git a/test/mjsunit/async-hooks/chained-promises.js b/test/mjsunit/async-hooks/chained-promises.js new file mode 100644 index 0000000000..8b346530eb --- /dev/null +++ b/test/mjsunit/async-hooks/chained-promises.js @@ -0,0 +1,48 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --expose-async-hooks + +// Check for chained promises asyncIds relation +(function() { + let asyncIds = [], triggerIds = []; + let ah = async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + asyncIds.push(asyncId); + triggerIds.push(triggerAsyncId); + }, + }); + ah.enable(); + let createdPromise = new Promise(function(resolve) { + resolve(42); + }).then(function() { + assertEquals(asyncIds.length, 2, 'Exactly 2 promises should be inited'); + assertEquals(triggerIds.length, 2, 'Exactly 2 promises should be inited'); + assertEquals(triggerIds[1], asyncIds[0], + "Parent promise asyncId doesn't correspond to child triggerAsyncId"); + }); +})(); diff --git a/test/mjsunit/async-hooks/execution-order.js b/test/mjsunit/async-hooks/execution-order.js new file mode 100644 index 0000000000..f63ecf0032 --- /dev/null +++ b/test/mjsunit/async-hooks/execution-order.js @@ -0,0 +1,65 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --expose-async-hooks + +// Check for correct execution of available hooks and asyncIds +(function() { + let inited = false, resolved = false, before = false, after = false; + let storedAsyncId; + let ah = async_hooks.createHook({ + init(asyncId, type, triggerAsyncId, resource) { + if (type !== 'PROMISE') { + return; + } + inited = true; + storedAsyncId = asyncId; + }, + promiseResolve(asyncId) { + assertEquals(asyncId, storedAsyncId, 'AsyncId mismatch in resolve hook'); + resolved = true; + }, + before(asyncId) { + assertEquals(asyncId, storedAsyncId, 'AsyncId mismatch in before hook'); + before = true; + }, + after(asyncId) { + assertEquals(asyncId, storedAsyncId, 'AsyncId mismatch in after hook'); + after = true; + }, + }); + ah.enable(); + + new Promise(function(resolve) { + resolve(42); + }).then(function() { + assertTrue(inited, "Didn't call init hook"); + assertTrue(resolved, "Didn't call resolve hook"); + assertTrue(before, "Didn't call before hook before the callback"); + assertFalse(after, "Called after hook before the callback"); + }); +})(); diff --git a/test/mjsunit/async-hooks/promises-async-await.js b/test/mjsunit/async-hooks/promises-async-await.js new file mode 100644 index 0000000000..2eba6ba6c5 --- /dev/null +++ b/test/mjsunit/async-hooks/promises-async-await.js @@ -0,0 +1,70 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Flags: --expose-async-hooks + +// Check for executionAsyncId/triggerAsyncId when chained promises and +// async/await are combined +(function() { + let p; + let outerExecutionAsyncId = -1, outerTriggerAsyncId = -1; + + function inIrrelevantContext(resolve) { + resolve(42); + } + + function inContext1(foo) { + foo(); + } + + function inContext2(foo) { + foo(); + } + + outerExecutionAsyncId = async_hooks.executionAsyncId(); + outerTriggerAsyncId = async_hooks.triggerAsyncId(); + + inContext1(() => { + p = new Promise(resolve => { + assertEquals(outerExecutionAsyncId, async_hooks.executionAsyncId()); + assertEquals(outerTriggerAsyncId, async_hooks.triggerAsyncId()); + inIrrelevantContext(resolve); + }).then(() => { + assertNotEquals(outerExecutionAsyncId, async_hooks.executionAsyncId()); + assertNotEquals(outerTriggerAsyncId, async_hooks.triggerAsyncId()); + }); + }); + + inContext2(async () => { + assertEquals(outerExecutionAsyncId, async_hooks.executionAsyncId()); + assertEquals(outerTriggerAsyncId, async_hooks.triggerAsyncId()); + await p; + assertNotEquals(outerExecutionAsyncId, async_hooks.executionAsyncId()); + assertNotEquals(outerTriggerAsyncId, async_hooks.triggerAsyncId()); + }); + +})(); diff --git a/test/mjsunit/es8/async-await-interleaved.js b/test/mjsunit/es8/async-await-interleaved.js new file mode 100644 index 0000000000..edc5a420ac --- /dev/null +++ b/test/mjsunit/es8/async-await-interleaved.js @@ -0,0 +1,61 @@ +// Copyright 2018 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Check for correct interleaving of Promises and async/await +(function () { + const iterations = 10; + let promiseCounter = iterations; + let awaitCounter = 0; + + async function check(v) { + awaitCounter = v; + // The following checks ensure that "await" takes 3 ticks on the + // microtask queue. Note: this will change in the future + if (awaitCounter === 0) { + assertEquals(iterations, promiseCounter); + } else if (awaitCounter <= Math.floor(iterations / 3)) { + assertEquals(iterations - awaitCounter * 3, promiseCounter); + } else { + assertEquals(0, promiseCounter); + } + } + + async function f() { + for (let i = 0; i < iterations; i++) { + await check(i); + } + return 0; + } + + function countdown(v) { + promiseCounter = v; + if (v > 0) Promise.resolve(v - 1).then(countdown); + } + + countdown(iterations); + f(); +})();