I was assuming a fixed size, so that there would never be any need to allocate any such memory within a transaction.
try { Foo(1); }
catch (exception& e1) {
try { Foo(2); }
catch (exception& e2) { /* handle e2 */ }
throw; // rethrow e1
}
I don't see how we can expect the user to manage the memory.
Nothing changes until the point at which an exception is going to escape from a to-be-canceled exception. At the point of the throw (rethrow of e1), if e1 is not of a type that is safe to throw unmodified, then at that point, the programmer would write whatever information s/he deems necessary into the single, fixed-sized, special area, and then throw an exception that is safe to throw (perhaps that indicates that there is information in the special area and/or indicating where that special area is, depending on other details).
In a Haswell-like context, writing to cancel_immune_state could presumably abort and switch to STM mode. With the intended usage model, I think that has reasonable performance characteristics, and allows an ambitious programmer to make use of Haswell (or similar) explicit aborts without always switching to STM mode.
It seems to me that even if we just included (1) and (2) for now, this would give us some reading as to whether we should provide more. If nobody complains that this is really clumsy, and the library to factor out the boilerplate doesn't materialize, we're done.
Opinions?
template<typename callable> class UndoerImpl
{
callable undoer;
bool _dismissed;
#if !defined(_MSC_VER) || _MSC_VER>1700
UndoerImpl() = delete;
UndoerImpl(const UndoerImpl &) = delete;
UndoerImpl &operator=(const UndoerImpl &) = delete;
#else
UndoerImpl();
UndoerImpl(const UndoerImpl &);
UndoerImpl &operator=(const UndoerImpl &);
#endif
explicit UndoerImpl(callable &&c) : undoer(std::move(c)), _dismissed(false) { }
void int_trigger() { if(!_dismissed && !is_nullptr(undoer)) { undoer(); _dismissed=true; } }
public:
UndoerImpl(UndoerImpl &&o) : undoer(std::move(o.undoer)), _dismissed(o._dismissed) { o._dismissed=true; }
UndoerImpl &operator=(UndoerImpl &&o) { int_trigger(); undoer=std::move(o.undoer); _dismissed=o._dismissed; o._dismissed=true; return *this; }
template<typename _callable> friend UndoerImpl<_callable> Undoer(_callable c);
~UndoerImpl() { int_trigger(); }
//! Returns if the Undoer is dismissed
bool dismissed() const { return _dismissed; }
//! Dismisses the Undoer
void dismiss(bool d=true) { _dismissed=d; }
//! Undismisses the Undoer
void undismiss(bool d=true) { _dismissed=!d; }
};
/*! \brief Alexandrescu style rollbacks, a la C++ 11.
Example of usage:
\code
auto resetpos=Undoer([&s]() { s.seekg(0, std::ios::beg); });
...
resetpos.dismiss();
\endcode
*/
template<typename callable> inline UndoerImpl<callable> Undoer(callable c)
{
//static_assert(!std::is_function<callable>::value && !std::is_member_function_pointer<callable>::value && !std::is_member_object_pointer<callable>::value && !has_call_operator<callable>::value, "Undoer applied to a type not providing a call operator");
auto foo=UndoerImpl<callable>(std::move(c));
return foo;
}
We specify a set of exception types that are allowed to escape from a cancel-on-escape transaction (cancelling the transaction as it escapes). These exceptions are propagated unchanged beyond the transaction. Other exceptions that attempt to escape a cancel-on-escape transaction result in termination. I propose the following "cancel-friendly" exception types:
exceptions "of integral or enumerated type"
bad_alloc
The former were allowed to be thrown by the cancel-and-throw statement in the draft specification. The latter is a case we think may be common and important, and I don't think it causes any problems (but please point out any problems I may have overlooked). Are there any others we should add to this list?
We've probably used the word "minimal" somewhat inadvisedly here. You
are right that the "small and simple" proposal Victor has made is not
technical "minimal".
This means they
can yell at us, show us their use cases, etc., and we can get some
decent feedback about how people want to use these features.
If we simply shut them down and say they can't do anything of the sort,
we won't get people trying it at all, so we won't learn what they'd like
to do and what they need in order to achieve it.
Regarding HTM, while I definitely understand where you're coming from
with "I only want it when it can exploit HTM", it has been all but
forbidden to even discuss HTM in this context until quite recently, and
we are certainly aiming to specify features that can be implemented
without HTM. Even though the HTM landscape has changed considerably and
looks promising, I think we should continue to assume the features we
propose should be effectively implementable without HTM, even though
it's good that we're now at least considering an HTM-enabled future in
these discussions.
In short, I agree with Michael Scott in that a library approach seems unreasonable for TM. Like the Rochester group, we explored supporting TM in a library and published a few papers about it, such as LCSD'07 and BoostCon'10:While our early work (LCSD'07) proposed that such an approach might be feasible, when we began to work on more detailed language integration (BoostCon'10), we realized that while we could handle some of the necessary issues to make transactions "work" in C++, the programming complexity seemed to explode. Things like handling a return in the middle of a transaction seemed unreasonably complex and could be easily missed by the average programmer.
> Think this an abtuse example? Okay, what about STL implementation
> compatibility? We have three major STL implementations in the wild,
> and ever increasing headaches from library A which will only work with
> STL A
That is an API issue, unless you're thinking about compiled code here.
> I probably didn't make sense in my earlier post, but STM is just
> another longjmp() style ABI breakage issue as I pointed out.
I disagree. Aside from that I can't see any breakage when a platform
sticks to the ABIs it uses, any TM ABI being used by compiled code is
just part of all the other ABIs being relied on (e.g., calling
conventions, exception handling, ...). Also note that even if you use
an HTM to implement transactions, there will still be an ABI that
consists of more than just using the HTM parts of the ISA of the
particular architecture.
> What I'm saying is that when memory transactions abort it has a very
> similar effect on C++'s execution state as longjmp().
No. In particular, there's no guarantee of all-or-nothing with longjmp.
An abort+restart of a transaction is invisible to the program (as it
should be). Cancellation is visible, but in the sense that something
else gets executed *instead of* the transaction.
On Wed, 2013-04-10 at 08:00 -0700, Niall Douglas wrote: > On Tuesday, April 9, 2013 11:29:03 AM UTC-4, Torvald Riegel wrote: > > Think this an abtuse example? Okay, what about STL > implementation > > compatibility? We have three major STL implementations in > the wild, > > and ever increasing headaches from library A which will only > work with > > STL A > > That is an API issue, unless you're thinking about compiled > code here. > > > Ah, but the STL is a very special case. It's the only API specifically > endorsed by the standard. That's true, and stdlib implementations should really be compatible with what the standard requires. > > Let me put this another way: you'd reasonably expect to be able to > swap one libc for another No, not in the general case, unless they guarantee to use the same ABI. > , or combine a binary built against one version of a libc with a > different version. Symbol versioning can help here, but that's more a way to make sure that a library can support more than one version of an interface. > You currently can't expect that with STL implementations: you > generally need to recompile. In general, that's certainly the case; but if the STL implementations you have in mind pay attention to using a stable ABI, you shouldn't have to recompile. > > Which in my mind is an ABI breakage, albeit one mostly language > introduced. > > > You're right this is off-topic, so I'll stop here. My main point is > that solving C++ memory transactions in isolation is the wrong > approach IMHO. I can't speak for other implementors, but for GCC, we did pay attention to having the TM implementation work well with all the other parts. Note that this is in most parts orthogonal to whether we provide transactions as a language or library feature. > You need to be thinking in terms of "what is the major breakage in C > and C++ interop of which memory transactions is but one of many" I don't see how memory transactions would break in a mixed C/C++ program. Exceptions can be as difficult as without transactions, so that's not inherent to the transactions. Perhaps you could look at the ABI and point out the issues you see in detail? > and now you're asking a much better question. Hence me pointing out > longjmp(). There are differences to longjmp, in particular that you can't just jump some place else on abort/cancellation/..., but to exactly one of the enclosing transactions. If you think about how to implement TM in compilers, I think you will see that the toolchain has all the code under control and analyzed. > > > I probably didn't make sense in my earlier post, but STM is > just > > another longjmp() style ABI breakage issue as I pointed > out. > > I disagree. Aside from that I can't see any breakage when a > platform > sticks to the ABIs it uses, any TM ABI being used by compiled > code is > just part of all the other ABIs being relied on (e.g., > calling > conventions, exception handling, ...). Also note that even if > you use > an HTM to implement transactions, there will still be an ABI > that > consists of more than just using the HTM parts of the ISA of > the > particular architecture. > > > I think I still haven't explained myself properly, as it appears you > have different understanding of an ABI to me. So what's your understanding of an ABI? > > What I'm saying is that when memory transactions abort it has a very > similar effect on C++'s execution state as longjmp(). No. In particular, there's no guarantee of all-or-nothing with longjmp. An abort+restart of a transaction is invisible to the program (as it should be). Cancellation is visible, but in the sense that something else gets executed *instead of* the transaction. > Solve longjmp() working well with C++ and you solve memory > transactions in C++. > > > You also solve many, many interrelated longstanding problems and bugs > with interop between C and C++. For example, thread cancellation, > interop between Python and C++ via Boost.Python, etc etc. > > > I'll be at C++ Now 2013. Is there scope for some sort of workshop? I won't be there. Perhaps others in SG5 will be. -- You received this message because you are subscribed to the Google Groups "SG5 - Transactional Memory" group. To unsubscribe from this group and stop receiving emails from it, send an email to tm+unsu...@isocpp.org. To post to this group, send email to t...@isocpp.org. Visit this group at http://groups.google.com/a/isocpp.org/group/tm/?hl=en.
FWIW, I think I am missing your point too. Saying it's a "wood vs
trees" thing doesn't get me closer to getting your point. But let me
explain one place where I've felt a significant disconnect in this
discussion, in case it helps clarify how we're not understanding you.
You've said a couple of times something like "solve longjmp() in C++,
and you solve STM in C++". Torvald's responses to you have mostly made
sense to me, which suggests that perhaps we're both misunderstanding you
in similar ways.
My reactions to your sentence quoted above are:
0) longjmp is just about behavior of a single thread: control flow and
thread-private state. TM is about multithreaded behavior and shared
state. So no amount of "solving" longjmp will "solve" STM. But, ...
1) ... maybe I don't know what you mean by "solve STM".
Maybe what you mean is that solving the issues you have in mind for
longjmp will solve the specific issues related to control flow and
thread-private state for transactions. If so, understanding this might
help us past a disconnect or two and allow a more fruitful discussion of
what you're thinking.
Please feel free to ignore this if it's not helpful. I hope it is.
Do you subsume the ability to compare-and-swap different, unrelated
memory locations atomically under "CAS lock optimization"? For me,
this "multi-location atomicity cheaper than locks on average" is
a major feature.
[Retrofitting exception safety requires changing existing code.]
> As
> a result, many, if not most, large C++ libraries aren't exception
> safe e.g. Qt and you therefore can't safely mix ANY exception unsafe
> C++ with exception safe C++ e.g. the STL. That situation has resulted
> entirely because no one has grasped the nettle about solving, once
> and for all, the flow control interop between C style "forward stack
> only with gotos" programming, pre-C++98 "forward stack only and no
> gotos" and post-C++98 style "forward and reversible stack and no
> gotos" programming. And we should, because I'm sick and tired of
> dealing with bug reports caused by the STL throwing exceptions inside
> Qt code which instantly aborts the process etc. etc. which any C++
> programmer has the right to expect to "just work".
Do you have any specific suggestion to help the integration of
exception-safe and unsafe code?
TM can help here, in a way, because it can undo memory effects
of otherwise exception-unsafe code when an exception escapes it
(cancel-on-exception-escape). However, in order to provide this
ability, TM comes with a set of requirements for the contained code.
(For example, no I/O is allowed.) I doubt that something as large
as Qt satisfies these requirements.
> For me STM is entirely about flow control, because that's the hard part
> and the part we have systematically avoided resolving for over a decade
> precisely because it's hard. The STM memory model is quite
> straightforward in comparison: harder than Hans' acquire/release
> atomics, but still straightforward.
I don't agree with this at all. We've had a lot of discussion recently
on issues related to integrating transactions with exceptions. But this
is after quite a lot of hard work on many aspects related to
concurrency, which is in my mind by far the most important aspect of
transactions. Even if I think about the benefits of transactions in
single-threaded code, I still think first of the data-related aspects
(e.g., ability to undo effects of a transaction without writing explicit
code for this purpose), and not about control flow.
I'm not saying that C++ is already perfect wrt control flow. If there
were a parallel or subsequent effort to improve C++ control flow, that
might enable and/or necessitate changes to how transactions are
integrated, but it is not in the charter of this group, nor is it in the
areas of expertise of most participants in this group, to "fix" C++
control flow.
I really don't think so. There are a number of fairly difficult
implementation and specification issues related to reasonable language
integration and concurrency that have nothing whatsoever to do with
explicit cancellation (which is the source of most if not all of any
disagreement related in any way to control flow). None of those will
magically disappear if someone "fixes control flow".