To be Reviewed By: August 28th, 2020

Authors: Patrick Johnson

Status: Draft | Discussion | Active | Dropped | Superseded

Superseded by: N/A

Related: ClassLoader Isolation

Problem

Typically Geode handles failure by exception; If an issue is encountered while executing code, an exception is thrown and propagated up until either it is caught or it crashes the system. It's too easy to miss an exception and allow it to crash Geode or swallow it without reporting the error at all. Exceptions may also be caught, wrapped, and rethrown multiple times on their way up the stack so, by the time they reach the surface, all context on the root cause of failure is lost. This isn't ideal because we obviously don't want the system to go down, but we definitely want to know when a failure occurs and what caused it. Since exception handling is dangerous and messy as well as expensive, it is best to avoid it when possible.

Anti-Goals

This proposal is concerned only with introducing a better way to handle failure and does not intend to...

  • Add or remove any exception types from Geode.
  • Remove all exception throwing, catching, or handling.

Solution

Fortunately, there are other ways to handle failure besides exceptions–just return a normal type that may contain either a result or an error. This isn't a new concept, errors are handled as normal return types in Go and even in parts of Geode. That's right, this idea is already being used by geode-protobuf and in the work related to the ClassLoader Isolation RFC. The proposed solution is to use this more functional approach to error handling in more places throughout Geode by introducing a new generic Result interface. A Result will represent the result of an operation and take two type parameters, a SuccessType and a FailureType. The SuccessType will be the expected result of the operation and the FailureType will be the type to return if the operation fails. The Result interface will have methods to...

  • Check if the operation succeeded or failed (isSuccessful() and isFailure())
  • Return the success result (SuccessType) assuming the operation succeeded
  • Return the error result (FailureType) assuming the operation failed

To make the Result simpler to use, there will be another interface called ServiceResult that will extend Result. ServiceResult will only take one type-parameter, SuccessType, and will use String as the FailureType to be able to return an error message. ServiceResult can also add convenience features such as lambda support.


There will be two concrete implementations of ServiceResult: Success and Failure. Success will contain a result of SuccessType and identity itself as successful when asked. Failure will contain an error message and identify itself as a failure. A method that uses this new way of handling results should return a Success with the result when the operation succeeds and a Failure with a meaningful error message when something goes wrong. The caller can then check whether the operation succeeded or failed and act accordingly by retrieving the result, or handling the error as they see fit (ignore it, print the error message, exit, recover, etc.). Note that trying to get a result from Failure or trying to get an error message from Success is not allowed and will result in an exception being thrown–you should check for success or failure before attempting to access the result or error message. While throwing an exception here isn't ideal, it's necessary for some circumstances.


ServiceResult makes it easy to indicate success or failure and pass back either a result or a useful message without throwing an exception.

Use Cases

Scenario 1: An operation completes successfully.

Expected behavior: A Success object is returned. Calling isSuccess() on the Success returns true and calling isFailure() returns false. The Success contains the result of the operation (SuccessType).


Scenario 2: An operation encountered an error.

Expected behavior: A Failure object is returned. Calling isFailure() on the Failure returns true and calling isSuccess() returns false. The Failure contains an error message describing the error.


Scenario 3: isSuccess() is called on a Success.

Expected behavior: true is returned.


Scenario 4: isSuccess() is called on a Failure.

Expected behavior: false is returned.


Scenario 5: isFailure() is called on a Success.

Expected behavior: false is returned.


Scenario 6: isFailure() is called on a Failure.

Expected behavior: true is returned.


Scenario 7: You attempt to retrieve the result from a Success.

Expected behavior: the result is returned.


Scenario 8: You attempt to retrieve the result from a Failure.

Expected behavior: a RuntimeException is thrown.


Scenario 9: You attempt to retrieve the error message from a Success.

Expected behavior: a RuntimeException is thrown.


Scenario 10: You attempt to retrieve the error message from a Failure.

Expected behavior: the error message is returned.

Changes and Additions to Public Interfaces

Addition of Result, ServiceResult, Success, and Failure. No changes to existing public interfaces.

Result API additions

Performance Impact

No anticipated performance impact.

Backwards Compatibility and Upgrade Path

No backward compatibility impact.

Prior Art

An alternative would be to continue handling errors by exception.

FAQ

Answers to questions you’ve commonly been asked after requesting comments for this proposal.

Errata

What are minor adjustments that had to be made to the proposal since it was approved?


  • No labels