GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — [WIP] feat(async): fix #740, use async hooks to wrap nodejs async api by JiaLiPassion · Pull Request #795 · angular/zone.js
This repository was archived by the owner on Feb 26, 2024. It is now read-only.
Conversation
|
| properties = {queue: this.queue, flush: this.flush.bind(this)}; | ||
|
|
||
| flush() { | ||
| if (!useZoneAwarePromise) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to disable the tests? I would imagine that the tests should still work even with async_hook?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
@mhevery , sorry, I just began to debug those test codes, all tests should be able to run with async_hook, but some tests need to be modified.
So please ignore those test case files.
And I found to make all tests work may cause some time and some test will still wait nodejs to make some changes, so I just make the PR to let you review the zone.ts and zone_async_hooks core code.
I will finish those test codes and let you review later.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| expect(log).toEqual([]); | ||
| flush(); | ||
| expect(log).toEqual(['RValue', 'second value']); | ||
| setTimeout(() => { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why are these needed? Is it because the flush() no longer works?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
yes, just like the above one, I am just began to debug those tests, I will finish modifying those tests and let you review. Please ignore those files for now.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| patchEventTargetMethods: | ||
| (obj: any, addFnName?: string, removeFnName?: string, metaCreator?: any) => boolean; | ||
| patchOnProperties: (obj: any, properties: string[]) => void; | ||
| beforeRunTask: (zone: Zone, task: Task) => BeforeRunTaskStatus; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I am not a fan of beforeRunTask and afterRunTask, but I do understand why you need them. But I think it would mean that we should also add them to ZoneDelegate? Or are you planning on making them private?
How do we communicate to the developer that this particular Task can't be wrapped? It never has runTask executed on it. It can never be in fakeAsynczone.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
yes, those APIs are private.
And I will check fakeAsync , I think the runTask will be called because in fakeAsyncZone, the callback is task.invoke and it will finally call task.zone.runTask. I will check it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
| }); | ||
|
|
||
| global[Zone.__symbol__('setTimeout')] = global.setTimeout; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why are these needed here? Seems out of place
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
yes, those will be deleted finally, I just want to make some current test case pass easily, and I can debug them easily. I will remove them later.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| patchEventTargetMethods: () => false, | ||
| patchOnProperties: noop | ||
| patchOnProperties: noop, | ||
| beforeRunTask: (zone: Zone, task: Task) => { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I see you are making them private, but than we should somehow hide them from the public API.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I want to confirm I understand hiding from public API correctly.
We should hide beforeRunTask, runTask, afterRunTask from Zone if we use async_hooks, because beforeRunTask and afterRunTask should only be called from async_hooks, so runTask should never be called from user code, otherwise it will also call beforeRunTask and afterRunTask and cause issue.
so in async_hooks mode, all those 3 methods are not available, is that right?
For compatibility with non async_hooks, should we just add a flag to check we are using async_hooks or not, and if we are using async_hooks, we just return when user call runTask, is that ok? I will make a commit to describe it!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (!task) { | ||
| return; | ||
| } | ||
| const beforeRunTask: BeforeRunTaskStatus = (task as any)[Zone.__symbol__(BEFORE_RUN_TASK_STATUS)]; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Task has data. We should put BeforeRunTaskStatus there, rather than monkey patching it on Task directly.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Got it, I will change it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
waiting for currently for example both @mhevery , please help to check this is correct or not, thank you ! I made a PR to get This PR only correct the And it will be implemented soon. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@JiaLiPassion We are also interested in getting Zones working on top of async hooks avoiding monkey patching if possible and would be happy to help. It looks like some zone APIs require a handle to the callback associated with an async task (onIntercept, onInvoke, etc). Did you have thoughts on how these callbacks could be observed using async hooks without monkey patching? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@matthewloring, thank you for reply!
thank you and I definitely need your help on this changes.
Yes, you are right, currently I have not figure out how to get the
and other API such as But if And if @mhevery , if we use |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@mhevery , @matthewloring , other things that may change when use
in the current and if we use Because in
just like @matthewloring said, we have no we have to use |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@matthewloring , continue to discuss about the so in setTimeout(() => { console.log('timeout callback')' }); will only trigger but in So I think from the design, |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
That is correct, async hooks allows for observation of async events while zones relies on being able to reschedule async work. This was my primary concern with refactoring zones to use async hooks. One possibility would be to have two "modes" supported by zones. A fast, observation-only mode based on async hooks that supported async/await and a slower, monkey-patching based mode which allows rescheduling async tasks but does not have async/await support. This might be too invasive a change (@mhevery what are your thoughts?) but would that address your use case @JiaLiPassion? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Yeah, your suggestion could be a good solution. So it will be
And about the original |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I am on vacation till end of month. But your discussion sounds reasonable to me. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
If it has two 'modes', would it make sense to split the library into two? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@ofrobots, yeah, maybe build a |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
@JiaLiPassion we could already do this with |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
So you mean we load Zone.__load_patch('node'); Zone.__load_patch('async_hooks`); and add some checks to prevent both them are loaded, is that correct? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@JiaLiPassion that is what I had in mind |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@mhevery , got it, I will continue to work on it after the performance one is done. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@mhevery Is it expected behavior for zones to only support a subset of its API depending on the set of patches loaded using |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
In my understanding, it is, you can find all the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@JiaLiPassion Looking at the list of patches, it seems like they make zones aware of more async constructs but still support the full zones API. That is, the set of tasks that zones are aware of increases but if I attach an onIntercept function it will still behave correctly for all tasks. If an async hooks plugin were added and a user attempts to reschedule all task callbacks using onIntercept then all tasks corresponding to async hook events will ignore the results of the onIntercept callback if I understand correctly. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@matthewloring , I want to make sure I understand your meaning correctly.
const foo = function() { console.log('foo'); }; const fooInZone = Zone.current.fork({ name: 'test', onIntercept: function (..., zone, callback) { console.log('callback intercepted'); return callback; } }).wrap(foo); fooInZone(); And in this code, const fooInZone = Zone.current.fork({ name: 'test', onIntercept: function (..., zone, callback) { console.log('callback intercepted'); return callback; } }).run(() => { setTimeout(() => { }, 1000); }); In this code,
About reschedule, if you talk about const foo = function() { console.log('foo'); }; const fooInZone = Zone.current.fork({ name: 'test', onIntercept: function (..., zone, callback) { console.log('callback intercepted'); // here return a new callback return function() { console.log('new foo'); }; } }).wrap(foo); fooInZone(); do you mean some use case like the above code? And for const barZone = Zone.current.fork({ name: 'bar' }); const fooInZone = Zone.current.fork({ name: 'foo', onScheduleTask: function (zoneDelegate, currentZone, targetZone, task) { if (task.source === 'something') { // I want the task to be scheduled in bar zone task.cancelScheduleRequest(); return barZone.scheduleTask(task); } } }).wrap(foo); fooInZone(); And I would like to confirm
what is the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I will summarize the known issues here.
1. Native async/await issue.Now with The issue can be described as case1: promise's parent is an resolved promise.const p1 = Promise.resolve(); const p2 = p1.then(r => r); In this case, current function init(id: number, provider: string, triggerId: number, parentHandle: any) { if (provider === PROMISE_PROVIDER) { if (!parentHandle) { return; } const parentId = parentHandle.parentId; if (!parentId) { return; } const task = Zone.current.scheduleMicroTask(PROMISE_SOURCE, noop, null, noop); promiseTasks[id] = task; } } case2: the promise's parent is an unresolved promise.const p1 = new Promise(() => {}); // This will never be resolved const p2 = new Promise((resolve) => setTimeout(resolve, 1000, p1)); // resolve p2 with p1 after 1s In this case, the And only expose in this case, 2. Async hooks only support notification mode.As we discussed, async hooks can't get the control of 3. onHandleErrorBecause we can't get the control of callback, so we are not be able to do monkey-patch like try { task.callback.apply(target, args); } catch (err) { zone.handleError(err); } we have to use process.on('uncaughtException', (err: any) => { // get current zone Zone.current.handleError(err); } It will become a little risky. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Hello from |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@AndreasMadsen , thank you very much, and to implement const p1 = new Promise(() => {}); // This will never be resolved const p2 = new Promise((resolve) => setTimeout(resolve, 1000, p1)); // resolve p2 with p1 after 1s In this case, the in my understanding the function then(onFulfilled, onRejected) { const chainedPromise = new Promise(); if (this.state == UNRESOLVED) { // current promise not resolved, keep callback into a list this[thenList].push(chainPromise, onFulfilled, onRejected); } else { // current promise is resolved, we can enqueue the chained promise to microTask queue // here we really schedule an async microTask // in zone.js I need to know when promise get here enqueue(chainedPromise); } } function resolvePromise(promise, value) { // here the resolve hook will be triggered resolveHook(promise); if (isPromise(value) && value.state == UNRESOLVED) { // promise still unresolved value.then(...); return; } // value is thenable, so promise is not really resolved if (isThenable(value)) { value.then(...); return; } chainedPromiseList = getThenList(promise); // promise was really resolved, we enqueue to start a microTask for chained promise // in zone.js I need to know when promise get here chainedPromiseList.forEach(chainedPromise => enqueue(chainedPromise)) } **so I wonder is that possible to
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
I think you are using a technical vocabulary that is specific to zone.js, so I don't really understand what you are saying about
yes, this is what nodejs/node#13437 is about.
The const binding = process.binding('util'); const state2string = new Map([ [binding.kPending, 'pending'], [binding.kFulfilled, 'fulfilled'], [binding.kRejected, 'rejected'] ]); const [state, result] = binding.getPromiseDetails(resource.promise); const readableState = state2string.get(state); console.log(readableState); // fulfilled I think this was explained in nodejs/node#14038 (comment) I have no idea if |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@AndreasMadsen , thank you very much!
sorry for poor explanation. const p1 = new Promise(() => {}); // This will never be resolved const p2 = new Promise((resolve) => setTimeout(resolve, 1000, p1)); // resolve p2 with p1 after 1s in this case, And about get state of promise. const binding = process.binding('util'); const state2string = new Map([ [binding.kPending, 'pending'], [binding.kFulfilled, 'fulfilled'], [binding.kRejected, 'rejected'] ]); const [state, result] = binding.getPromiseDetails(resource.promise); const readableState = state2string.get(state); console.log(readableState); // fulfilled Thank you for the information,
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
I see, have you experimented with using
You should assume so. If you have a good use case, you should open an issue in node.js where you request this feature is made public.
Most C++ calls have, but you will have to benchmark it to be sure. The internal function is used in |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
the timing of promise.then() is late, and a really want is in my understandings, the code look like this. resolvePromise(promise, value) { promise.state = RESOLVED; // here I want a trigger. trigger(PROMISE_RESOLVED); // push the chained promise to microTask (process.tick?) queue. enqueueToMicroTask(getChainedPromises(promise)); } And
Got it, I will try to make some cases to describe my requirements.
Got it, thank you, I will make benchmark to test it. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Any news on this? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@jpsfs, currently I am waiting for https://bugs.chromium.org/p/v8/issues/detail?id=6862 to resolve the |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
8d64c10 to
4b52ea0
Compare
There was an error while loading. Please reload this page.
Update on 2018/01/04Support es2017 async/awaitBecause currently the idea can be described as the following code. init(id, triggerId, parentHandle) { const promise = parentHandle.promise; idPromises[id] = Zone.current; // save id and zone mapping promise.then = function(onResolve: any, onReject: any) { const task = zone.scheduleMicroTask(PROMISE_PROVIDER, noop, null, noop); isPromiseTick = true; process.nextTick(() => { task.zone.runTask(task, null, null); originalThen.call(this, onResolve, onReject); }); }; } before(id) { const zone = getZoneById(); setCurrentZoneFrame(zone); // retrieve the zone by id } after(id) { setCurrentZoneFrame(null); // leave zone } and consider the following use case. const zone = Zone.current.fork({name: 'promise'}); async function asyncOutside() { return 'asyncOutside'; } zone.run(async() => { const outside = await asyncOutside(); expect(outside).toEqual('asyncOutside'); expect(Zone.current.name).toEqual(zone.name); }) with
Because gulp test/node/es2017 will run |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Update on 2018/01/04Support
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@JiaLiPassion Be aware that the V8 team would like to remove The tracking won't be as easy as getting a specific event, but I think it is much easier for the V8 team to implement and I would like to avoid adding another promise-specific event to |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@AndreasMadsen , thank you for the information. If a And |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
That is correct. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@AndreasMadsen , got it, now |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
angular with tsconfig target ES2017 async/await will not work with zone.js
There was an error while loading. Please reload this page.