Tuesday, September 27, 2011

an unwinder should be in the standard library

The Unwinder is an implementation of the ScopeGuard pattern. The Unwinder is a class with a constructor that accepts a function and then runs that function in its destructor. The class has a method to dismiss the Unwinder. A dismissed unwinder will not run the function in its destructor. The Unwinder can be used as a generic implementation of the RRID (Resource Release Is Destruction) pattern.

With the addition of lambdas, auto and declspec to the C++ language the Unwinder becomes so trivial to use that in many cases no RAII class should be written. The implementation I use is called Unwinder.

Unwinder usage

int main()
{
  CRITICAL_SECTION lock = {};
  if (
    !InitializeCriticalSectionAndSpinCount(
        &lock,
        0x00000400)) {
    terminate();
  }

  ON_UNWIND_AUTO([&] {DeleteCriticalSection(&lock);});

  EnterCriticalSection(&lock);
  ON_UNWIND_AUTO([&] {LeaveCriticalSection(&lock);});

  // protected region

  {
    LeaveCriticalSection(&lock);
    ON_UNWIND_AUTO([&] {EnterCriticalSection(&lock);});

    // unprotected region
  }

  // protected region

  return 0;
}

Comparisons


This actually intersects with discussions of code style. There are many ways to structure function implementations. Of those there are a few common patterns that produce predictable results. Here are two structures; goto-cleanup and nested-scope:

int main()
{
  //
  // The goto-cleanup structure
  //

  CRITICAL_SECTION lock = {};
  BOOL inLock = FALSE;

  if (
    !InitializeCriticalSectionAndSpinCount(
        &lock,
        0x00000400)) {
    terminate();
  }

  EnterCriticalSection(&lock);
  inLock = TRUE;

  // protected region

  LeaveCriticalSection(&lock);
  inLock = FALSE;

  // unprotected region

  EnterCriticalSection(&lock);
  inLock = TRUE;

  // protected region

Cleanup:
  if (inLock) {
    LeaveCriticalSection(&lock);
    inLock = FALSE;
  }
  DeleteCriticalSection(&lock);
  return 0;
}
int main()
{
  //
  // The nested-scope structure
  //

  CRITICAL_SECTION lock = {};

  if (
    !!InitializeCriticalSectionAndSpinCount(
        &lock,
        0x00000400)) {
    {
      EnterCriticalSection(&lock);

      // protected region

      {
        LeaveCriticalSection(&lock);

        // unprotected region

        EnterCriticalSection(&lock);
      }

      // protected region

      LeaveCriticalSection(&lock);
    }

    DeleteCriticalSection(&lock);
  }

  return 0;
}

In both of these structures the code to undo or release an action is located a great distance from the action. Thus to read or verify the code one must scroll back and forth, even in short functions one must bounce visually all over the code to understand it.
Also, both of these are exception phobic. They require that exceptions are not used in the code.
Using the Unwinder, one is able to read left->right, top->bottom with no need to bounce. Each action is immediately followed by the undo. Even better, the code becomes exception agnostic, it will behave correctly with or without exceptions.

Implementation


The Unwinder is so subtle to build that it really should be in the standard. I have been through at least 4 implementations to reach my current one. One of the previous implementations was contributed by a coworker. His was based on std::function and did not require a macro to use correctly. After using it in my code for a while I pointed out that std::function could throw on construction and that this required changes to the implementation and made usage very tricky. After a year of periodic reviews of and changes to that implementation we finally decided to deprecate it, it was just too subtle and risky. The implementation included here does require macros, but is exception agnostic. This implementation is maintained here.

//
// common tools
//

#define MAKE_IDENTIFIER_EXPLICIT_PASTER(Prefix, Suffix) \
  Prefix ## Suffix

#define MAKE_IDENTIFIER_EXPLICIT(Prefix, Suffix) \
  MAKE_IDENTIFIER_EXPLICIT_PASTER(Prefix, Suffix)

#define MAKE_IDENTIFIER(Prefix) \
  MAKE_IDENTIFIER_EXPLICIT(Prefix, __LINE__)

#define FAIL_FAST_FILTER() \
  __except(FailFastFilter(GetExceptionInformation())) \
  { \
  } do {} while(0,0)

inline
LONG WINAPI FailFastFilter(
  __in  struct _EXCEPTION_POINTERS* exceptionInfo)
{
  RaiseFailFastException(
    exceptionInfo->ExceptionRecord,
    exceptionInfo->ContextRecord,
    0);
  return EXCEPTION_CONTINUE_SEARCH;
}

template<typename Function>
auto FailFastOnThrow(
  Function && function) -> decltype(
      std::forward<Function>(function)())
{
  //
  // __ try must be isolated in its own
  // function in order for the compiler
  // to reason about C++ unwind in the
  // calling and called functions.
  //
  __try {
    return std::forward<Function>(function)();
  }
  FAIL_FAST_FILTER();
}

#define FAIL_FAST_ON_THROW(Function) \
  FailFastOnThrow((Function))

//
// unwinder
//

template<typename Function>
class unwinder
{
public:
  ~unwinder() {
    if (!!function) {
      FAIL_FAST_ON_THROW([&] {(*function)();});
    }
  }

  explicit unwinder(Function* functionArg)
    : function(functionArg) {
  }

  void dismiss() {
    function = nullptr;
  }

private:
  unwinder();
  unwinder(const unwinder&);
  unwinder& operator=(const unwinder&);

  Function* function;
};

#define ON_UNWIND(Name, Function) \
  ON_UNWIND_EXPLICIT( \
      uwfunc_ ## Name, \
      Name, \
      Function)

#define ON_UNWIND_AUTO(Function) \
  ON_UNWIND_EXPLICIT( \
      MAKE_IDENTIFIER(uwfunc_), \
      MAKE_IDENTIFIER(unwind_), \
      Function)

#define ON_UNWIND_EXPLICIT(FunctionName, UnwinderName, Function) \
  auto FunctionName = (Function); \
  unwinder<decltype(FunctionName)> \
  UnwinderName(std::addressof(FunctionName))

No comments:

Post a Comment