-
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
Context cancelled message shown when iteration context done #1283
Comments
@na-- I think it's not the interrupted but it occurs frequently in case of request context timeout or cancelled, I'm investigating the root cause. |
goja Interrupt only interrupts the JS vm, not the Go code which runs it. So there might be a race condition that an interrupted iteration was not probably detected, especially in case of heavy load. Detect that case and silent the interrupted error. Fixes #1283
goja Interrupt only interrupts the JS vm, not the Go code which runs it. So there might be a race condition that an interrupted iteration was not probably detected, especially in case of heavy load. Detect that case and silent the interrupted error. Fixes #1283
I spent some time on Friday digging through and thinking about the issues mentioned here and that #1284 tries to solve, and I think we're going to need some substantial refactoring... I think this is the wrong way to handle VU interruption and contexts with the new executors: The complicated handling of contexts is unnecessary... Each of the new executors handles contexts and VUs very deliberately and precisely, so we can just separate the context handling (i.e. spawning a goroutine to cancel/interrupt the goja runtime) and the actual running of the users' code. I think we should have separate concepts of an initialized VU (no context, can't do work, sitting in the VU buffer pool) that can transform itself (by giving it a I propose that we need to change the
Basically, I propose that the interfaces should look somewhat like this: // A Runner is a factory for VUs. It should precompute as much as possible upon
// creation (parse ASTs, load files into memory, etc.), so that spawning VUs
// becomes as fast as possible. The Runner doesn't actually *do* anything in
// itself, the ExecutionScheduler is responsible for wrapping and scheduling
// these VUs for execution.
//
// TODO: Rename this to something more obvious? This name made sense a very long
// time ago.
type Runner interface {
// Creates an Archive of the runner. There should be a corresponding NewFromArchive() function
// that will restore the runner from the archive.
MakeArchive() *Archive
// Spawns a new VU. It's fine to make this function rather heavy, if it means a performance
// improvement at runtime. Remember, this is called once per VU and normally only at the start
// of a test - RunOnce() may be called hundreds of thousands of times, and must be fast.
NewVU(ctx context.Context, out chan<- stats.SampleContainer, id int64) (InitializedVU, error)
// Runs pre-test setup, if applicable.
Setup(ctx context.Context, out chan<- stats.SampleContainer) error
// Returns json representation of the setup data if setup() is specified and run, nil otherwise
GetSetupData() []byte
// Saves the externally supplied setup data as json in the runner
SetSetupData([]byte)
// Runs post-test teardown, if applicable.
Teardown(ctx context.Context, out chan<- stats.SampleContainer) error
// Returns the default (root) Group.
GetDefaultGroup() *Group
// Get and set options. The initial value will be whatever the script specifies (for JS,
// `export let options = {}`); cmd/run.go will mix this in with CLI-, config- and env-provided
// values and write it back to the runner.
GetOptions() Options
SetOptions(opts Options) error
}
// VUActivationParams are supplied by each executor when it retrieves a VU from
// the buffer pool and activates it for use.
type VUActivationParams struct {
Ctx context.Context
Env map[string]string
Tags map[string]string
Exec null.String
DeactivateCallback func(InitializedVU)
}
// InitializedVUs are virtual users ready for work. They need to be activated
// (i.e. given a context) before they can actually be used. Activation also
// requires a callback function, which will be called when the supplied context
// is done. That way, VUs can be returned back to a pool and reused.
type InitializedVU interface {
// Fully activate the VU so it will be able to run code
Activate(*VUActivationParams) ActiveVU
}
type ActiveVU interface {
// Runs the VU once. The only way to interrupt the execution is to cancel
// the context given to InitializedVU.Activate()
RunOnce() error
} |
I'm wondering if it isn't a good idea to also tackle #1250 during the same refactoring (that is, if we end up doing something like what I suggested above), or if it's just my tendency to bundle semi-related things speaking? 😅 |
@na-- Your
Where will we store that context? (new field in VU struct?) |
I tried implementing this concept today, and there're many confusions:
For clarification, what I have done:
The code does compile, but seems that all the tests were broken. (The output error just ate all of my screen). |
No, we don't want to store this context. As you can see from this code, the idea was that VU initialization would also accept a context, since it actually takes some time and we might want to interrupt it: https://github.com/loadimpact/k6/blob/f6be35cfa006c56954d6821e0f94f4863f063e45/core/local/local.go#L134-L138 But the JS runner doesn't implement that yet... And while I'd prefer that we fix it, it's not strictly necessary to do so now, so I'd also be mostly OK if we remove the context from
Not sure I fully understand your question, but the whole idea is that whatever code (in our case, some executor) is calling
None of the tests should be broken functionally, since we're not changing any functionality with this refactoring, though of course some tests would require substantial edits because of the new
😕 😕
Not sure you have fully understood my suggested changes 😕 If you have any questions, please ask them instead of just directly changing the code without understanding the ideas behind the changes... This change specifically doesn't make sense to me: "Change every We're not just activating the VU before every |
How can we know if the iteration is done? https://github.com/loadimpact/k6/blob/f6be35cfa006c56954d6821e0f94f4863f063e45/js/runner.go#L483
I understand, but you pass the What I think,
Can you elaborate? The
IIRC, the only place need to change is https://github.com/loadimpact/k6/blob/f6be35cfa006c56954d6821e0f94f4863f063e45/lib/executor/helpers.go#L82 But I mean many tests create a VU by calling |
@na-- Please take a look at https://github.com/loadimpact/k6/tree/cuonglm/refactoring-runner to see how I try to apply your suggestion. Just go to |
A had a quick look at the changes in that branch and have a few notes:
Well, you haven't implemented the actual changes I proposed, you've just renamed a bunch of things and made a few small cosmetic alterations. if the tests had passed, then this would have meant that our tests are very bad... 😄 I'm not sure you understand the ideas and the reasons behind my proposed changes. If so, please ask questions directly about them, since I'm not sure the code you produced is helpful in this discussion...
The iteration is done when
I don't see how this is an issue, but I think there was some miscommunication here, I'll try to explain how I envision the workflow again. Basically, each
The second step above is why we don't need to worry about the things you mentioned - each The only issue should be the fact that the goroutine spawned by
I just meant that you obviously had to pass the
Far from it, as I explaining again above, we'd also have to change the executors' |
To give a more concrete example of how I envision the activation/deactivation of the VUs in the executors, the following code: https://github.com/loadimpact/k6/blob/f6be35cfa006c56954d6821e0f94f4863f063e45/lib/executor/per_vu_iterations.go#L181-L205 Should look somewhat like this when my proposed changes are implemented: //TODO: this might be better-off as a method, instead of as a lambda
handleVU := func(initializedVU lib.InitializedVU) {
// This is probably not strictly needed, but with it, we can return some
// VUs to the buffer sooner
ctx, cancel := context.WithCancel(maxDurationCtx)
defer cancel()
// TODO: some of this should probably be in a helper function, since
// it's going to be repeated in all executors...
vu := initializedVU.Activate(&lib.VUActivationParams{
Ctx: ctx,
DeactivateCallback: func() {
pvi.executionState.ModCurrentlyActiveVUsCount(-1)
pvi.executionState.ReturnVU(initializedVU)
},
})
pvi.executionState.ModCurrentlyActiveVUsCount(+1)
activeVUs.Add(1)
defer activeVUs.Done()
for i := int64(0); i < iterations; i++ {
select {
case <-regDurationDone:
return // don't make more iterations
default:
// continue looping
}
runIteration(maxDurationCtx, vu)
atomic.AddUint64(doneIters, 1)
}
}
for i := int64(0); i < numVUs; i++ {
initializedVU, err := pvi.executionState.GetPlannedVU(pvi.logger)
if err != nil {
cancel()
return err
}
go handleVU(initializedVU)
} |
@na-- Thanks, but I see a conflict here, in:
This |
😕 ...I'm still getting the feeling that there's some serious misunderstanding going on 😖
type ActiveVU interface {
RunOnce() error
} Please explain what conflict you see a bit more clearly... |
hmm I just realized that this refactoring would actually sort-of fix another minor k6 issue - #889 |
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
The complicated handling of context in RunOnce is not necessary. Each of the new executors handles contexts and VUs very deliberately and precisely. We can separate the context handling to interrupt goja runtime and the actual running user's code. This PR introduces two new interfaces, "InitializedVU" annd "ActiveVU". InitializedVU is what Runner.NewVU will return, and is stored in ExecutionState.vus buffer. Whenever a InitializedVU is pull from the buffer, caller can call InitializedVU.Activate with VUActivationParams argument. This will return an ActiveVU, which will actually run the user's code. The InitializedVU.Activate will spawn a goroutine to track the context for interrupting script execution, and calling the callback function and return the VU to the buffer. Fixes #889 Fixes #1283
Relevant goja issue: dop251/goja#120 edit: now closed by dop251/goja@0a0a0d8, so we can fix the issue on our side |
This cleans up how context was being handled for purposes of interruption, and introduces a VU activation method that also handles de-activation (i.e. returning the VU to the pool) via a callback passed during execution. See grafana#1283
This cleans up how context was being handled for purposes of interruption, and introduces a VU activation method that also handles de-activation (i.e. returning the VU to the pool) via a callback passed during execution. See grafana#1283
Hey @na--, could you take a look at my WIP branch for this and let me know if it matches what you had in mind? Most of the implementation is taken verbatim from your examples, so I'm hoping it does. :) The refactor fixes the I'm currently working on the tests and have a couple leftover issues I might need your help with, but that can wait for the PR. Just wanted to make sure I'm on the right track for now. |
This cleans up how context was being handled for purposes of interruption, and introduces a VU activation method that also handles de-activation (i.e. returning the VU to the pool) via a callback passed during execution. See #1283
This cleans up how context was being handled for purposes of interruption, and introduces a VU activation method that also handles de-activation (i.e. returning the VU to the pool) via a callback passed during execution. See #1283
This cleans up how context was being handled for purposes of interruption, and introduces a VU activation method that also handles de-activation (i.e. returning the VU to the pool) via a callback passed during execution. See #1283
This cleans up how context was being handled for purposes of interruption, and introduces a VU activation method that also handles de-activation (i.e. returning the VU to the pool) via a callback passed during execution. See #1283
This was fixed in #1368. |
The text was updated successfully, but these errors were encountered: