April 01, 2013
On 04/01/2013 12:44 PM, Dmitry Olshansky wrote:> 01-Apr-2013 20:00, John Colvin пишет:

>> Therefore, you're better off not trying to cleanup if program state
>> could be invalid.
>
> Data is corrupted no matter if you just fail to write it in a consistent
> state (sudden assertion in some 3-rd party library) or corrupt
> accidentally by bad write (during cleanup on corrupted RAM).

The failed assertion may be the moment when the program detects that something is wrong. A safe program should stop doing anything else.

> Therefore you should always try to orderly cleanup but do not rely on it
> to actually work at all circumstances (thus backups, commits/save
> points, watchdogs and whatnot).

A safe program must first guarantee that that cleanup is harmless, which is not possible when the program is in an invalid state. Imagine sending almost infinite number of "cleanup" commands to a device that can harm people who are around it.

Ali

April 01, 2013
On 4/1/2013 4:08 AM, Lars T. Kyllingstad wrote:
> It's time to clean up this mess.

About out-of-memory errors
--------------------------

These are considered non-recoverable exceptions for the following reasons:

1. I've almost never seen a program that could successfully recover from out of memory errors, even ones that purport to.

2. Much effort is expended trying to make them recoverable, yet it doesn't work, primarily because the recovery paths are never tested.

3. There are an awful lot of instances where memory is allocated - almost as many as allocating stack space. (Running out of stack space doesn't even throw an Error exception, the program is just unceremoniously aborted.)

4. Making it recoverable means that pure functions now have side effects. Function purity, rather than a major feature of D, would become a little-used sideshow of marginal utility.

5. Although a bad practice, destructors in the unwinding process can also allocate memory, causing double-fault issues.

6. Memory allocation happens a lot. This means that very few function hierarchies could be marked 'nothrow'. This throws a lot of valuable optimizations under the bus.

7. With the multiple gigs of memory available these days, if your program runs out of memory, it's a good sign there is something seriously wrong with it (such as a persistent memory leak).

8. If you must recover from specific out of memory possibilities, you can still use malloc() or some other allocation scheme that does not rely on the GC.

April 01, 2013
On 4/1/2013 4:08 AM, Lars T. Kyllingstad wrote:
> It's time to clean up this mess.


As for why finally blocks are not executed for Error exceptions, the idea is to minimize cases where the original error would now cause an abort during the unwinding process. Catching an Error is useful for things like:

1. throw the whole plugin away and restart it
2. produce a log of what happened before aborting
3. engage the backup before aborting
4. alert the operator that the system has failed and why before aborting

Unwinding is not necessary for these, and can even get in the way by causing other failures and aborting the program by attempting cleanups when the code is in an invalid state.

April 01, 2013
02-Apr-2013 00:34, Ali Çehreli пишет:
> On 04/01/2013 12:44 PM, Dmitry Olshansky wrote:> 01-Apr-2013 20:00, John
> Colvin пишет:
>
>  >> Therefore, you're better off not trying to cleanup if program state
>  >> could be invalid.
>  >
>  > Data is corrupted no matter if you just fail to write it in a consistent
>  > state (sudden assertion in some 3-rd party library) or corrupt
>  > accidentally by bad write (during cleanup on corrupted RAM).
>
> The failed assertion may be the moment when the program detects that
> something is wrong. A safe program should stop doing anything else.

And that's precisely the interesting moment. It should stop but the definition of "stop" really depends on many factors. Just pretending that calling abort is a panacea is totally wrong IMO.

BTW what do you exactly mean by "safe" program?

>  > Therefore you should always try to orderly cleanup but do not rely on it
>  > to actually work at all circumstances (thus backups, commits/save
>  > points, watchdogs and whatnot).
>
> A safe program must first guarantee that that cleanup is harmless, which
> is not possible when the program is in an invalid state.

There could a lot of ways to do that even in "illegal" state. Restore and conservative reconstruction of valid state is often possible (like restoring files on a faulty hard drive) with a help of certain tricks you can get high estimates of these actions to succeed.

Once you have resigned yourself to black and white - valid /invalid there is no way to go indeed.

> Imagine sending
> almost infinite number of "cleanup" commands to a device that can harm
> people who are around it.

These are far better off with built-in hardware fail-safe switches and redundant circuitry.

Just suddenly stopping the program in the control of a spinning blade that cuts through somebody's tissue is not good enough.

>
> Ali
>


-- 
Dmitry Olshansky
April 01, 2013
On Mon, 01 Apr 2013 22:34:39 +0200, Ali Çehreli <acehreli@yahoo.com> wrote:

> A safe program must first guarantee that that cleanup is harmless, which is not possible when the program is in an invalid state. Imagine sending almost infinite number of "cleanup" commands to a device that can harm people who are around it.

Of course. But the opposite is also the case - failure to turn off dangerous
hardware, or leaving hardware in a dangerous state when the program fails
is just as bad as putting it in an unknown state. The decision must be made
on a case-by-case basis.

I am reminded of Therac-25[1]. though the situation there was slightly
different, similar situations could arise from not turning off hardware.


[1]: http://en.wikipedia.org/wiki/Therac-25
-- 
Simen
April 01, 2013
01-Apr-2013 23:46, Lars T. Kyllingstad пишет:
> On Monday, 1 April 2013 at 18:40:48 UTC, Dmitry Olshansky wrote:
>> 01-Apr-2013 15:08, Lars T. Kyllingstad пишет:
>>> It's time to clean up this mess.
>>>
>>> http://wiki.dlang.org/DIP33
>>
>> Overall neat.
>>
>> 1. Where to define this hierarchy.
>> Captain the obvious says std.exception, but can we fit all of them there?
>
> Some of them have to be in core.exception.  Personally, I think the rest
> of the base hierarchy should be in std.exception.  In fact, I think the
> "standard hierarchy" should be defined as "what's in core.exception and
> std.exception".
>
> If we identify cases where modules absolutely *must* extend these
> classes for extremely specific purposes, this can be done within the
> module.

Good. We are mostly on the same page here and later.

>
>> 2. ProcessException is IMHO a system exception. Plus some RTOses
>> systems may not have processes in the usaul POSIX sense. It needs some
>> thought.
>
> Well, I *don't* think ProcessException is a SystemException. :) And if
> druntime/Phobos is to be used on OSes that don't have processes, there
> are other adaptations that have to be made that are probably more
> fundamental.

Okay. I guess all of this goes to "Embedded D"/"D Lite" kind of spec.

Last thing - why separating ThreadException vs ProcessException and should they have some base class? Just asking to see the rationale behind it that is missing from DIP currently.

>
>> 4. A quiz as an extension of 3 - where a e.g. serial port exceptions
>> would fit in this hierarchy? Including Parity errors, data overrun etc.
>> Let's think a bit ahead and pick names wisely.
>> Maybe turn NetworkException --> Comm(unication)Exception, I dunno.
>
> Good question, I dunno either. :)  I agree we should think about it.
>
>
>> 5. Continuing the above - a failed call (in-system) of a general
>> purpose IPC library would be a ... SystemException? A network
>> exception? Should it matter to the user?
>
> Note that I am only saying that the standard exceptions should cover
> *most* error categories.  100% is not feasible, and we shouldn't even
> try.  This may be one of the exceptions (haha) to the rule.
>

Well, maybe we could just extend it later on. As it's easier to add then remove then let's deffer the hard ones :)

>> 6. I like ParseException. Wondering if could be generalized a bit - in
>> a sense it's a "bad format"/"malformed" exception. For instance
>> corrupt DB file reporting ParseException (= bad format) is rather wacky.
>
> I agree, and welcome all suggestions for better names.

>
>> 8. For IOExcpetion we might consider adding an indication on which
>> file handle the problem happened and/or if it's closed/invalid/"mostly
>> fine" that.
>
> Which form do you suggest that such an indicator should take?

That's the trick - I hoped somebody will just say "aha!" and add one :)

The internal handle is hard to represent other then ... some platform specific integer value. There goes generality... Other then this there is a potential to stomp on feet of higher-level abstraction on top of that handle.

That last bit makes me reconsider the idea. While I see some potential use for it I suspect it's too niche to fit in the general hierarchy.

> Bear in
> mind that it should be general enough to cover all, or at least most,
> kinds of I/O exceptions.

Adding a Kind that states one of:
out-of-data (read empty file)
illegalOp (reading closed file, writing read-only file/socket)
interrupted (operation was canceled by OS, connection forcibly closed, disk ejected etc.)
hardFault (OS reports hardware failure)

...
etc. shouldn't hurt. Need to think this through better, of course, and consult OSes manuals again.
-- 
Dmitry Olshansky
April 01, 2013
On 4/1/2013 2:20 PM, Simen Kjærås wrote:
> I am reminded of Therac-25[1]. though the situation there was slightly
> different, similar situations could arise from not turning off hardware.

Relying on a program running correctly in order to avoid disaster is a terrible design. Even mathematically proving a program to be correct is in no way, shape, or form sufficient to deal with this.

April 01, 2013
On Monday, April 01, 2013 21:33:22 Lars T. Kyllingstad wrote:
> My problem with subclasses is that they are a rather heavyweight addition to an API. If they bring no substance (such as extra data), I think they are best avoided.

Except that they're extremely valuable when you need to catch them. Being able to do something like

try
{
 ...
}
catch(FileNotFoundException fnfe)
{
 //handling specific to missing files
}
catch(PermissionsDeniedException pde)
{
 //handling specific to lack of permissions
}
catch(FileExceptionfe
{
 //generic file handling error
}
catch(Exception e)
{
 //generic error handling
}

can be very valuable. In general, I'd strongly suggest having subclasses for the various "Kind"s in addition to the kind field. That way, you have the specific exception types if you want to have separate catch blocks for different error types, and you have the kind field if you just want to catch the base exception.

If anything, exceptions are exactly the place where you want to have derived classes with next to nothing in them, precisely because it's the type that the catch mechanism uses to distinguish them.

- Jonathan M Davis
April 01, 2013
On 04/01/2013 02:01 PM, Dmitry Olshansky wrote:> 02-Apr-2013 00:34, Ali Çehreli пишет:

>> The failed assertion may be the moment when the program detects that
>> something is wrong. A safe program should stop doing anything else.
>
> And that's precisely the interesting moment. It should stop but the
> definition of "stop" really depends on many factors. Just pretending
> that calling abort is a panacea is totally wrong IMO.
>
> BTW what do you exactly mean by "safe" program?

I meant a program that wants to produce correct results. I was indeed thinking about Therac-25 that Simen Kjærås mentioned. I agree that there must be hardware fail-safe switches as well but they could not protect people from every kind of software failure in that example.

Having said that, I can see the counter argument as well: We are in an inconsistent state, so trying to do something about it could be better than not running a cleanup code. But I also remember that an AssertError may be thrown by an assert() call, telling us that a programmer put it in there explicitly, meaning that the program cannot continue. If there was any chance of recovery, then the programmer could have thrown an Exception or remedy the situation right then.

Ali

April 01, 2013
On Mon, 01 Apr 2013 18:26:22 -0400, Jonathan M Davis <jmdavisProg@gmx.com> wrote:

> On Monday, April 01, 2013 21:33:22 Lars T. Kyllingstad wrote:
>> My problem with subclasses is that they are a rather heavyweight
>> addition to an API. If they bring no substance (such as extra
>> data), I think they are best avoided.
>
> Except that they're extremely valuable when you need to catch them. Being able
> to do something like
>
> try
> {
>  ...
> }
> catch(FileNotFoundException fnfe)
> {
>  //handling specific to missing files
> }
> catch(PermissionsDeniedException pde)
> {
>  //handling specific to lack of permissions
> }
> catch(FileExceptionfe
> {
>  //generic file handling error
> }
> catch(Exception e)
> {
>  //generic error handling
> }
>
> can be very valuable. In general, I'd strongly suggest having subclasses for
> the various "Kind"s in addition to the kind field. That way, you have the
> specific exception types if you want to have separate catch blocks for different
> error types, and you have the kind field if you just want to catch the base
> exception.
>
> If anything, exceptions are exactly the place where you want to have derived
> classes with next to nothing in them, precisely because it's the type that the
> catch mechanism uses to distinguish them.

In general, this is not enough.  Imagine having an exception type for each errno number.  Someone may want that!

Note that there are two categories of code dealing with thrown exceptions:

1. whether to catch
2. what to do

Right now, we have the super-basic java/c++ model of matching the type for item 1.  D could be much better than that:

catch(SystemException e) if(e.errno == EBADF)
{
   ...
}

For item 2, once you have the caught exception, you have mechanisms to deal with the various fields of the exception.  So even without improvements to #1, you can rethrow the exception if it's not what you wanted.  Just the code isn't cleaner:

catch(SystemException e)
{
   if(e.errno != EBADF)
      throw e;
}

-Steve