Documentation ¶
Overview ¶
Package eventloop implements an event loop to be used thought js and it's subpackages
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type EventLoop ¶
type EventLoop struct {
// contains filtered or unexported fields
}
EventLoop implements an event with handling of unhandled rejected promises.
A specific thing about this event loop is that it will wait to return not only until the queue is empty but until nothing is registered that it will run in the future. This is in contrast with more common behaviours where it only returns on a specific event/action or when the loop is empty. This is required as in k6 iterations (for which event loop will be primary used) are supposed to be independent and any work started in them needs to finish, but also they need to end when all the instructions are done. Additionally because of this on any error while the event loop will exit it's required to wait on the event loop to be empty before the execution can continue.
func New ¶
New returns a new event loop with a few helpers attached to it: - adding setTimeout javascript implementation - reporting (and aborting on) unhandled promise rejections
func (*EventLoop) RegisterCallback ¶
RegisterCallback signals to the event loop that you are going to do some asynchronous work off the main thread and that you may need to execute some code back on the main thread when you are done. So, once you call this method, the event loop will wait for you to finish and give it the callback it needs to run back on the main thread before it can end the whole current script iteration.
RegisterCallback() *must* be called from the main runtime thread, but its result enqueueCallback() is thread-safe and can be called from any goroutine. enqueueCallback() ensures that its callback parameter is added to the VU runtime's tasks queue, to be executed on the main runtime thread eventually, when the VU is done with the other tasks before it. Unless the whole event loop has been stopped, invoking enqueueCallback() will queue its argument and "wake up" the loop (if it was idle, but not stopped).
Keep in mind that once you call RegisterCallback(), you *must* also call enqueueCallback() exactly once, even if don't actually need to run any code on the main thread. If that's the case, you can pass an empty no-op callback to it, but you must call it! The event loop will wait for the enqueueCallback() invocation and the k6 iteration won't finish and will be stuck until the VU itself has been stopped (e.g. because the whole test or scenario has ended). Any error returned by any callback on the main thread will abort the current iteration and no further event loop callbacks will be executed in the same iteration.
A common pattern for async work is something like this:
func doAsyncWork(vu modules.VU) *goja.Promise { enqueueCallback := vu.RegisterCallback() p, resolve, reject := vu.Runtime().NewPromise() // Do the actual async work in a new independent goroutine, but make // sure that the Promise resolution is done on the main thread: go func() { // Also make sure to abort early if the context is cancelled, so // the VU is not stuck when the scenario ends or Ctrl+C is used: result, err := doTheActualAsyncWork(vu.Context()) enqueueCallback(func() error { if err != nil { reject(err) } else { resolve(result) } return nil // do not abort the iteration }) }() return p }
This ensures that the actual work happens asynchronously, while the Promise is immediately returned and the main thread resumes execution. It also ensures that the Promise resolution happens safely back on the main thread once the async work is done, as required by goja and all other JS runtimes.
TODO: rename to ReservePendingCallback or something more appropriate?
func (*EventLoop) Start ¶
Start will run the event loop until it's empty and there are no uninvoked registered callbacks or a queued function returns an error. The provided firstCallback will be the first thing executed. After Start returns the event loop can be reused as long as waitOnRegistered is called.
func (*EventLoop) WaitOnRegistered ¶
func (e *EventLoop) WaitOnRegistered()
WaitOnRegistered waits on all registered callbacks so we know nothing is still doing work.