-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
group
doesn't work with async calls well
#2728
Comments
group
doesn't work with promises group
doesn't work with async calls well
First and foremost, thanks for putting such a detailed analysis and explanation of the problem together, much appreciated 👍🏻 🙇🏻 I can see the issue clearly too now, and I kind of come to the same conclusion as you: there doesn't seem to be any elegant solution to this. This leads me to believe that any solution we pick would end up turning into some complicated legacy that we would likely regret. Disclaimer Thus here are a bunch of notes and questions that the issue raised for me. I don't expect those to bring solutions, but maybe they open the discussion in ways that helps us move towards an acceptable solution somehow:
Finally, I don't think you've mentioned if you had any preferences toward one or the other solution yet; if you do, I'd love to hear it. I hope this melting pot of questions, intuitions, and ideas 💡 turns out somehow useful, and maybe sparks new ideas and approaches. Regardless, I'll keep thinking about it and will come back with anything else that might pop up 🙇🏻 |
This is mostly why I am leaning to in practice ... deprecating
My other problem here is that in a lot of the cases I would argue group adds more or less no value. And people do seem to expect it to do stuff ... it doesn't. For example #880 and #921 were because people expected that group_duration will be just the time it takes to do the requests. As k6 gets more and more features, there will arguably be more and more stuff that users will expect are automatically added or not to different metrics. I would argue most of those need to be decided on a case by case basis by the user. This also remind me that ... currently An example group("my cool group", ()=> {
http.asyncRequest("GET", login_url).then((resp)=>{
return http.asyncRequest("POST", add_to_cart)
}).then((resp)=>{
return http.asyncRequest("GET", goto_cart)
}).then((resp)=>{
return http.asyncRequest("POST", pay_url)
})
})
sleep(10s)// or something else that takes 10s Should the above group_duration be 10s longer? Or not take that into consideration. How do we then measure what happens off the main thread (so not in the JS executin time) and do we need to do this for each operation? Some kind of "AddToGroupTime?" API maybe. Since grafana/k6-docs#210 I have been pretty sure most of the uses of On the other hand I have seen users use it nicely and I do agree there is value in it. I am just not certain that can't be done in some other way. Looking at the group + checks high coupling I feel like the correct approach here will be to make it possible to write better Like the Group is not just a metric it is this whole struct and all the things on it and it has a bunch of things directly related to Checks Lines 72 to 100 in 0b73cd6
I think I went enough on this point
The thing is that it doesn't happen "inside" it is asynchronous. The whole thing with async calls is that you call them and the program continues running and later at some point you will get your result. In our case we then do somethign with that result and the expectation (from purely visual reading of the code) is that this something will be in the same group.
So the issues is basically that due to callbacks (whether from promises or not) are not executed within the group, they act the same, instead of how users will likely expect. Which is IMO where we should go and just - let users know that I guess this means that we need a way to make some kind of similar functionality for async calls 🤷♂
This both needs to work for non promises as well and k6 does not get any notification that a promise was created inside a group. We can probably hack something around I guess this also again will mean that the
I am not certain I got what you are saying, but this likely needs to work with non promises as well. See Websocket API example it uses an event API with pure callbacks (that get more than once, so they are called "handlers"). But should if those are defined within a const socket = group("somegroup", () => {
return new WebSocket('ws://localhost:8080');
});
group("open group", () => {
// Connection opened
socket.addEventListener('open', (event) => {
socket.send('Hello Server!'); // what does this gets tagged with?
});
});
group("message group", () => {
// Listen for messages
socket.addEventListener('message', (event) => {
console.log('Message from server ', event.data);
});
});
The only thing that I can think of is that if And that likely can be a new API. But I am not really certain if this is going to be a thing we can either way - so maybe that will be more involved. Also, arguably it is blocked on goja having async functions which currently isn't the case.
This is also my feeling. For example if we "record" the group in RegisterCallback (or more likely the parent call on
I guess my proposal at this point is to heavily discourage usage of But I guess that means that with such an API anything in between will be part of the group. SO maybe even I guess it will be nice if we could see some kind of other API that does this in the wild ... |
A group provided with an async function will now: 1. Treat the whole time it take for the async function promise to finisher the duration of the group. 2. It will also use goja's AsyncContextTracker to make it so that the code using `await` within the group will still continue to be tagged with the group after `await` returns. 3. Instead of directly calling the async function it schedules it to be called the next time a promise job will work. The current AsyncContextTracker is only used for this and as such is directly changed in the `k6` module. In the future there likely will be API so that multiple modules can use it simultaneously, but that seems way too involved to be included in this change and also currently only `group` needs this. fixes #2848 #2728
A group provided with an async function will now: 1. Treat the whole time it take for the async function promise to finisher the duration of the group. 2. It will also use goja's AsyncContextTracker to make it so that the code using `await` within the group will still continue to be tagged with the group after `await` returns. 3. Instead of directly calling the async function it schedules it to be called the next time a promise job will work. The current AsyncContextTracker is only used for this and as such is directly changed in the `k6` module. In the future there likely will be API so that multiple modules can use it simultaneously, but that seems way too involved to be included in this change and also currently only `group` needs this. fixes #2848 #2728
edit: the below is no longer true see #2728 (comment) After a lot of internal discussions in which @sniku was instrumental it was decided that:
example with the API from #2825 group("something", async () => {
await asyncRequest("GET", url) // this will be tagged with the group "something"
await asyncRequest("GET", url) // so will this one
asyncRequest("GET", url).then((resp) => {
// do something with resp
asyncRequest("GET", url) // this will **NOT** be tagged with a group
})
asyncRequest("GET", url) // this will again be tagged with the group "something"
})```
Also #2863 represents another decision made later that `group` will not call `async` functions directly, but will be called the next time a Promise job will be executed. This makes it more alike other `async` taking APIs across the js ecosystem and was what most users expected |
The general inconsistencies between group as an idea and asynchronous code makes it not great fit and such we will try to discourage users to mix them. Updates #2728
After even more discussion it was decided to not support async functions in The magical solution of only supporting async/await has both proven to have:
It is also kind of hard to define what a group of request needs to be with async APIs unless you specify per each call. Given also the fact k6 has very low amount of actually async APIs at this point and the brand new async/await syntax the k6 OSS team thinks its better to explore other possibilities. For the next release cycle (it is too late for this one unfortunately) we will try to make the group and check API less magical. In practice making it possible for simple JS code to have the same UX as the current special API. This will also enable users and us to experiment more easily with possible APIs. |
The general inconsistencies between group as an idea and asynchronous code makes it not great fit and such we will try to discourage users to mix them. The same is done to check for consistency and as otherwise an async function will always give back true as it returns a promise which is cast to boolean. Updates #2728
The general inconsistencies between group as an idea and asynchronous code makes it not great fit and such we will try to discourage users to mix them. The same is done to check for consistency and as otherwise an async function will always give back true as it returns a promise which is cast to boolean. Updates #2728 Co-authored-by: na-- <n@andreev.sh>
The following script:
Will not emit "my counter" with "group=coolgroup".
To that the user will need to wrap the inner parts of the
then
in a group as inThis is because
group
is kind ofAs the
then
"callbacks" get called only after the stack is empty the wholegroup
code would have been executed, resetting the group back to the root name (which is empty). And when thethen
get's called it will have an empty group.The "workaround" works as it just calls
group
again.A more hacking but ... more useful workaround is
This ... likely has other problems.
Solutions:
I would argue this might be just showing that
group
is a bad abstraction and should be deprecated. It makes little sense in a lot of the cases.On the other hand people are using it (albeit sometimes wrongly) and likely will still want to use it once k6 has a lot more async APIs (which return promises).
Some of those can be fixed in the actual go code - it can get the
group
before returning the promise. Which in practice is needed as getting the group in a goroutine while js code is runnign si racy asgroup()
call can be made, changing it, while the goroutine gets it.This in no way helps though with later
.then
calls.So for a more real example
for all of those setting
group
will currently not work great.async
/await
support:While we might be able to fix some of this for case wher
group("name" , async function () {...})
through just doing some magic ingroup
- that will likely only work for theawait
usages, but not if you then ( 😉 ) call.then
or something.So it might work great for some of the "async" stuff but not for the other.
pure callbacks
If somebody has an API that just have callbacks ... having them support
group
will be on a case by case callback basis. They will need to make certain to get thegroup
from where thecallback
is defined and then when making it to set thegroup
... and then unset it.API changes
I guess it is possible for the
js/eventloop
to provide API to ...that, but that will likely be quite involved, but might be the easiest way forward.The text was updated successfully, but these errors were encountered: