| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
|
||
| ### Overview | ||
|
|
||
| Here is a simple overview of the public API. All of this API is explained in |
There was a problem hiding this comment.
s/Here/Following
Sorry, something went wrong.
| ``` | ||
|
|
||
| #### `async_hooks.createHook(callbacks)` | ||
|
|
There was a problem hiding this comment.
args listing? e.g.
* `callbacks` ...
Sorry, something went wrong.
There was a problem hiding this comment.
Also, yaml block?
Sorry, something went wrong.
There was a problem hiding this comment.
Including args listing for consistency. The entire block below elaborates on what the callbacks are.
Not sure what you mean by yaml block could you give an example?
Sorry, something went wrong.
There was a problem hiding this comment.
Not sure what you mean by yaml block could you give an example?
<!-- YAML
added: REPLACEME
-->you can add those directly below headings :) (oh, and edit: the REPLACEME is literal, it will be replaced during releasing)
Sorry, something went wrong.
There was a problem hiding this comment.
OK added, thanks
Sorry, something went wrong.
| emulate the lifetime of handles and requests that do not fit this model. For | ||
| example, `HTTPParser` instances are recycled to improve performance. Therefore the | ||
| `destroy()` callback is called manually after a connection is done using | ||
| it, just before it's placed back into the unused resource pool. |
There was a problem hiding this comment.
s/it's/it is
Sorry, something went wrong.
| specifics of all functions that can be passed to `callbacks` is in the section | ||
| `Hook Callbacks`. | ||
|
|
||
| **Error Handling**: If any `AsyncHook` callbacks throw, the application will |
There was a problem hiding this comment.
This would be better as a heading... e.g.
##### Error Handling
Sorry, something went wrong.
| Some examples include `TCP`, `GetAddrInfo` and `HTTPParser`. Users will be able | ||
| to define their own `type` when using the public embedder API. | ||
|
|
||
| **Note:** It is possible to have type name collisions. Embedders are recommended |
There was a problem hiding this comment.
s/**Note:**/*Note*:
Sorry, something went wrong.
|
|
||
| **Note:** Some resources depend on GC for cleanup. So if a reference is made to | ||
| the `resource` object passed to `init()` it's possible that `destroy()` is | ||
| never called. Causing a memory leak in the application. Of course if you know |
There was a problem hiding this comment.
Please avoid the use of you
Sorry, something went wrong.
| placed in an unused resource pool to be used later. For these `destroy()` will | ||
| be called just before the resource is placed on the unused resource pool. | ||
|
|
||
| **Note:** Some resources depend on GC for cleanup. So if a reference is made to |
There was a problem hiding this comment.
s/**Note:**/*Note*:
Sorry, something went wrong.
| @@ -0,0 +1,670 @@ | |||
| # Async Hooks | |||
|
|
|||
|
|
|||
There was a problem hiding this comment.
No need for the extra blank lines in here
Sorry, something went wrong.
|
|
||
| The `async-hooks` module provides an API to register callbacks tracking the | ||
| lifetime of asynchronous resources created inside a Node.js application. | ||
|
|
There was a problem hiding this comment.
Please include something like:
It can be accessed using `require('async-hooks')`
Sorry, something went wrong.
|
|
||
| ## Terminology | ||
|
|
||
| An async resource represents either a "handle" or a "request". |
There was a problem hiding this comment.
rather than the quotes, make these italic. e.g. handle or a request
Sorry, something went wrong.
|
repeating my comment from #12892 (comment):
What effect does that have for async_hooks right now?
--- a/lib/async_hooks.js
+++ b/lib/async_hooks.js
@@ -222,6 +222,13 @@ class AsyncEvent {
processing_hook = false;
}
+ makeCallback(cb, ...args) {
+ emitBeforeS(this[async_id_symbol], this[trigger_id_symbol]);
+ cb(...args);
+ emitAfterS(this[async_id_symbol]);
+ return this;
+ }
+In response to @Fishrock123 (#12892 (comment)) ...
What made the domain API to high-level was not the Embedder API but the other API (I don't know what to call it try/catch). In async_hooks we are replacing the other API with the Hooks API. If only including the high-level Embedder API turns out to be a problem, it is a semver-minor change to introduce the remaining Embedder API. In #11883 (comment) I explained the problems with the domain API: Can you elaborate on the domains problem you allude to? |
Sorry, something went wrong.
|
|
||
| // Return the id of the handle responsible for triggering the callback of the | ||
| // current execution scope to fire. | ||
| const tid = aysnc_hooks.triggerId(); |
There was a problem hiding this comment.
s/aysnc/async
Sorry, something went wrong.
| ```js | ||
| const async_hooks = require('async_hooks'); | ||
|
|
||
| asyns_hooks.createHook({ |
There was a problem hiding this comment.
s/asyns/async
Sorry, something went wrong.
| TTYWRAP(6) -> Timeout(4) -> TIMERWRAP(5) -> TickObject(3) -> root(1) | ||
| ``` | ||
|
|
||
| The `TCPWRAP` isn't part of this graph; evne though it was the reason for |
There was a problem hiding this comment.
s/evne/even
Sorry, something went wrong.
|
|
||
| ### `class AsyncEvent()` | ||
|
|
||
| The class `AsyncEvent` was designed to be extended from for embedder's async |
There was a problem hiding this comment.
Maybe replace from for by by the?
Sorry, something went wrong.
| #### `AsyncEvent(type[, triggerId])` | ||
|
|
||
| * arguments | ||
| * `type` {String} the type of ascycn event |
There was a problem hiding this comment.
s/ascycn/async
Sorry, something went wrong.
|
|
||
| ```js | ||
| class DBQuery extends AsyncEvent { | ||
| construtor(db) { |
There was a problem hiding this comment.
s/construtor/constructor
Sorry, something went wrong.
| * `id` {number} Generated by `newId()` | ||
| * Returns {Undefined} | ||
|
|
||
| Notify hooks that a resource is being destroyed (or being moved to the free'd |
There was a problem hiding this comment.
s/free'd/freed
Sorry, something went wrong.
| ``` | ||
|
|
||
| It is important to note that the id returned fom `currentId()` is related to | ||
| execution timing. Not causality (which is covered by `triggerId()`). For |
There was a problem hiding this comment.
Nit: s/ /
Sorry, something went wrong.
| example: | ||
|
|
||
| ```js | ||
| const server = net.createServer(function onconnection(conn) { |
There was a problem hiding this comment.
Nit: s/onconnection/onConnection
Sorry, something went wrong.
| // MakeCallback(). | ||
| async_hooks.currentId(); | ||
|
|
||
| }).listen(port, function onlistening() { |
There was a problem hiding this comment.
Nit: s/onlistening/onListening
Sorry, something went wrong.
|
|
||
| * `triggerId` {number} | ||
| * `callback` {Function} | ||
| * Returns {Undefined} |
There was a problem hiding this comment.
All types should be lower cased. I saw Undefined, String, Object and Function
Sorry, something went wrong.
There was a problem hiding this comment.
All types? Getting mixed info here. I was told elsewhere only primitive types.
For now I lowercased Undefined and String as requested above.
Going to wait for clarification here until I lower case more.
Sorry, something went wrong.
There was a problem hiding this comment.
A nit. This also needs a summary at the top, as it's a long document.
Sorry, something went wrong.
| `${type}(${id}): trigger: ${triggerId} scope: ${cId}`); | ||
| }, | ||
| before (id) { | ||
| process._rawDebug(' '.repeat(ws) + 'before: ', id); |
There was a problem hiding this comment.
it's not explained why we need to use _rawDebug here.
Sorry, something went wrong.
There was a problem hiding this comment.
It's also not explained how to get the data out of the app process in general. If I want to send it across network or push to a database, all I can do is disable hooks and enable them back, but that has a potential of dropping a lot of information in meantime, so _rawDebug seems the only option to get any information out of async hooks.
Could this be addressed head-on in the docs?
If not, we should document practical use cases for this. All synchronous APIs can be used, but there's not a lot of them left.
Sorry, something went wrong.
There was a problem hiding this comment.
There is the option of not allowing any async hooks to execute while one of its own hooks is running. That should be the most straight forward approach, but we'd also loose information about what's happening during hook execution. If that's a fine trade then I can whip up the patch.
FYI: Reason process._rawDebug() needs to be used is because it writes directly to stderr. A similar effect would be to do fs.writeSync(2, 'foo');. Otherwise init will be called and cause a circular call to the hooks. Causing the application to crash.
Sorry, something went wrong.
There was a problem hiding this comment.
We all know why rawDebug is required, but the doc should mention that.
I'm not sure if disabling hooks while one is running is a good idea because you probably can't guarantee other parts of the app won't get executed between async calls of hook callback.
But without that we need to educate everyone they can only use sync writes (and make their hooks slow down other stuff).
Would it be possible to expose an API to push items to a queue that would then be offloaded somewhere on a background thread? I expect people will want to send hook results out live as means of debugging their code.
Pushing custom trave events from hooks might also be a thing.
Sorry, something went wrong.
There was a problem hiding this comment.
Oh, unless you mean not running hooks if a hook callback is one of the parents in callstack, which seems like a good solution to all the issues with sending out data.
Hooks code can be diagnosed with profiling and flamegraphs still, so that's not much of an issue.
Sorry, something went wrong.
There was a problem hiding this comment.
@trevnorris would that just be in the current tick or for the full context?
async_hooks.createHooks({
init() {
setImmediate(() => setImmediate(() => /* does this fire hooks */));
}
})
Sorry, something went wrong.
There was a problem hiding this comment.
What @AndreasMadsen asked ^^
It looks ok, but we'd need to be clear about what happens in the setImmediate() case.
Sorry, something went wrong.
There was a problem hiding this comment.
I think an fs.writeSync(1, ...) can be used in place of _rawDebug(). People will want to play around with hooks and print things while gainining an understanding, so since console.log() messes this up, perhaps an intro section is needed on this topic, describing the problem and a solution. It could show a safe "print" function using fs.writeSync as an example, and then that function could be used consistently throughout the other examples in the docs
Sorry, something went wrong.
There was a problem hiding this comment.
Sorry for the delay.
@AndreasMadsen @jasnell
would that just be in the current tick or for the full context?
That puts a swift end to my suggestion. I lapsed back to the previous implementation where all resources were tracked individually so each resource could omit firing its hooks.
I might suggest we add an API like async_hooks.log(). Telling the user to fs.writeSync(1) seems like something that enough people will need to do that we might as well codify it in the API.
Sorry, something went wrong.
There was a problem hiding this comment.
That puts a swift end to my suggestion. I lapsed back to the previous implementation where all resources were tracked individually so each resource could omit firing its hooks.
I had my suspicions :D
Something I have been thinking about is if two separate AsyncHook instances both call an async operation. Sure they can know about their own and prevent an internal recursion, but I don't think they can prevent a recursion jumping between each other.
Sorry, something went wrong.
| ##### `init(id, type, triggerId, resource)` | ||
|
|
||
| * `id` {number} a unique id for the async resource | ||
| * `type` {String} the type of the async resource |
There was a problem hiding this comment.
String, Undefined should also be lowercase ;)
Sorry, something went wrong.
|
Thanks for all the feedback .. tied up mostly this week, but will get to it next week and try to fix all problems that were pointed out. |
Sorry, something went wrong.
The state is checked when unwinding, and the application will crash if it isn't called in the correct order. It will also crash if the user forgets to run emitAfter() as the stack is unwinding. So abusing the API, or even being negligent, should be impossible.
To clarify, do you mean emitBefore() and emitAfter() in AsyncEvent, the "Sensitive Embedder API", or both? In regards to AsyncEvent I don't agree that only having a `makeCallback() type command is enough for users, as you said it is also easier to add than remove features. Though this API is experimental at the moment and I think it'd be good for users to be able to try out both. If for nothing else to work out kinks with the existing API. As for the "Sensitive Embedder API", I'm not too attached at keeping it around but it will make it much easier for users to incorporate into existing code; rather than needing to inherit from AsyncEvent. As I said above, it should be impossible for users to hack around this API. There are a plethora of checks in state management to make sure nothing of this sort happens. Which is why emitAfter() requires the asyncId to be passed. Adding a makeCallback() type function for this would be helpful as well. In short, if the "Sensitive Embedder API" is left intact then I would be alright removing emitBefore()/emitAfter() from AsyncEvent and replacing it with a sort of makeCallback() (though I don't love the name; possibly it should just be run() or something similar). I am alright adding a makeCallback() call to the embedder API. |
Sorry, something went wrong.
This makes sense. I am a little worried about if we got this right, but assuming so, replacing emitBefore and emitAfter with makeCallback() is not super important to me. But I would still like to know what the use case is.
I think there are plenty of cases where an experimental API couldn't be changed later. But more importantly, I think we will get much better feedback if we are lacking a feature, than if we have too many features. Nobody complains about too many features. For example, let's say that nobody uses the "Standalone JS API" (I think this is quite likely), then we will get no feedback and thus assume it is perfect. When we mark async_hooks as stable and it later turns out that it did have issues, then we are in trouble. I would much rather have a clear indication that every part of the async_hooks API is used.
I don't know what "Sensitive Embedder API" is, can we use the terminology from the EPS? There are two APIs:
I want to keep the "Standalone JS API" private for now. I don't think the "Standalone JS API" makes things easier, using: resource = new AsyncEvent();
resource.emitBefore();is at least as easy as: id = async_hooks.newId();
async_hooks.emitInit(id);
async_hooks.emitBefore(id); |
Sorry, something went wrong.
|
Addressed all obvious nits, asked for clarification on others. If anyone has a fix for either of those (i.e. @trevnorris) please just provide me with a commit and I'll cherry-pick it onto here. Alternatively anyone who wants to help LMK and I'll add them as a collab to the repo with this branch. |
Sorry, something went wrong.
There was a problem hiding this comment.
Mostly looks good to me, with a few grammar nits and suggestions.
Sorry, something went wrong.
|
|
||
| The `async-hooks` module provides an API to register callbacks tracking the | ||
| lifetime of asynchronous resources created inside a Node.js application. | ||
| It can be accessed using `require('async-hooks')`. |
There was a problem hiding this comment.
These should be underscores (i.e. `async_hooks`)
Sorry, something went wrong.
There was a problem hiding this comment.
Also, please move the require statement to it's own line for consistency with other docs. e.g.
It can be accessed using:
```js
const async_hooks = require('async_hooks');
Sorry, something went wrong.
| Handles are a reference to a system resource. Some resources are a simple | ||
| identifier. For example, file system handles are represented by a file | ||
| descriptor. Other handles are represented by libuv as a platform abstracted | ||
| struct, e.g. `uv_tcp_t`. Each handle can be continually reused to access and |
There was a problem hiding this comment.
Is anything described in the Some resources … uv_tcp_t bit actually visible to users? I don’t think so, so I’d just drop it.
Sorry, something went wrong.
| operate on the referenced resource. | ||
|
|
||
| Requests are short lived data structures created to accomplish one task. The | ||
| callback for a request should always and only ever fire one time. Which is when |
There was a problem hiding this comment.
one time. Which is → one time, which is
Sorry, something went wrong.
| Registers functions to be called for different lifetime events of each async | ||
| operation. | ||
| The callbacks `init()`/`before()`/`after()`/`destroy()` are registered via an | ||
| `AsyncHooks` instance and fire during the respective asynchronous events in the |
There was a problem hiding this comment.
AsyncHooks → AsyncHook
Sorry, something went wrong.
There was a problem hiding this comment.
fire -> "are called"?
Sorry, something went wrong.
| --> | ||
|
|
||
| * `callbacks` {Object} the callbacks to register | ||
|
|
There was a problem hiding this comment.
Can you add * Returns: {AsyncHook}?
Sorry, something went wrong.
|
|
||
| *Note:* Some resources depend on GC for cleanup. So if a reference is made to | ||
| the `resource` object passed to `init()` it's possible that `destroy()` is | ||
| never called. Causing a memory leak in the application. Of course if |
There was a problem hiding this comment.
never called. Causing a memory leak → never called, causing a memory leak
Sorry, something went wrong.
| ``` | ||
|
|
||
| It is important to note that the id returned fom `currentId()` is related to | ||
| execution timing. Not causality (which is covered by `triggerId()`). For |
There was a problem hiding this comment.
execution timing. Not causality → execution timing, not causality
Sorry, something went wrong.
|
|
||
| ```js | ||
| const server = net.createServer(function onConnection(conn) { | ||
| // Returns the id of the server, not of the new connection. Because the |
There was a problem hiding this comment.
the new connection. Because the → the new connection, because the
Sorry, something went wrong.
| ```js | ||
| const server = net.createServer(function onConnection(conn) { | ||
| // Returns the id of the server, not of the new connection. Because the | ||
| // on connection callback runs in the execution scope of the server's |
There was a problem hiding this comment.
I’d drop on, or spell it as onConnection to match the code
Sorry, something went wrong.
| If the user's callback throws an exception then `emitAfter()` will | ||
| automatically be called for all `id`'s on the stack if the error is handled by | ||
| a domain or `'uncaughtException'` handler. So there is no need to guard against | ||
| this. |
There was a problem hiding this comment.
I’d drop this last sentence fragment, but if you keep it, please join it with a comma
Sorry, something went wrong.
There was a problem hiding this comment.
If this addresses my question about lack of try/catch above, it probably needs a bit more text, not less, to describe what will happen.
Sorry, something went wrong.
|
|
||
| ## Terminology | ||
|
|
||
| An async resource represents either a _handle_ or a _request_. |
There was a problem hiding this comment.
s/_handle_/*handle*
s/_request_/*request*
Sorry, something went wrong.
| the assigned task has either completed or encountered an error. Requests are | ||
| used by handles to perform tasks such as accepting a new connection or | ||
| writing data to disk. | ||
|
|
There was a problem hiding this comment.
Something should be described around here about how the async_hooks API specifically relates to handles and requests
Sorry, something went wrong.
There was a problem hiding this comment.
It doesn't relate, in fact, it abstracts the two cases into the resource concept.
Sorry, something went wrong.
| ### Overview | ||
|
|
||
| Following is a simple overview of the public API. All of this API is explained in | ||
| more detail further down. |
There was a problem hiding this comment.
Just strike the second sentence here.
Sorry, something went wrong.
| destroy: 5 | ||
| ``` | ||
|
|
||
| First notice that `scope` and the value returned by `currentId()` are always |
There was a problem hiding this comment.
*Note*: As illustrated in the example, `currentId()` and `scope` each specify the value of the current execution context; which is delineated by calls to `before()` and `after()`.
Sorry, something went wrong.
| the same. That's because `currentId()` simply returns the value of the | ||
| current execution context; which is defined by `before()` and `after()` calls. | ||
|
|
||
| Now only using `scope` to graph resource allocation results in the following: |
There was a problem hiding this comment.
Remove the word Now
Sorry, something went wrong.
Can we keep it exposed but undocumented? Could even attach the functions to process.binding('async_wrap') (though that may be a bit of a hack). I would like users like yourself that really dig deep and experiment to have access and be able to try it out. And TBH I'm using it right now, but guess that shouldn't be a reason to keep it. :P |
Sorry, something went wrong.
Yes, that is all I want. I have quite a lot of concerns about abuse, recently I got some new about setting the triggerId for foreign resources. I'm sorry that I don't more time to put it into a coherent writing right now. |
Sorry, something went wrong.
There was a problem hiding this comment.
Lots of great info here on a very large and subtle API, good work.
Sorry, something went wrong.
| ```js | ||
| const async_hooks = require('async_hooks'); | ||
|
|
||
| // Return the id of the current execution context. |
There was a problem hiding this comment.
"ID"?
Sorry, something went wrong.
| function init(id, type, triggerId, resource) { } | ||
|
|
||
| // before() is called just before the resource's callback is called. It can be | ||
| // called 0-N times for handles (e.g. TCPWrap), and should be called exactly 1 |
There was a problem hiding this comment.
"should" -> "will", should implies a goal that may not always be achieved, I don't think that is the case here.
Sorry, something went wrong.
There was a problem hiding this comment.
Reason I said "should be" was to communicate at the time that since it's an experimental API there's a possibility that it may not be called, and if it isn't then there's a bug.
Sorry, something went wrong.
| function before(id) { } | ||
|
|
||
| // after() is called just after the resource's callback has finished. | ||
| // This hook will not be called if an uncaught exception occurred. |
There was a problem hiding this comment.
occured where? in the async_hook callback? In the user's callback function?
Sorry, something went wrong.
There was a problem hiding this comment.
Also: Is this actually true? I have tried to reproduce this, but it’s all a bit tricky to see through. If it is true that after won’t be called, I’d call that a bug? How would the hooks know that the execution context is exited in that case? @nodejs/diagnostics
Sorry, something went wrong.
There was a problem hiding this comment.
@addaleax that is left over documentation from a previous implementation. The after callbacks should always be called.
Sorry, something went wrong.
|
|
||
| // destroy() is called when an AsyncWrap instance is destroyed. In cases like | ||
| // HTTPParser where the resource is reused, or timers where the handle is only | ||
| // a JS object, destroy() will be triggered manually soon after after() has |
There was a problem hiding this comment.
JS->Javascript
Sorry, something went wrong.
|
|
||
| // destroy() is called when an AsyncWrap instance is destroyed. In cases like | ||
| // HTTPParser where the resource is reused, or timers where the handle is only | ||
| // a JS object, destroy() will be triggered manually soon after after() has |
There was a problem hiding this comment.
"manually", this sounds like an imp detail, that you are saying that the core C++ won't call destroy(), but it will be arranged to be called by node's javascript code, but is that relevant to the user?
Sorry, something went wrong.
There was a problem hiding this comment.
I think with HTTPParser, the handle might be gone before destroy() is actually invoked, so accessing the handle would cause problems. (So as to not call JS during a GC operation.)
Sorry, something went wrong.
| If the user's callback throws an exception then `emitAfter()` will | ||
| automatically be called for all `id`'s on the stack if the error is handled by | ||
| a domain or `'uncaughtException'` handler. So there is no need to guard against | ||
| this. |
There was a problem hiding this comment.
If this addresses my question about lack of try/catch above, it probably needs a bit more text, not less, to describe what will happen.
Sorry, something went wrong.
| * Returns {undefined} | ||
|
|
||
| Call all `destroy()` hooks. This should only ever be called once. An error will | ||
| be thrown if it is called more than once. This **must** be manually called. If |
There was a problem hiding this comment.
as opposed to automatically called? What callbacks are called automatically?
Sorry, something went wrong.
There was a problem hiding this comment.
Some of our resource objects are destroyed automatically by the gc. Since they are implemented in C++ they actually call the destroy callbacks appropriately.
Sorry, something went wrong.
|
|
||
| * Returns {number} the unique `id` assigned to the resource. | ||
|
|
||
| Useful when used with `triggerIdScope()`. |
There was a problem hiding this comment.
what is triggerIdScope()? should this be a link to something? is that a method on something? what?
Sorry, something went wrong.
|
|
||
| #### `asyncEvent.triggerId()` | ||
|
|
||
| * Returns {number} the same `triggerId` that is passed to `init()` hooks. |
There was a problem hiding this comment.
maybe "triggerId that was passed in the AsyncEvent constructor"?
Sorry, something went wrong.
| The following API can be used as an alternative to using `AsyncEvent()`, but it | ||
| is left to the embedder to manually track values needed for all emitted events | ||
| (e.g. `id` and `triggerId`). It mainly exists for use in Node.js core itself and | ||
| it is highly recommended that embedders instead use `AsyncEvent`. |
There was a problem hiding this comment.
why do we doc it? does it have a use-case? is there some rare thing it can be used for that the AsyncEvent API cannot be used for? Why don't we use the AsyncEvent API in node core? If its for efficiency, it seems worrying that we are suggesting user-land use an API that we won't use in node core.
Sorry, something went wrong.
There was a problem hiding this comment.
The low-level Embedder API is no longer included in the documentation.
Sorry, something went wrong.
There was a problem hiding this comment.
As requested here is the commit resolving the "Standalone JS Embedder API" discussion. Agreement with @trevnorris was that we will keep the API but not document it. Hopefully, we can get a better idea of what is needed from userland.
As a bonus, we renamed AsyncEvent to AsyncResource, here is a commit that updates the documentation:
PS: do we plan to add documentation for the C++ Embedder API in this PR?
Sorry, something went wrong.
I would say “no”, at least for the node::AsyncResource bits; If they are to receive explicit mention in our markdown docs, I’d say that should be the addon docs. Just as a data point, we don’t document the current MakeCallback API there either; and since it’s kinda customary for C/C++ API docs to be part of the headers, I think that’s okay. |
Sorry, something went wrong.
|
I'm going through all the reviews. I'm adding a ❤️ to everything I have seen, I will make an updated PR shortly. |
Sorry, something went wrong.
|
Thanks @AndreasMadsen should I close this one then? |
Sorry, something went wrong.
|
Sure, we can reopen if I suddenly don't respond. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Continuation of ongoing PR to add documentation for the async hooks feature which was merged recently.
The existing commits in the previous PR have been cherry-picked onto latest master.
I suggest to work out all nits and finally squash this into one proper commit.
Checklist
Affected core subsystem(s)