-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an execution.Controller and a local no-op implementation
This doesn't change any existing k6 behavior (see how the tests were not touched), but it adds hooks for distributed execution later on.
- Loading branch information
Showing
10 changed files
with
181 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package execution | ||
|
||
// Controller implementations are used to control the k6 execution of a test or | ||
// test suite, either locally or in a distributed environment. | ||
type Controller interface { | ||
// GetOrCreateData requests the data chunk with the given ID, if it already | ||
// exists. If it doesn't (i.e. this was the first time this function was | ||
// called with that ID), the given callback is called and its result and | ||
// error are saved for the ID and returned for all other calls with it. | ||
// | ||
// This is an atomic and single-flight function, so any calls to it while the callback is | ||
// being executed the the same ID will wait for the first call to to finish | ||
// and receive its result. | ||
// | ||
// TODO: split apart into `Once()`, `SetData(), `GetData()` and implement | ||
// the GetOrCreateData() behavior in a helper like the ones below? | ||
GetOrCreateData(ID string, callback func() ([]byte, error)) ([]byte, error) | ||
|
||
// Signal is used to notify that the current instance has reached the given | ||
// event ID, or that it has had an error. | ||
Signal(eventID string, err error) error | ||
|
||
// Subscribe creates a listener for the specified event ID and returns a | ||
// callback that can wait until all other instances have reached it. | ||
Subscribe(eventID string) (wait func() error) | ||
} | ||
|
||
// SignalAndWait implements a rendezvous point / barrier, a way for all | ||
// instances to reach the same execution point and wait for each other, before | ||
// they all ~simultaneously continue with the execution. | ||
// | ||
// It subscribes for the given event ID, signals that the current instance has | ||
// reached it without an error, and then waits until all other instances have | ||
// reached it or until there is an error in one of them. | ||
func SignalAndWait(c Controller, eventID string) error { | ||
wait := c.Subscribe(eventID) | ||
|
||
if err := c.Signal(eventID, nil); err != nil { | ||
return err | ||
} | ||
return wait() | ||
} | ||
|
||
// SignalErrorOrWait is a helper method that either immediately signals the | ||
// given error and returns it, or it signals nominal completion and waits for | ||
// all other instances to do the same (or signal an error themselves). | ||
func SignalErrorOrWait(c Controller, eventID string, err error) error { | ||
if err != nil { | ||
_ = c.Signal(eventID, err) | ||
return err // return the same error we got | ||
} | ||
return SignalAndWait(c, eventID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Package local implements the execution.Controller interface for local | ||
// (single-machine) k6 execution. | ||
package local | ||
|
||
// Controller "controls" local tests. It doesn't actually do anything, it just | ||
// implements the execution.Controller interface with no-op operations. The | ||
// methods don't do anything because local tests have only a single instance. | ||
// | ||
// However, for test suites (https://github.com/grafana/k6/issues/1342) in the | ||
// future, we will probably need to actually implement some of these methods and | ||
// introduce simple synchronization primitives even for a single machine... | ||
type Controller struct{} | ||
|
||
// NewController creates a new local execution Controller. | ||
func NewController() *Controller { | ||
return &Controller{} | ||
} | ||
|
||
// GetOrCreateData immediately calls the given callback and returns its results. | ||
func (c *Controller) GetOrCreateData(_ string, callback func() ([]byte, error)) ([]byte, error) { | ||
return callback() | ||
} | ||
|
||
// Subscribe is a no-op, it doesn't actually wait for anything, because there is | ||
// nothing to wait on - we only have one instance in local tests. | ||
// | ||
// TODO: actually use waitgroups, since this may actually matter for test | ||
// suites, even for local test runs. That's because multiple tests might be | ||
// executed even by a single instance, and if we have complicated flows (e.g. | ||
// "test C is executed only after test A and test B finish"), the easiest way | ||
// would be for different tests in the suite to reuse this Controller API *both* | ||
// local and distributed runs. | ||
func (c *Controller) Subscribe(_ string) func() error { | ||
return func() error { | ||
return nil | ||
} | ||
} | ||
|
||
// Signal is a no-op, it doesn't actually do anything for local test runs. | ||
// | ||
// TODO: similar to Wait() above, this might actually be required for | ||
// complex/branching test suites, even during local non-distributed execution. | ||
func (c *Controller) Signal(_ string, _ error) error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.