Composite atomic-like operations

I want to create operations that may fail, but there is a way to roll back.

For example, an external call to book a hotel room and an external call to pay by credit card. Both of these calls may fail, for example, there are no rooms left, an invalid credit card. Both have ways to lean back - cancel a hotel room, cancel a loan fee.

  • Is there a name for this type of (not real) atom. Whenever I look for a haskell transaction, I get STM.
  • Is there an abstraction, a way to build them, or a library in haskell or in any other language?

I feel that you can write a monad Atomic Tthat will track these operations and roll them back if there is an exception.

Edit:

These operations may be IO. If the operations were only memory operations, as the two answers show, STM will suffice.

For example, hotel reservations will be through HTTP requests. Database operations, such as inserting records through a socket connection.

In the real world, there is a grace period for irreversible operations before the operation is completed. credit card payments and hotel reservations can be settled at the end of the day, and therefore, until this point, the penalty is canceled.

+5
source share
4 answers

If you need to resort to creating your own monad, it will look something like this:

import Control.Exception (onException, throwIO)

newtype Rollbackable a = Rollbackable (IO (IO (), a))

runRollbackable :: Rollbackable a -> IO a
runRollbackable (Rollbackable m) = fmap snd m
    -- you might want this to catch exceptions and return IO (Either SomeException a) instead

instance Monad Rollbackable where
    return x = Rollbackable $ return (return (), x)
    Rollbackable m >>= f
       = do (rollback, x) <- m
            Rollbackable (f x `onException` rollback)

(You may also need instances of Functorand Applicative, but they are trivial.)

:

rollbackableChargeCreditCard :: CardNumber -> CurrencyAmount -> Rollbackable CCTransactionRef
rollbackableChargeCreditCard ccno amount = Rollbackable
   $ do ref <- ioChargeCreditCard ccno amount
        return (ioUnchargeCreditCard ref, ref)

ioChargeCreditCard :: CardNumber -> CurrencyAmount -> IO CCTransactionRef
-- use throwIO on failure
ioUnchargeCreditCard :: CCTransactionRef -> IO ()
-- these both just do ordinary i/o

:

runRollbackable
   $ do price <- rollbackableReserveRoom roomRequirements when
        paymentRef <- rollbackableChargeCreditCard ccno price
        -- etc
+5

TVar , STM .

(, " 100 " ), , (, " 100 " ), , : Control.Exceptions.bracketOnError

bracketOnError
        :: IO a         -- ^ computation to run first (\"acquire resource\")
        -> (a -> IO b)  -- ^ computation to run last (\"release resource\")
        -> (a -> IO c)  -- ^ computation to run in-between
        -> IO c         -- returns the value from the in-between computation

Control.Exception.bracket, , , .

, , :

let safe'charge'Bob = bracketOnError (charge'Bob) (\a -> refund'Bob)

safe'charge'Bob $ \a -> do
   rest'of'transaction
   which'may'throw'error

, , Control.Exception.mask, , .

, Control.Exception Control.Exception.Base, , GHC.

+1

You can really do it with the smart STM app. The key is to separate the I / O parts. I assume that the problem is that the transaction may seem successful at the initial stage and fail only later. (If you can immediately recognize the refusal, or soon, everything will be easier):

main = do
   r   <- reserveHotel
   c   <- chargeCreditCard

   let room         = newTVar r
       card         = newTVar c
       transFailure = newEmptyTMVar

   rollback <- forkIO $ do 
       a <- atomically $ takeTMVar transFailure --blocks until we put something here
       case a of
         Left  "No Room"       -> allFullRollback
         Right "Card declined" -> badCardRollback

  failure <- listenForFailure -- A hypothetical IO action that blocks, waiting for
                              -- a failure message or an "all clear"
  case failures of
      "No Room"       -> atomically $ putTMVar (Left "No Room")
      "Card Declined" -> atomically $ putTMVar (Right "Card declined")
      _               -> return ()

Now there is nothing MVars could handle: all we do is branch the thread to wait and see if we need to fix it. But you are likely to do something else with your card fees and hotel reservations ...

0
source

All Articles