Exception Handling

Lecture Notes for CS 190
Winter 2018
John Ousterhout

  • Reading: Chapter 9 of book
  • Exceptions come from many sources:
    • From above:
      • Bad input from user or client
      • Misconfiguration and operator errors
    • From below: underlying system facilities don't work as desired:
      • Disk I/O error
      • Can't open file (wrong permissions, missing directory, etc.)
      • Out of memory
      • Network socket already in use
    • From peers in a distributed system:
      • Server crashes
      • Slow communication
      • Lost network packets
    • From ourselves: internal bugs
  • Errors and exceptions are a major source of complexity and bugs
    • They account for a lot of code in large systems
    • They disrupt normal code flow:
      • They happen in the middle of other activities
      • Something didn't work like you expected
    • Hard to figure out how to handle them
      • May not be able to complete work in progress
      • Cleanup/recovery may be tricky
    • Language support is clunky
      • Verbose
      • Makes code hard to read
    • Hard to test
    • Don't occur very often in running applications
      • May not work when needed
  • Programmers often make the exception problem worse:
    • Defensive programming: throw exceptions for anything that looks a tiny bit suspicious. More exceptions are better?
    • Expediency: rather than figure out how to solve a problem, just throw an exception, punt it to the next level
    • Result: even more exceptions, many of which no-one really knows how to handle.
  • Key idea: reduce the number of places where exceptions must be handled.
  • Specific techniques:
    • Whenever possible, define errors out of existence:
      • Deleting variables in Tcl
      • File deletion in Windows
      • Bounds checks in Java substring method
    • Mask exceptions (recover automatically so the exception doesn't have to be reported)
    • Collapse exceptions (handle several different cases with the same code)
      • Promote one exception to another (in RAMCloud, many exceptions get promoted to "server crash").
        • Reuse existing handler
        • May be too inefficient for exceptions that happen frequently
      • Defer reporting to a place where other exceptions could already happen.
        • Example: in RAMCloud, report RPC exceptions only on wait, not send.
        • Or, handle exception once in a higher-level method, rather than multiple times in lower-level methods.
      • Group exceptions into categories according to how they will be handled.
        • Find a code structure where each category is only handled in one place.
      • Advantages of collapsing:
        • Simplifies code (fewer cases).
        • Remaining handlers get invoked more often (will get debugged).
    • Just panic (crash app)?
      • If there's not a viable way to handle it
      • Example: running out of memory in malloc
      • Or, throw an unchecked exception, which isn't handled except at the very top level.
  • If an exception can't be masked, it can be reported in 4 ways:
    • Log a message and continue
    • Return a special value
    • Checked exception (caller must deal with it)
    • Unchecked exception
  • Think about how the caller will handle it:
    • If the caller will almost always care about the exception, might as well report it with a return value.
    • If caller will almost never be able to recover from it, use an unchecked exception
  • Exceptions work better the farther they are thrown.
  • Make lots of specific information available after exceptions:
    • Include in exception message?
    • Additional fields of exception?
    • Or, output to system log
  • Assertions versus exceptions:
    • Assertions are for things that should absolutely never happen in production
    • An assertion failure means there is a bug in the code
    • Assertion checking can be disabled during production for highest performance
  • How much internal error checking should an application do?
    • One philosophy: check everything (tons of assertions)
    • I tend to use assertions fairly lightly (assume that unit tests will flush out most bugs)
    • Must always check "less trusted" input
    • Internal consistency checker (e.g. for B-tree structure)