User: Password:
|
|
Subscribe / Log in / New account

No thanks.

No thanks.

Posted Nov 9, 2012 23:38 UTC (Fri) by nix (subscriber, #2304)
In reply to: No thanks. by Cyberax
Parent article: Haley: We're doing an ARM64 OpenJDK port!

Consider how long it took after the introduction of exceptions into C++ to figure out how to write truly exception-safe code. It took *decades*. Doing it is not remotely obvious.


(Log in to post comments)

No thanks.

Posted Nov 10, 2012 3:25 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link]

Nope. The way to write exception-safe code has been clear from the start (modulo a few foulups in the Standard). That's why there's no "finally" keyword in C++.

The main problem was that exceptions were added quite lately in the game, and they were not reliable and/or generally available until at least early 2000-s. So we're stuck with huge amount of legacy non-exception-safe code.

But that's a poor excuse for creating a new language without exception support.

No thanks.

Posted Nov 10, 2012 16:06 UTC (Sat) by nix (subscriber, #2304) [Link]

Well it's curious the way I have multiple whole books which have about a third of their total page count discussing how to write exception-safe code in the presence of copy constructors, assignment operators, operator() and other such things which throw exceptions (throw in the possible elision of temporaries if you want to make it really fun).

Perhaps I was hallucinating and e.g. Herb Sutter's writings on the subject do not in fact exist, and it was 'clear from the start'. That would explain it, but I don't think that's so.

No thanks.

Posted Nov 10, 2012 21:49 UTC (Sat) by Cyberax (✭ supporter ✭, #52523) [Link]

And the whole books should have been written on writing safe C code in presence of errors.

A simple question - how do you deal with errors in the ubiquitous "goto error_exit" pattern in C?

No thanks.

Posted Nov 10, 2012 23:08 UTC (Sat) by nix (subscriber, #2304) [Link]

I actually solved this once with a really ugly exception-unwinder (implemented mostly in macros with one fugly bit of arch-dependent code). A few years later I had to port it to a new platform and wished I'd done something else.

(btw, I wish you'd be less bloody combative all the time. You turn every discussion on LWN into a minor war.)

No thanks.

Posted Nov 11, 2012 0:45 UTC (Sun) by hummassa (subscriber, #307) [Link]

Kettle, meet pot. :-D
But j/k, I love your discussions. From both of you. I learn a lot.

No thanks.

Posted Nov 11, 2012 5:12 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

>I actually solved this once with a really ugly exception-unwinder (implemented mostly in macros with one fugly bit of arch-dependent code).
Yup. Each sufficiently complex error handling system is indistinguishable from exceptions.

> (btw, I wish you'd be less bloody combative all the time. You turn every discussion on LWN into a minor war.)
Well, it's clear for me that there are only two kinds of opinions: mine and incorrect. I don't understand why other people still disagree.

No thanks.

Posted Nov 11, 2012 9:54 UTC (Sun) by paulj (subscriber, #341) [Link]

You can structure your code (in C, C++, Java, etc) so that you only have to check for errors at object creation, and thereafter only where it is convenient - i.e. you can do as many operations on the objects as you want without having to check for erros, without any use of control-flow branch points exploding exceptions:
do {
  var f = foo_new ();
  var b = bar_new ();

  if (!f || !b)
    break;

  f.twiddle ();
  b.jiggle ();
  
  <do as many more operations as you want on f and b,
   without need for checking for errors>

  if (f.state == ERROR || b.state == ERROR)
    break;

  <do whatever updates to this objects internal state,
   and update its .state appropriately>
  return;
} while (0);

if (f)
  foo_delete (f);
if (b)
  bar_delete (b);

<set this object to its error state>

return;
You do this by having the objects keep a finite state machine internally, with an idempotent error state. You could do more sophisticated error recovery by also tracking the prior state, that transited into the error state and potentially other more detailed info, e.g. with:
if (b.state == ERROR) {
  switch (b.prev_state) {
     ...
  }
}
Most of the same benefit of exceptions - of being able to move error handling out of the primary flow of code - but without the control-flow explosion (no need for goto either).

No thanks.

Posted Nov 11, 2012 10:03 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

So you now have to prefix ALL methods of f's class with "if (error) return error;". Nice.

And of course, you can forget about doing "b.twiddle(f.jiggle())".

And it'll be especially vexing in code that might be reused in several parts of a project. Additionally, your deleters (foo_delete, bar_delete) might in turn cause errors, how are you going to process them?

It's amazing to what lengths people would go just to avoid using exceptions. Why, they'd even re-invent exceptions without all their nice properties and with all the bad ones.

No thanks.

Posted Nov 11, 2012 11:01 UTC (Sun) by paulj (subscriber, #341) [Link]

Whether you do or don't use exceptions, it's far from a bad idea for objects to always check their internal consistency on entry from external callers. As for dealing with errors in clean up, remind me again how exceptions improves on that? Would you throw an error from a destructor?

Also, you seem to be suffering from some kind of binary thinking - that people are either for or against something - and projecting that onto others. I have not at any stage expressed an opinion on whether exceptions should be avoided or not.

No thanks.

Posted Nov 11, 2012 11:20 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

>Whether you do or don't use exceptions, it's far from a bad idea for objects to always check their internal consistency on entry from external callers.
That's a bad idea because of a several reasons. All reliable systems should be built on the idea of invariants and contracts. The violation of the contract/invariant should be treated as a fatal error.

So putting "assert(someInvariantState==43)" in method's body is fine as a purely debugging tool to find programmer's error. However, doing "if (someInvariantState!=42) return -ERR;" is a big NO-NO.

>As for dealing with errors in clean up, remind me again how exceptions improves on that? Would you throw an error from a destructor?
C++ doesn't have a nice solution for that. Java has one in the form of nested exceptions - it's possible to replace the current exception with the new one, retaining the current one as a nested exception.

No thanks.

Posted Nov 11, 2012 15:29 UTC (Sun) by paulj (subscriber, #341) [Link]

From my perspective, programming network services, where inputs may come from bad actors, and where the last you want to do is to allow a bad actor to cause your system to deny service to the good actors, I flatly disagree that violation of contracts should result in fatal errors - termination of that service to that actor, in a clean way, yes, fatal end of programme and all service: absolutely the wrong goal. (Invariants should be fatal, but that's a different thing).

And if you think I've been arguing that errors should be returned and caught and handled at each method call you've simply not read my comments, and you're just arguing with a figment of your own imagination. Good luck with that :).

No thanks.

Posted Nov 11, 2012 15:57 UTC (Sun) by raven667 (subscriber, #5198) [Link]

Fatal termination of the program might be the only safe bet if the internal state is compromised by a bad actor, I would be distrustful of claims to "cleanly" recover or try to "handle" errors that they actually can't handle. That's why long-running processes should be run under high availability supervision like daemontools or systemd.

I'm no expert on implementation details but the idea of Exceptions seems right to me, errors can't be ignored, they have to be handled or the program dies, which seems better to me than having your program jump the track and keep stupidly barreling along mowing through your data, driving through your living room window 8-)

No thanks.

Posted Nov 11, 2012 16:19 UTC (Sun) by paulj (subscriber, #341) [Link]

If the internal state is compromised yes. However, you want to reject the bad actor's input before that happens. Parsing things with state machines and related automata, and cleanly rejecting invalid input (and dispensing with associated state) is not rocket science. (Note that method calls related to some piece of state can be viewed as inputs/transitions on an automaton).

Asserting and crashing should be a last resort in daemons that must provide shared-state services to multiple actors. While it is preferable to assert than to allow a security critical error, asserting itself may be a security critical error - handling errors cleanly is again preferable to the assert. A better approach is to have multiple layers of defence, with asserts against critical inconsistency errors at lower layers (e.g. forcing all IO through checked, bounded buffer abstractions), and well-defined automata at higher layers to provide clean error semantics (potentially multiple levels of such). You can't just restart, because of the shared-state. If you externalise the shared-state so that the code can be restarted, you still need to ensure that shared-state can never be manipulated into an inconsistent state.

All the world is a state machine, even the functional programming world. ;)

No thanks.

Posted Nov 11, 2012 10:39 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

Oh, and if "f.twiddle ();" returns with an error and "b.jiggle ();" depends on some side effects of "f.twiddle()" that hasn't happened then you get a very nice "wish you happy debugging!" moment.

No thanks.

Posted Nov 11, 2012 10:55 UTC (Sun) by hummassa (subscriber, #307) [Link]

And more -- good look synchronizing the FSMs for 42 instances of each of 42 different types of objects (think a *simple* payroll application). THAT is full of happy debugging moments.

No thanks.

Posted Nov 11, 2012 12:36 UTC (Sun) by paulj (subscriber, #341) [Link]

Pretty much all non-trivial objects (composites, or ones with implicit constraints on fields that can't be expressed in the type system or language) have an FSM, at a minimum the 2-state FSM of "Internal state is consistent" and "Internal state is not consistent". You have to deal with that reality one way or another. Tracking that consistency internally through an explicit FSM is one way. Dealing with invalid use of that object after errors through an idempotent error state or assertions or exceptions all have their pros and cons.

You don't have to synchronise all the FSMs. Or at least, if you do, then it wasn't because you added an explicit FSM - you had to effectively be synchronising their state already anyway. All it does is crystalise and making explicit (inc making it queryable to outside users) requirements that are already there.

No thanks.

Posted Nov 11, 2012 13:06 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

That's about THE WORST possible system to write software. It combines everything:
1) Hidden state, affecting execution silently.
2) Completely decoupling the error site and the place where error is detected/handled.
3) Possible unforseen interactions through side-effects.
4) Mutable objects.
5) Non-thread safety by design.

No thanks.

Posted Nov 11, 2012 15:07 UTC (Sun) by paulj (subscriber, #341) [Link]

1. Many state+code objects in software have hidden state - it's called encapsulation.

2. Decoupling error handling from the error site (in terms of the programming language syntax) is exactly what exceptions do!

3. You could level this argument at pretty much any OOP code, you'd have to be writing pure functional code to avoid this charge.

4. Ditto.

5. Thread safety is completely orthogonal. Making it thread-safe is no different to making other stateful objects thread safe.

You're just throwing accusations blindly in the hope some stick, methinks. :)

No thanks.

Posted Nov 12, 2012 1:52 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link]

>1. Many state+code objects in software have hidden state - it's called encapsulation.

Normal designers try to MINIMIZE it. You are not only add it gratuitously, but also introduce non-trivial interdependencies between objects.

For instance:
>f.twiddle();
>j.jiggle();

What if j.jiggle() takes 10 minutes to complete? Then your code would just be losing time if "f.twiddle()" is in error.

>2. Decoupling error handling from the error site (in terms of the programming language syntax) is exactly what exceptions do!

Not error handling, but error _detection_. You can simply forget to check that f's state is OK after the loop. Then there's a problem with compounded errors.

> 3. You could level this argument at pretty much any OOP code, you'd have to be writing pure functional code to avoid this charge.

Nope. Good modern OOP code tries to minimize mutability (it's OK to use mutable objects where it's necessary) - a lot of new OOP languages even have variables that are immutable by default, for example.

> 5. Thread safety is completely orthogonal. Making it thread-safe is no different to making other stateful objects thread safe.

No. For instance "f" object might be immutable except for its "error" state.

BTW, I think that there should definitely be a death penalty for those who abuse do..while loops to emulate "goto".

No thanks.

Posted Nov 12, 2012 10:05 UTC (Mon) by paulj (subscriber, #341) [Link]

No dependencies have been added between objects.

In extreme cases, such as j.jiggle () taking 10 minutes, then you might want to check before it. I gave a sketch of a programming pattern - it wasn't intended to be an exact solution for every possible programming problem. If you use it, you will still need to apply some intelligence of your own.

Note further, I was not even claiming that this is *THE* pattern to solve all error-handling. I was just giving it as an example, for consideration, as you earlier had asked for examples of error handling without exceptions (well, you asked for a goto error-exit pattern example, but I assumed other examples would also be allowed).

Object mutability: If an object is immutable, then its state doesn't change and will be immutable. Clearly there is little point in applying state-tracking patterns to single-state objects. This pattern applies to objects with a finite multiplicity of states. Despite your contention, I am fairly sure programming is full of such objects.

Even if you use a series of immutable objects and transition between them, you will still have an FSM with mutable state at a level above those immutable objects, implicitly or explicitly.

No thanks.

Posted Nov 11, 2012 12:27 UTC (Sun) by paulj (subscriber, #341) [Link]

You have to query the state of the objects FSM before using any of its other state in this pattern, yes. For other querying methods, returning state other than the FSM state, you can make that a terminal exception (in the "assert()" sense) if such other state is queried for when the object is reached its error state, or you could throw an exception.


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds