[swift-evolution] Preconditions aborting process in server scenarios [was: Throws? and throws!]

Jeremy Pereira jeremy.j.pereira at googlemail.com
Wed Jan 18 05:30:24 CST 2017


> On 17 Jan 2017, at 11:53, Alex Blewitt <alblue at apple.com> wrote:
> 
>> 
>> On 17 Jan 2017, at 11:46, Jeremy Pereira <jeremy.j.pereira at googlemail.com> wrote:
>> 
>>> 
>>> On 17 Jan 2017, at 11:28, Alex Blewitt <alblue at apple.com> wrote:
>>> 
>>> 
>>>> On 17 Jan 2017, at 11:10, Jeremy Pereira via swift-evolution <swift-evolution at swift.org> wrote:
>>>> 
>>>> 
>>>>> On 17 Jan 2017, at 02:38, thislooksfun via swift-evolution <swift-evolution at swift.org> wrote:
>>>>> 
>>>>> I really hate to bring Java up, but I really do think it got at least one thing right with its error system, namely that one subset of error (namely `RuntimeException`), wouldn't be enforced by the compiler, but could optionally be caught.
>>>> 
>>>> I don’t entirely agree for two reasons:
>>>> 
>>>> 1. If a runtime error is thrown and caught, there is no way to guarantee the logical consistency of the state of the running process because who knows which stack frames were unwound without cleaning up properly. There is no way to safely catch a run time exception and recover.
>>> 
>>> From a Java perspective, clearly that's not true. 
>> 
>> It absolutely is true. 
>> 
>>     int someMethod()
>>     {
>>         aquireSomeLock();
>>         doSomethingThatIsNotDeclaredToThrowAnException();
>>         releaseSomeLock();
>>     }
> 
> The general way to perform locking in Java is to use a synchronised block around a particular resource, and those are cleaned up by the Java VM when unwinding the stack, or to use a finally block to do the same. You can certainly write incorrect code in Java; the correct code would look like:
> 
> int someMethod() {
>   try {
>     acquireSomeLock();
>     doSomethingThatIsNotDeclaredToThrowAnException();
>   } finally {
>     releaseSomeLock();
>   }
> }
> 

Yes, which is why catching runtime errors is a problem. You need all the boilerplate of  try { … } finally { … } every time you have a resource like this just in case any of the methods you call while you have the resource throw a run time exception. If you have more than one such resource in a scope you start to need pyramids of try … finally structures. It gets quite onerous, so people don’t do it or they don’t do it properly.

And also people often make mistakes when writing their try finally blocks as you did. The correct way to code my example is

int someMethod() 
{
  acquireSomeLock();
  try 
  {
    doSomethingThatIsNotDeclaredToThrowAnException();
  } 
  finally 
  {
    releaseSomeLock();
  }
}

(you don’t want to call releaseSomeLock() if the exception is throw while acquiring it - assuming acquireSomeLock() was properly designed).


> It's more likely to be written as:
> 
> int someMethod() {
>   synchronized(lockObject) {
>     doSomethingThatIsNotDeclaredToThrowAnException();
>   }
> }

Here you are assuming all resources that need to be acquired/released can be replaced with Java locks. You are also assuming that all cases where logical consistency is needed are locks or memory. What if you have a sequence of operations that must appear to be performed atomically in order to keep the model consistent?

synchronized int someMethodThatNeedsToBeAtomic()
{
    doOperationOne();
    doOperationTwo();
    doOperationThree();
    doOperationFour();
}

If I want to be able to recover from RunTimeExceptions, I need to catch them here and roll back any work already done. That means multiple try catch blocks or some other logic so I know how much I need to roll back. With Swift, if a method is not declared to throw, you know you don’t need write code to handle errors. The same applies to Java but only if you do not try to recover from RuntimeExceptions.

> 
>> 
> 
> True; file descriptors are one such example. Again, the language has the ability to recover instead of blowing up when that occurs:
> 
> try(FileInputStream in = new FileInputStream("theFIle")) {
>   // do stuff with input stream
> } // input stream is closed

This is a recent feature with Java and it’s certainly a step in the right direction.


>> 
>> No it’s not a poor example. I’ve seen it happen with real software in real production scenarios.
> 
> And if we're talking anecdata, I've seen large production systems where a particular message has been unable to be processed due to some incorrect data formatting (in some cases, an invalid UTF-8 sequence of bytes being passed in and treated as UTF-8). The general way is not to crash and return that and let it be processed a subsequent time, but ignored, put into a manual resolve queued, and keep going.

No, the correct way to handle errors in input data is validate it and/or throw a checked exception which can be handled and recovered safely in Java. In your case, you don’t want an exception in the log, you want a message saying what went wrong, where and how to fix it

> 
>> 
>>> 
>>>> I like Swift’s error handling because programming errors (like force unwrapping nil) are punished mercilessly by process termination and errors caused by external factors cannot be completely ignored. You have to at least put an empty catch block somewhere.
>>> 
>>> This is one of the significant problems in Swift at the moment for server-side logic. It may make sense to do this from a single-user application, but writing a server which is designed to handle hundreds or thousands of simultaneous clients can suffer from the fact that one bad client request can take out everyone's connection. In the server working group and in existing tools like Kitura/Vapor/Perfect etc. it's a non-trivial problem to solve, other than using a CGI like model where each request is handled by a single worker process that can terminate independently of the other requests in flight.
>> 
>> I agree it’s a non trivial problem to resolve. I’m saying that the Java “solution” of RuntimeExceptions doesn’t resolve it. 
> 
> Except you've not said /why/ the Java 'solution' of RuntimeExceptions doesn't resolve it. You've just used a couple of examples to prove it's possible to write bad Java code; but you can write bad code in any language.
> 

I’ve already said: unchecked exceptions cannot be recovered safely (see everything above).




More information about the swift-evolution mailing list