[swift-evolution] Preconditions aborting process in server scenarios [was: Throws? and throws!]
Dave Abrahams
dabrahams at apple.com
Tue Jan 17 14:38:18 CST 2017
on Tue Jan 17 2017, Joe Groff <swift-evolution at swift.org> wrote:
>> On Jan 17, 2017, at 3:53 AM, Alex Blewitt via swift-evolution
> <swift-evolution at swift.org> wrote:
>>
>>>
>>> On 17 Jan 2017, at 11:46, Jeremy Pereira
>>> <jeremy.j.pereira at googlemail.com
>
>>> <mailto:jeremy.j.pereira at googlemail.com>>
>>> wrote:
>>>
>>>>
>>>> On 17 Jan 2017, at 11:28, Alex Blewitt <alblue at apple.com <mailto:alblue at apple.com>> wrote:
>>>>
>>>>
>>>>> On 17 Jan 2017, at 11:10, Jeremy Pereira via swift-evolution
>>>>> <swift-evolution at swift.org
>>>>> <mailto:swift-evolution at swift.org>>
>>>>> wrote:
>>>>>
>>>>>
>>>>>> On 17 Jan 2017, at 02:38, thislooksfun via swift-evolution
>>>>>> <swift-evolution at swift.org
>>>>>> <mailto: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();
>> }
>> }
>>
>> It's more likely to be written as:
>>
>> int someMethod() {
>> synchronized(lockObject) {
>> doSomethingThatIsNotDeclaredToThrowAnException();
>> }
>> }
>>
>> and where 'lockObject' is 'this' then the shorter
>>
>> synchronized int someMethod() {
>> doSomethingThatIsNotDeclaredToThrowAnException();
>> }
>>
>>>> It may well be true for the current implementation of Swift, and
>>>> there's questions about how to clean up objects with reference
>>>> counts outstanding (since there's no garbage collector). It isn't
>>>> necessarily the case that it isn't possible, but it does require
>>>> some additional stack unwinding; and the implementation of that
>>>> may be too cumbersome/impractical/undesirable to occur.
>>>
>>> Not all resources are reference counts or memory allocations.
>>
>> 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
>>
>> Compilers and tools can generate warnings for such cases, in the same way that other languages can warn about not freeing memory.
>>
>>>
>>>>
>>>>> 2. People abuse RuntimeException to simplify their error handling
>>>>> code: “if I throw a RuntimeException I don’t need a zillion catch
>>>>> clauses or throws declarations”. Furthermore, if a library uses
>>>>> RuntimeExceptions where it should be using Exceptions there is no
>>>>> way to know if its API has changed except by reading the
>>>>> (hopefully up to date) documentation.
>>>>>
>>>>> Problem 2 makes me particularly bitter because JEE programmers
>>>>> seem to have learned that allowing code to throw null pointer
>>>>> exceptions has no real consequences for them so they become very
>>>>> cavalier about doing their null checks. The user sees an 500
>>>>> error page, the sys admin gets a 200 line stack trace in the log,
>>>>> but the system carries on. If you are lucky enough to have the
>>>>> source code to diagnose the problem, it usually turns out that
>>>>> the exception was thrown on a line with eight chained method
>>>>> calls. When you do track the problem down, it turns out you
>>>>> forgot a line in the properties file or something similar but the
>>>>> programmer couldn’t be bothered to help you out because it was
>>>>> easier just to let the null pointer exception happen.
>>>>
>>>> That's a pretty poor example, and in any case, the individual
>>>> cause would allow the system to continue on processing subsequent
>>>> requests, which is generally what's wanted. When you're working on
>>>> large systems and with large data sets, there are generally always
>>>> problematic items like this which have to be diagnosed
>>>> sufficiently in order to be retried, or even handled manually. It
>>>> has very little to do with the language and more to do with the
>>>> quality of data which isn't something you always have control
>>>> over.
>>>
>>> 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.
>>
>>>
>>>>
>>>>> 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.
>
> The main problem with the Java approach is that, by giving programmer
> errors precise, observable semantics in your program, they no longer
> become purely programmer errors. Writing a bunch of code in a `try {
> ... } catch (NullPointerException *) { ... }` block to swallow null
> checks gives you a well-defined Java program, so static analysis
> tooling that tries to flag these problems has to contend with false
> positive "intended" uses of runtime errors. It also means error checks
> can never be optimized around or removed without breaking valid
> programs.
It also means, from a purely pragmatic view, that control flow for
things that are really not reliably recoverable conditions (like index
out-of-range) is not cleanly separated from control flow for handling
recoverable things like dropped network conditions, so it can be really
hard to know that you're reliably shutting down the program when you
should, and that's not being subverted by some general “recovery” code.
--
-Dave
More information about the swift-evolution
mailing list