Skip to content
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

feat(libooniengine): add support for running tasks #1112

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

DecFox
Copy link
Contributor

@DecFox DecFox commented Feb 22, 2023

Checklist

  • I have read the contribution guidelines
  • reference issue for this pull request: engine: add support for running tasks probe#2416
  • if you changed anything related to how experiments work and you need to reflect these changes in the ooni/spec repository, please link to the related ooni/spec pull request:
  • if you changed code inside an experiment, make sure you bump its version number

Description

This diff adds support for running tasks in libooniengine which are subject to interaction via the FFI consumer. (edited)

Copy link
Contributor

@bassosimone bassosimone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should temporarily stash part of the code we have in this pull request and just focus on adding support for running tasks, without additional complexity that may distract us.

I think the right next-step is to just flesh out how the Dart consumer will interact with tasks.

@@ -221,6 +221,63 @@ func NewSession(ctx context.Context, config SessionConfig) (*Session, error) {
return sess, nil
}

func NewSessionWithoutTunnel(ctx context.Context, config *SessionConfig) (*Session, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced we need this new constructor

return nil, errors.New("SoftwareVersion is empty")
}
if config.KVStore == nil {
config.KVStore = &kvstore.Memory{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should not mutate a config passed by pointer.

Comment on lines 74 to 75

// OONITask
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// OONITask

///
/// @return Zero on failure, nonzero on success. If the return value
/// is nonzero, a task is running. In such a case, the caller is
/// responsible to eventually dispose of the task using OONIEngineFreeMemory.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we can use OONIEngineFreeMemory to dispose of a task? I thought we should be having a dedicated API that removes the corresponding handle from the corresponding map.

///
/// @return A NULL pointer on failure, non-NULL otherwise. If the return
/// value is non-NULL, the caller takes ownership of the OONIMessage
/// pointer and MUST free it using OONIMessageFree when done using it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this say OONIEngineFreeMemory?

"github.com/ooni/probe-cli/v3/internal/model"
)

type LogLevel int32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot these be strings? I think it's easier for a consumer if these are strings.

func newTaskLogger(emitter taskMaybeEmitter) *taskLogger {
return &taskLogger{
emitter: emitter,
verbose: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor should also initialize verbose.

Comment on lines 30 to 31
err := json.Unmarshal([]byte(C.GoString(req)), out)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
err := json.Unmarshal([]byte(C.GoString(req)), out)
if err != nil {
if err := json.Unmarshal([]byte(C.GoString(req)), out); err != nil {

waitForNextEvent(timeout time.Duration) *response

// isDone implements OONITaskIsDone.
isDone() bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are you implementing isDone?


// runTicker emits a ticker event every second. It is a subtask
// associated with all tasks.
func runTicker(ctx context.Context, close chan any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we have a timeout associated with each WaitForNextEvent, we don't need a ticker.

@DecFox DecFox changed the title feat(libooniengine): introduce the ability to bootstrap and close sessions feat(libooniengine): add support for running tasks Mar 20, 2023
@DecFox
Copy link
Contributor Author

DecFox commented Mar 21, 2023

Thank you for the feedback! I have made the requested changes.

@DecFox DecFox marked this pull request as ready for review March 25, 2023 06:56
@DecFox DecFox requested a review from hellais as a code owner March 25, 2023 06:56
Copy link
Contributor

@bassosimone bassosimone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did another pass and provided lots of suggestions!

Thanks for moving this forward! 🙌

Comment on lines 13 to 14
/// engine that performs a background operation and emits meaningful
/// events such as, for example, the results of measurements.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description should be updated to mention that we emit interim outputs such as logs and progress as well as the result of the operation.

void OONIENgineFreeMemory(void *ptr);
void OONIEngineFreeMemory(void *ptr);

/// OONIEngineCall starts a new OONITask using the given [req].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I wrote the prototype, I used [req] but it seems the right syntax when using Doxygen is @p req. Do you mind updating the documentation in this file?

/// value is non-NULL, the caller takes ownership of the char pointer
/// and MUST free it using OONIEngineFreeMemory when done using it.
///
/// This function will return an empty string:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused by this documentation. It strikes me as bad to return NULL or an empty string or a valid string and it would be better for the return value to be either NULL or a valid JSON string. I think we should double check the implementation to be sure it actually does what is documented or whether the documentation was just inconsistent with the actual implementation. (TODO(@bassosimone))

/// value is non-NULL, the caller takes ownership of the char pointer
/// and MUST free it using OONIEngineFreeMemory when done using it.
///
/// This function will return an empty string:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need a timeout for this function call. It should be called after the task is done, and there should be a result once this happens. So, we can simplify the life of the caller by removing support for the timeout.

/// checking whether this function has returned an empty string.
char *OONIEngineTaskGetResult(OONITask task, int32_t timeout);

/// OONIEngineTaskIsDone returns whether the task identified by [taskID] is done. A taks is
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// OONIEngineTaskIsDone returns whether the task identified by [taskID] is done. A taks is
/// OONIEngineTaskIsDone returns whether the task identified by [taskID] is done. A task is

r := reflect.ValueOf(req)
t := r.Type()
for i := 0; i < r.NumField(); i++ {
if !r.Field(i).IsNil() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, the Request contains structs and not pointers. Because of this, the check that would actually work here would be IsZero. In any case, I suggested pointers, so IsNil would be okay.

That said, I am not sure we should implement task dispatching using reflection like this.

result: make(chan *Response, 1),
stopped: make(chan any),
}
go tp.main(ctx, name, req)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're dispatching by name, we should probably add a Name to the Request struct. If we want to dispatch by pointer, otherwise, we can avoid passing name field here and only check for the req fields.

return nil // timeout while blocking for read
case ev := <-tp.result:
return ev // block for read till we receive a result
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I mentioned when discussing the C API, this can just be return <-tp.result

Comment on lines 119 to 122
for !tp.IsDone() {
const blockForever = -1
_ = tp.WaitForNextEvent(blockForever)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if draining the channel is actually needed. IIUC, we always emit in nonblocking mode.

runner := taskRegistry[name]
if runner == nil {
log.Printf("OONITaskStart: unknown task name: %s", name)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot just return here. We now always need to tp.result <- resp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants