Using scope guards to manage transactions

D doesn't require you to make wrapper types or write try/catch statements all the time. You can also use scope guards. They can be triggered by three conditions: exit, success, or failure. Here, we'll perform a multistep transaction with scope guards to ensure exception safety.

How to do it…

In order to use scope guards to manage transactions, perform the following steps:

  1. Begin your transaction.
  2. Write a scope(success) guard to commit the transaction immediately after starting it (if committing isn't implicit).
  3. Write a scope(failure) guard to roll back the transaction immediately after starting it. You may use multiple blocks to rollback multiple steps.
  4. Write a scope(exit) guard to free any resources. Write the free code immediately after the acquisition code. You may use multiple blocks to free multiple resources.
  5. Write the following code for your transaction:
    {
      // performing a SQL transaction
      database.query("START TRANSACTION");
      scope(success) database.query("COMMIT");
      scope(failure) database.query("ROLLBACK");
      database.query(step_one); // these should throw on failure
      database.query(step_two);
    }
    {
      // performing a multi-part file download
      auto connection = Connection.acquire();
      scope(exit) connection.close(); // release code immediatelyafter acquisition
      connection.download("file1");
      scope(failure) std.file.remove("file1"); // undo codeimmediately after perform code
      connection.download("file2");
      scope(failure) std.file.remove("file2");
    }

How it works…

D's scope guards are very useful to manage resources in an exception-safe way at the point of use. Each scope statement registers the code to be run on the given condition; success if the function returns without throwing an exception, failure if it exits by exception, or exit, which is unconditional. The code is executed in the reverse order of registration and registration happens with regular flow control, as shown in the following code snippet:

scope(exit) writeln("Running scope exit");
scope(success) writeln("Running scope success");
return;
scope(exit) writeln("This is never run since the function returned before it was registered.")

Running this code will print Running scope success and then Running scope exit.

Note

Implementation wise, scope(exit) is rewritten into a finally{} block, scope(success) is rewritten into code appended to the end of a try {} block, and scope(failure) is implemented at the end of a try catch {} block, which rethrows the original exception after running the handler.

The advantage of scope(success) over putting the commit code at the end of the function is that scope(success) is written once, but always run, even if you have multiple exit points (return statements) from the function.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.36.38