Jump to page: 1 2
Thread overview
scope + destructor with Exception parameter for RAII
Nov 28, 2006
Leandro Lucarella
Nov 28, 2006
Sean Kelly
Nov 28, 2006
BCS
Nov 28, 2006
Pragma
Nov 28, 2006
Sean Kelly
Nov 28, 2006
Sean Kelly
Nov 28, 2006
Sean Kelly
Nov 29, 2006
Leandro Lucarella
Nov 29, 2006
Sean Kelly
Nov 30, 2006
Leandro Lucarella
Nov 30, 2006
Sean Kelly
November 28, 2006
What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.

So you can do something like:

class Transaction
{
	Database db;
	public:
	this(Database d) { db = d; db.begin(); }
	~this() // success
	{
		db.commit();
	}
	~this(Exception e)
	{
		db.rollback();
	}
	// ...
}

scope Transaction t = new Transaction(db);

This concept could be extended to have as many destructors as you want, to handle different kind of exceptions, like:

class Transaction
{
	// ...
	~this() // success
	{
		db.commit();
	}
	~this(DatabaseException e)
	{
		db.rollback();
		// some recovery code
	}
	~this(AnotherException e)
	{
		db.rollback();
		// some other stuff
	}
	~this(Exception e)
	{
		db.rollback();
	}
}

I find it a little ugly and error prone =)
If, for example, you have to use scope(exit/success/failure) you have to  add yourself the finalization code on every use:

Each time you want to use a transaction (for example) you have to write:
Transaction t = new Transaction(db);
scope(failure) db.rollback();
scope(exit) db.commit();
// ... code

If you forget one scope(...) db.xxx(); you hit a bug. On the other hand, if you could just use:
scope Transaction t = new Transaction(db);

The code is simpler, less error prone, and plus you have all the Transaction logic in the transaction object, not in the code that uses the transaction.

Opinions? Factibility?

-- 
Leandro Lucarella
Integratech S.A.
4571-5252
November 28, 2006
Leandro Lucarella wrote:
> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.

I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.

One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight.  I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which).


Sean
November 28, 2006
Sean Kelly wrote:
> Leandro Lucarella wrote:
>> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.
> 
> I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.
> 
> One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight.  I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which).
> 
> 
> Sean


I think this works under DMD

try
{
	throw new Error("some stuff");
}
catch(Error e)
{
	throw new Error("more stuff\n"~e.toString);
}


It could be vary handy to have this ability to swap out one exception for another.
November 28, 2006
BCS wrote:
> Sean Kelly wrote:
>> Leandro Lucarella wrote:
>>> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.
>>
>> I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.
>>
>> One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight.  I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which).
>>
>>
>> Sean
> 
> 
> I think this works under DMD
> 
> try
> {
>     throw new Error("some stuff");
> }
> catch(Error e)
> {
>     throw new Error("more stuff\n"~e.toString);
> }
> 
> 
> It could be vary handy to have this ability to swap out one exception for another.

Agreed.  Although I don't know if this interferes with Sean's interpretation of the spec or not.  I've done this in a few places myself, and I find it quite useful.

-- 
- EricAnderton at yahoo
November 28, 2006
Pragma wrote:
> BCS wrote:
>> Sean Kelly wrote:
>>> Leandro Lucarella wrote:
>>>> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.
>>>
>>> I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.
>>>
>>> One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight.  I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which).
>>>
>>>
>>> Sean
>>
>>
>> I think this works under DMD
>>
>> try
>> {
>>     throw new Error("some stuff");
>> }
>> catch(Error e)
>> {
>>     throw new Error("more stuff\n"~e.toString);
>> }
>>
>>
>> It could be vary handy to have this ability to swap out one exception for another.
> 
> Agreed.  Although I don't know if this interferes with Sean's interpretation of the spec or not.  I've done this in a few places myself, and I find it quite useful.

The above example isn't the same thing.  The in-flight exception is caught and a different one is rethrown.  What I consider illegal is something like this:

    try
    {
        throw new Exception( "A" );
    }
    finally
    {
        throw new Exception( "B" );
    }

What exception is in-flight after the finally block completes?  And what happens to the other exception?  The only correct behavior here is to terminate the application.


Sean
November 28, 2006
Sean Kelly wrote:
> Pragma wrote:
>> BCS wrote:
>>> Sean Kelly wrote:
>>>> Leandro Lucarella wrote:
>>>>> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.
>>>>
>>>> I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.
>>>>
>>>> One thing I have done for Ares, however, is to terminate the program if one exception is thrown while another is in flight.  I think DMD/Phobos does not do this currently, and instead either ignores the new exception, or substitutes it for the in-flight exception (I can't remember which).
>>>>
>>>>
>>>> Sean
>>>
>>>
>>> I think this works under DMD
>>>
>>> try
>>> {
>>>     throw new Error("some stuff");
>>> }
>>> catch(Error e)
>>> {
>>>     throw new Error("more stuff\n"~e.toString);
>>> }
>>>
>>>
>>> It could be vary handy to have this ability to swap out one exception for another.
>>
>> Agreed.  Although I don't know if this interferes with Sean's interpretation of the spec or not.  I've done this in a few places myself, and I find it quite useful.
> 
> The above example isn't the same thing.  The in-flight exception is caught and a different one is rethrown.  What I consider illegal is something like this:
> 
>     try
>     {
>         throw new Exception( "A" );
>     }
>     finally
>     {
>         throw new Exception( "B" );
>     }

By the way, the change I made will only trap exceptions thrown from an object's dtor, as it was a change to the finalizer code.  The above will work just fine, with only one of the two exceptions escaping.  Same as:

    scope(exit) throw new Exception( "B" );
    throw new Exception( "A" );

I could probably be convinced that the throw from the finally block should simply be ignored, as the finally block is intended to do some simple clean-up, but the scope(exit) bit above is clearly a programming error.  Throwing from an object's dtor is a lot closer to throwing from scope(exit) than finally IMO.  Because while they are functionally identical, I think they are likely to be used in different ways.  With a finally block, the programmer is stating that he expects an exception to be thrown and that the code should execute regardless.  I'm still not certain that I like the idea of ignoring an exception from a finally block, but it bothers me less than ignoring exceptions from dtors or scope(exit)/scope(failure) blocks.


Sean
November 28, 2006
One more follow-up and I promise I'll stop :-)  I just remembered why I changed Ares in the first place.  Since objects are finalized during GC collections, throwing from a dtor results in completely unpredictable program behavior.  For example:

    {
        class MyClass
        {
            ~this()
            {
                throw new Exception( "die" );
            }
        }
        MyClass c = new MyClass();
    }

    OtherClass c = new OtherClass(); // A

In the program above, an exception may be thrown from point A that has *nothing to do with an out of memory condition* and worse, it will be thrown only if program memory is in a state where the GC needs to collect to free up resources.  It's even possible that the instance of MyClass could have been declared and allocated in a completely different thread, resulting it its being passed up a call stack that was not written to expect such a condition.  So the presence of a GC and GC-called finalizers makes throwing from dtors even worse than it is in deterministic situations (which is still quite bad).

Also, I have found few instances where an exception really needs to be thrown from a dtor.  While resource cleanup operations may indeed fail, more often than not the documentation will say that a failure may only occur if the parameters are invalid.  And with proper encapsulation is is guaranteed not to happen.


Sean
November 29, 2006
Sean Kelly escribió:
> Leandro Lucarella wrote:
>> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.
> 
> I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.

Exceptions would not be thrown from the dtor, it just executes some code if it was executed during an unwinding because an exception was raised:

scope Transaction t = new Transaction();
throw Exception("ouch!");

Here the Transaction.~this(Exception) is called, but there is no Exception throwing in the dtor.

I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)?

Any other thoughts on this (the thread diverged a little from the original topic ;)?

-- 
Leandro Lucarella
Integratech S.A.
4571-5252
November 29, 2006
Leandro Lucarella wrote:
> Sean Kelly escribió:
>> Leandro Lucarella wrote:
>>> What do you think of adding an optional parameter (exception) to the destructor, defaulting to null, to indicate the destructor was called then unwinding because an exception was thrown? Then you can almost forget about scope(exit/success/failure) and you have a RAII as complete  as Python's 'with' statement.
>>
>> I don't like it, personally.  It doesn't seem a good idea for a dtor to alter its behavior based on whether an exception is in flight, and exceptions should never be thrown from dtors anyway.  Doing so makes writing correct code far too complicated.
> 
> Exceptions would not be thrown from the dtor, it just executes some code if it was executed during an unwinding because an exception was raised:
> 
> scope Transaction t = new Transaction();
> throw Exception("ouch!");
> 
> Here the Transaction.~this(Exception) is called, but there is no Exception throwing in the dtor.

So are you saying you want this feature to allow Transaction to decide whether to roll-back or commit?  I had assumed it was to determine whether it was "safe" for Transaction's dtor to throw an exception itself.

> I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)?

The lack of encapsulation doesn't bother me much, though now I see what you're getting at.  I do think that having:

    auto scope t = new Transaction();
    scope(failure) t.rollback();
    // commits if not rolled back on scope exit, alternately use
    scope(success) t.commit();

actually aids readability a bit, at the expense of some extra code.

That said, I have considered adding a routine that the user can call to determine whether an exception is in flight.  Similar to the one in C++, but without all the annoying shortcomings.  It would mean setting a thread-local flag or pointer in the internal exception handling code, etc.  I think this is a better approach than altering dtor syntax for the same purpose, as it avoids language changes and doesn't lose any usefulness in the process.

> Any other thoughts on this (the thread diverged a little from the original topic ;)?

See above :-)


Sean
November 30, 2006
Sean Kelly escribió:
>> I don't see why writing correct code is that complicated. And how do you address the problem of repeating error handling code and the lack of encapsulation of scope(success/failure)?
> 
> The lack of encapsulation doesn't bother me much, though now I see what you're getting at.  I do think that having:
> 
>     auto scope t = new Transaction();
>     scope(failure) t.rollback();
>     // commits if not rolled back on scope exit, alternately use
>     scope(success) t.commit();
> 
> actually aids readability a bit, at the expense of some extra code.

So you are against all RAII done in the C++ way, I guess...

> That said, I have considered adding a routine that the user can call to determine whether an exception is in flight.  Similar to the one in C++, but without all the annoying shortcomings.  It would mean setting a thread-local flag or pointer in the internal exception handling code, etc.  I think this is a better approach than altering dtor syntax for the same purpose, as it avoids language changes and doesn't lose any usefulness in the process.

I see this more as a hack than a clean solution (I don't say the dtor Exception parameter is heaven but I see it a little more cleaner =), but its fair enough.

I didn't know C++ had a way to determine an exception is in flight...

>> Any other thoughts on this (the thread diverged a little from the original topic ;)?
> 
> See above :-)

Thanks, really =)

-- 
Leandro Lucarella
Integratech S.A.
4571-5252
« First   ‹ Prev
1 2