eventloop

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 31, 2023 License: AGPL-3.0 Imports: 4 Imported by: 0

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

func New(vu modules.VU) *EventLoop

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

func (e *EventLoop) RegisterCallback() (enqueueCallback func(func() error))

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

func (e *EventLoop) Start(firstCallback func() error) error

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL