[LEAF] The usage of TLS
Hi Everyone, I have a remark regarding the LEAF library. But first, I would like to thank Emil for writing and sharing this library. Handling failures is an important aspect of writing programs that does not have an ultimate answer in C++, and is still a subject of exploration. LEAF seems to confirm this. I would also like to thank Michael for managing this upcoming review and for making the announcement ahead of time. A ten-day review period is usually too short (even if later extended), and the early announcement gives the opportunity to devote more time into reviewing a library and interact with the author. The following is not a review, but I wanted to highlight only one aspect of the library. Namely, the usage of thread-local storage. There is a number of reasons people do not want to or cannot use the C++ exception handling mechanism. One of them is that the implementation of exception handling requires the usage of TLS, and TLS is not implementable on some platforms, like GPUs. If Nvidia's compiler does not implement C++ exception handling mechanism, it is not because it would be slow, but because it would require TLS support. From this perspective, if I cannot use C++ exceptions because of TLS and I have to use something else, then another TLS-based library is not really an alternative. In this case it does not matter that it is only IDs that are stored in TLS. The sole fact of even employing TLS is a deal breaker. Of course, LEAF does not have to satisfy all the people that cannot use C++ exceptions. It can service the environments where TLS is affordable. But the usage or non-usage of TLS is an important factor in making a decision what tool/framework/library to use for handling failures, and because of this it deserves to be mentioned in the beginning of the documentation, so that potential clients can quickly make a decision if this library is for them. If your concern is that people would be incorrectly led to think that the entire error objects are stored in TLS, you can also say explicitly up front that it is only about int-size IDs. Otherwise, people will be mislead anyway. A person will ask, "how is it possible that this library does not return the entire error object in leaf::result<> and it does not take the pointer to the allocated object as function argument? Is it using TLS? Let's grep for `thread_local`. Positive: it must be using TLS for storing error objects." For a similar reason, I find the comparison with Boost.Outcome, at the end of documentation, unfair. If the goal of this comparison is to help people decide which library is best suited for them, LEAF or Outcome, the usage or non-usage of TLS is an important factor. Another thing is that I cannot see how a TLS-based implementation can work with C++20 coroutines. In C++ 20 the following piece of code { A(); co_await X(); B(); } means that when the call to B() will be potentially "packaged" as a task and scheduled to be executed possibly on a different thread. It means that B() will be executed on a different thread than A(), which means that any attempt to communicate between A() and B() using TLS will not be reliable (e.g. if A() is in fact a call to leaf::preload()). This is the reason why we still cannot implement the feature known as "on scope failure" (like destructor call, but only when we are leaving the scope due to exception) in C++ even though we have `std::uncaught_exceptions()` (which uses TLS under the hood). This is why a proposal like P0052 ( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0052r10.pdf) didn't make it to C++20. Now, LEAF may have a solution to this problem, but I cannot figure it out from the documentation. So, I do not know if it is safe to use LEAF with coroutines in multi-threaded environment. I would really want this to be clarified in the docs before the library review starts. Regards, &rzej;
Thanks for the interest. On Sun, May 3, 2020 at 4:15 PM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
It can service the environments where TLS is affordable.
You're assuming that the use of TLS pointers to error objects in LEAF, moving error objects directly to the scope where they are needed, bypassing return values and stack frames, is less efficient than transporting them in return values. Depending on your platform and your error object types, it could go either way. Separately, LEAF can transport any number of error objects of arbitrary types without dynamic allocations. If you need to do this with a conventional result<T> implementation, you'll be allocating memory. That may be as much of a showstopper as unavailability of TLS could be for LEAF. For a similar reason, I find the comparison with Boost.Outcome, at the end
of documentation, unfair. If the goal of this comparison is to help people decide which library is best suited for them, LEAF or Outcome, the usage or non-usage of TLS is an important factor.
It's important but not in terms of efficiency, only in terms of availability and suitability. LEAF is not suitable for coroutines or GPUs (though GPU code doesn't need much in terms of error handling anyway). LEAF works well with async code based on threads, see: https://github.com/zajo/leaf/blob/master/examples/capture_in_result.cpp?ts=4 (without exceptions) https://github.com/zajo/leaf/blob/master/examples/capture_in_exception.cpp?t... (using exceptions)
pon., 4 maj 2020 o 02:41 Emil Dotchevski via Boost
Thanks for the interest.
On Sun, May 3, 2020 at 4:15 PM Andrzej Krzemienski via Boost < boost@lists.boost.org> wrote:
It can service the environments where TLS is affordable.
You're assuming that the use of TLS pointers to error objects in LEAF, moving error objects directly to the scope where they are needed, bypassing return values and stack frames, is less efficient than transporting them in return values. Depending on your platform and your error object types, it could go either way.
I am sorry. It was a bad choice of words on my side. I did not mean to imply that the usage of TLS would make the library go slower compared to alternatives. What I was trying to say is that on some platforms LEAF would not compile because of the missing TLS support, and that I would only discover it after having invested a significant amount of time, because the documentation does not declare it up front. These are the guidelines that I learned from Robert Ramey's talk on documenting a library (https://www.youtube.com/watch?v=ACeNgqBKL7E). As a potential user I need to learn in no more than 5 minutes: what the library does, how I will use it (LEAF documentation does it perfectly), and what are the requirements. For the last point I would expect to see: "your platform needs to support TLS, and your program cannot use coroutines in asynchronous manner." IOW: high-level design trade-offs should be documented up front.
Separately, LEAF can transport any number of error objects of arbitrary types without dynamic allocations. If you need to do this with a conventional result<T> implementation, you'll be allocating memory. That may be as much of a showstopper as unavailability of TLS could be for LEAF.
I acknowledge this. And I think this part is nicely outlined in the documentation. My critique was not about the trade-offs that the library does. It was about not documenting it clearly. As a side note, the choice of providing efficient stack storage for arbitrary number and kind of error objects does not necessarily require the usage of STL. An alternative described in https://www.research.ed.ac.uk/portal/files/78829292/low_cost_deterministic_C... creates a similar stack storage, but instead passes a pointer to it to every function that needs to report a failure using this mechanism. But the authors are implementing it as a compiler extension, and this choice to pass the pointer to the storage to all function may not be suitable for a library implementation. So the trade-off here is: TLS in order to preserve the normal set and type of function inputs.
of documentation, unfair. If the goal of this comparison is to help
For a similar reason, I find the comparison with Boost.Outcome, at the end people
decide which library is best suited for them, LEAF or Outcome, the usage or non-usage of TLS is an important factor.
It's important but not in terms of efficiency, only in terms of availability and suitability. LEAF is not suitable for coroutines or GPUs (though GPU code doesn't need much in terms of error handling anyway).
Regarding platforms that do not support TLS, I will learn about it the hard way when I try to compile the library, which is enough to avoid any bugs. However, not being suitable for coroutines is more serious, because the library will compile and maybe even execute as expected in simple test cases. And it may give false impression that this just works. I would recommend putting a visible note in the documentation saying that the library is using TLS and therefore is not suitable for programs that use coroutines for asynchronous calls.
LEAF works well with async code based on threads, see:
https://github.com/zajo/leaf/blob/master/examples/capture_in_result.cpp?ts=4 (without exceptions)
https://github.com/zajo/leaf/blob/master/examples/capture_in_exception.cpp?t... (using exceptions)
Acknowledged. Regards, &rzej;
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
On 4. May 2020, at 10:29, Andrzej Krzemienski via Boost
wrote: These are the guidelines that I learned from Robert Ramey's talk on documenting a library (https://www.youtube.com/watch?v=ACeNgqBKL7E). As a potential user I need to learn in no more than 5 minutes: what the library does, how I will use it (LEAF documentation does it perfectly), and what are the requirements. For the last point I would expect to see: "your platform needs to support TLS, and your program cannot use coroutines in asynchronous manner." IOW: high-level design trade-offs should be documented up front.
On that note, we have a documentation guideline which says exactly that: https://www.boost.org/development/requirements.html#Documentation "General introduction to the library. The introduction particularly needs to include: A very high-level overview of what the library is good for, and perhaps what it isn't good for, understandable even by those with no prior knowledge of the problem domain." Best regards, Hans
On 4/05/2020 20:29, Andrzej Krzemienski wrote:
These are the guidelines that I learned from Robert Ramey's talk on documenting a library (https://www.youtube.com/watch?v=ACeNgqBKL7E). As a potential user I need to learn in no more than 5 minutes: what the library does, how I will use it (LEAF documentation does it perfectly), and what are the requirements. For the last point I would expect to see: "your platform needs to support TLS, and your program cannot use coroutines in asynchronous manner." IOW: high-level design trade-offs should be documented up front.
Coroutines can be used asynchronously without using threads. You just need to be careful to schedule them on an executor that is backed by only one thread, rather than a threadpool. In fact at least all the times that I have personally used coroutines, it's been in this manner -- when you're just managing multiple parallel tasks with a known-external delay (eg. slow network I/O), you don't really need a threadpool.
wt., 5 maj 2020 o 04:39 Gavin Lambert via Boost
These are the guidelines that I learned from Robert Ramey's talk on documenting a library (https://www.youtube.com/watch?v=ACeNgqBKL7E). As a potential user I need to learn in no more than 5 minutes: what the
On 4/05/2020 20:29, Andrzej Krzemienski wrote: library
does, how I will use it (LEAF documentation does it perfectly), and what are the requirements. For the last point I would expect to see: "your platform needs to support TLS, and your program cannot use coroutines in asynchronous manner." IOW: high-level design trade-offs should be documented up front.
Coroutines can be used asynchronously without using threads. You just need to be careful to schedule them on an executor that is backed by only one thread, rather than a threadpool.
In fact at least all the times that I have personally used coroutines, it's been in this manner -- when you're just managing multiple parallel tasks with a known-external delay (eg. slow network I/O), you don't really need a threadpool.
You are correct. Not every asynchronous use of coroutines is broken. Only those that can potentially execute the continuation part on a different thread. Regards, &rzej;
On 04/05/2020 01:40, Emil Dotchevski via Boost wrote:
LEAF is not suitable for coroutines
I cannot see any good reason why LEAF could not transport the current tree of LEAF state within a C++ Coroutine across kernel threads. Rough implementation: 1. Traverse the current LEAF state in the current C++ Coroutine. 2. Suspend the current C++ coroutine, resume an internal checking C++ Coroutine. 3. Traverse the current LEAF state from within the internal checking C++ Coroutine. 4. Compare this LEAF state with the previous one. If some of the LEAF state "escapes" the previous Coroutine, resume the original C++ Coroutine and report failure, as this LEAF state cannot be transported between kernel threads. 5. Otherwise transporting LEAF state with the current C++ coroutine ought to be as simple as restamping the head TLS pointer to the current LEAF state when one resumes Coroutine execution in another kernel thread (i.e. squirrel away the head TLS pointer into the C++ coroutine frame, restore it upon resumption) 6. For convenience, wrap up all of the above into C++ coroutine awaitables which self-specialise when they see a T which is a LEAF result e.g. boost::leaf::awaitables::lazy<T>. You may find inspiration in the implementation code for outcome::awaitables::lazy<T>, as Outcome provides Outcome-specialising awaitable types. As an aside, Outcome did not support C++ Coroutines originally as I did not expect much demand for such support. I assumed that given the poor QoI of C++ Coroutines in all the major implementations nobody would deploy production software using them. It turns out I was wrong on that. Outcome + C++ Coroutines is surprisingly popular. And even I have ended up using it personally in test code (I still think Coroutines are not production ready). I would expect that if LEAF is accepted, you will see the same. Niall
The mechanism in LEAF for dealing with this sort of thing is to manually
activate/deactivate a leaf::context<>.
The general architecture of LEAF is this: there are one or more
leaf::context
On 04/05/2020 01:40, Emil Dotchevski via Boost wrote:
LEAF is not suitable for coroutines
I cannot see any good reason why LEAF could not transport the current tree of LEAF state within a C++ Coroutine across kernel threads.
Rough implementation:
1. Traverse the current LEAF state in the current C++ Coroutine.
2. Suspend the current C++ coroutine, resume an internal checking C++ Coroutine.
3. Traverse the current LEAF state from within the internal checking C++ Coroutine.
4. Compare this LEAF state with the previous one. If some of the LEAF state "escapes" the previous Coroutine, resume the original C++ Coroutine and report failure, as this LEAF state cannot be transported between kernel threads.
5. Otherwise transporting LEAF state with the current C++ coroutine ought to be as simple as restamping the head TLS pointer to the current LEAF state when one resumes Coroutine execution in another kernel thread (i.e. squirrel away the head TLS pointer into the C++ coroutine frame, restore it upon resumption)
6. For convenience, wrap up all of the above into C++ coroutine awaitables which self-specialise when they see a T which is a LEAF result e.g. boost::leaf::awaitables::lazy<T>. You may find inspiration in the implementation code for outcome::awaitables::lazy<T>, as Outcome provides Outcome-specialising awaitable types.
As an aside, Outcome did not support C++ Coroutines originally as I did not expect much demand for such support. I assumed that given the poor QoI of C++ Coroutines in all the major implementations nobody would deploy production software using them.
It turns out I was wrong on that. Outcome + C++ Coroutines is surprisingly popular. And even I have ended up using it personally in test code (I still think Coroutines are not production ready).
I would expect that if LEAF is accepted, you will see the same.
Niall
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (5)
-
Andrzej Krzemienski
-
Emil Dotchevski
-
Gavin Lambert
-
Hans Dembinski
-
Niall Douglas