3rdparty/eventuals

Explain what `Closure` is

Opened this issue · 6 comments

I found https://github.com/reboot-dev/respect/pull/572 almost impossible to review because there is no documentation about what an ::eventuals::Closure is. How does it differ from a ::eventuals::Then? When do I use it?

Even a simple set of comments in closure.h explaining why it exists would have been sufficient for me to work with it, but alas, there's no documentation of any kind, not even comments. :(

I suggest we add "what does this do, why does this exist" comments at the top of all of our files in eventuals/ - but this issue tracks the now-known-to-have-caused-pain closure.h.

In essence, Closure does exactly what capture lists in lambdas do. It owns some variables that you would like to use in the eventuals "enclosed" by it. Please see test/filesystem.cc for examples.

I agree with the need for the comments or documentation. I constantly am baffled by some of the stuff that we have, especially when it's about schedulers 😄

In essence, Closure does exactly what capture lists in lambdas do. It owns some variables that you would like to use in the eventuals "enclosed" by it. Please see test/filesystem.cc for examples.

... But Closure just takes a lambda, no additional variables. How does Closure capture anything? How does it make things available to "enclosed" eventuals? What does it even mean for an eventual to be "enclosed" by it? Why doesn't Then do the same thing with the identical signature?

(Just to be clear, I appreciate an explanation here, AND we should still add an explanation to the file! 😃 )

I just spent 30 minutes staring at closure.h (and comparing it to then.h), and I currently don't feel like I'm capable of documenting what this does or how it does it :/

One thing that looks like a key difference between the two:
Then passes the result obtained from invoking f_(...) into k_.Start(), after which that result is forgotten:

eventuals/eventuals/then.h

Lines 126 to 133 in 1a31f7c

void Start(Args&&... args) {
if constexpr (std::is_void_v<std::invoke_result_t<F_, Args...>>) {
f_(std::forward<Args>(args)...);
k_.Start();
} else {
k_.Start(f_(std::forward<Args>(args)...));
}
}

In contrast, Closure stores that result in a member optional field that persists until the eventual is destroyed:

continuation_.emplace(f_().template k<Arg_>(std::move(k_)));

But stuff I don't understand:

  • What are f_ and k_?
    • not just their meanings, but also their types (why does Then pass Start's args to itsf_ invocation, whereas Closure passes its k_ to its f_ invocation, and then it passes Start's args to a Start method called on the result? What arguments do each functor type expect?)
    • what's the return type of f_? What does storing it do?
    • is f_ just the passed in thing, e.g. the lambda? If so, why does storing its return value make sense, and why does it affect capture lifetime?
  • What arguments are passed to Start?
  • Is this comment about Closure::k_'s lifetime wrong or misleading (since k_ is move()'d into continuation_, meaning that continuation_'s lifetime is just as important as k_'s?)
  • How long do lambda captures last for?

Tentatively assigning to @benh who might be most able to help clear up our collective confusion here!

I've thrown some synchronous time on the calendar for the 3 of us to discuss this.

@benh is going to come up with an example to clarify this