From: Vladimir Zolotykh
Subject: using HANDLER-CASE
Date: 
Message-ID: <3BD2DDD0.15DD82C3@eurocom.od.ua>
Please consider the following small piece of code. Its aim (at least I
hope it'll do so) is to ensure that several database operations will
be done in consistent manner, e.g. will be completed all or neither of
them. This can be done by wrapping these actions in a transaction
(BEGIN, COMMIT, ROLLBACK). If error of any kind take place during a
transaction I'd like to discard (rollback) all actions and complete
them (commit) otherwise. My question is how to do that on CL properly.

If some error happens inside a transaction I'd like not to stop
signaling the error but let it go (re-signal it?).

Another question. Is it possible from some deep level to go
directly to the top level of the Listener without entering the debugger,
as command Q does in CMUCL or :res in ACL ?

;; If *test-mode* is T just print the commands to database
;; backend, doesn't actually perform it.

;; SQL-UPDATE (method on GF) and SQL-UPDATE-SESSIONS
;; issue UPDATE commands to the backend unless 
;; were called with :PRINT T.

(unless *test-mode*
  (execute-command "begin"))
(handler-case
    (progn
      (maphash #'(lambda (order-id order)
                   (declare (ignore order-id))
                   (when (data-modified-p order)
                     (sql-update order :print *test-mode*)))
               orders)
      (sql-update account :print *test-mode*)
      (sql-update-sessions sessions :print *test-mode*))
  (condition (condition)
    ;; Nothing serious happens if we issue
    ;; ROLLBACK or COMMIT w/o a preceding BEGIN.
    (execute-command "rollback")
    (error condition))
  (:no-error (condition)
    ;; Sould it be possible to write :no-error () ... ?
    (declare (ignore condition))
    (execute-command "commit")))
;; Continue processing

.......

-- 
Vladimir Zolotykh                         ······@eurocom.od.ua

From: Kent M Pitman
Subject: Re: using HANDLER-CASE
Date: 
Message-ID: <sfwlmi5ors9.fsf@world.std.com>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> Please consider the following small piece of code. Its aim (at least I
> hope it'll do so) is to ensure that several database operations will
> be done in consistent manner, e.g. will be completed all or neither of
> them. This can be done by wrapping these actions in a transaction
> (BEGIN, COMMIT, ROLLBACK). If error of any kind take place during a
> transaction I'd like to discard (rollback) all actions and complete
> them (commit) otherwise. My question is how to do that on CL properly.
> 
> If some error happens inside a transaction I'd like not to stop
> signaling the error but let it go (re-signal it?).

You can do so, but it's really not the right way to do it.
 
> Another question. Is it possible from some deep level to go
> directly to the top level of the Listener without entering the debugger,
> as command Q does in CMUCL or :res in ACL ?

All of these probably use restarts.  ABORT will generally take you to
the innermost command level.  As a rule, you should not try to tranfer
through more than one such level or you will violate normal interactive
stack discipline.

One way to do this would be, for example:

(handler-bind ((error #'(lambda (c) (declare (ignore c)) (abort))))
  ...)
 
> ;; If *test-mode* is T just print the commands to database
> ;; backend, doesn't actually perform it.
> 
> ;; SQL-UPDATE (method on GF) and SQL-UPDATE-SESSIONS
> ;; issue UPDATE commands to the backend unless 
> ;; were called with :PRINT T.
> 
> (unless *test-mode*
>   (execute-command "begin"))
> (handler-case
>     (progn
>       (maphash #'(lambda (order-id order)
>                    (declare (ignore order-id))
>                    (when (data-modified-p order)
>                      (sql-update order :print *test-mode*)))
>                orders)
>       (sql-update account :print *test-mode*)
>       (sql-update-sessions sessions :print *test-mode*))
>   (condition (condition)

I seriously don't recommend handling CONDITION here.
CONDITION can include all kinds of intended-to-be-nonfatal things
that will proceed fine without user intervention.
Usually ERROR is the right condition, and sometimes SERIOUS-CONDITION is,
but CONDITION seems overgeneral.

Frankly, I'm not sure you want HANDLER-CASE at all here.
I think  you'd be better off in an UNWIND-PROTECT.
What do you want to happen when there's a non-local tranfer of control
out of this code (e.g., by THROW).  Presently you don't either commit or
rollback.

>     ;; Nothing serious happens if we issue
>     ;; ROLLBACK or COMMIT w/o a preceding BEGIN.
>     (execute-command "rollback")
>     (error condition))
>   (:no-error (condition)
>     ;; Sould it be possible to write :no-error () ... ?
>     (declare (ignore condition))
>     (execute-command "commit")))
> ;; Continue processing
> 
> .......

This kind of control structure seems more reliable and doesn't involve
dealing with conditions at all, which I think are a red herring.

(let ((success nil))
  (unwind-protect (progn ... do the deed ...
                         (setq success t))
    (if success
        (execute-command "commit")
        (execute-command "rollback"))))
From: Thomas F. Burdick
Subject: Re: using HANDLER-CASE
Date: 
Message-ID: <xcvzo6j73hb.fsf@famine.OCF.Berkeley.EDU>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> Please consider the following small piece of code. Its aim (at least I
> hope it'll do so) is to ensure that several database operations will
> be done in consistent manner, e.g. will be completed all or neither of
> them. This can be done by wrapping these actions in a transaction
> (BEGIN, COMMIT, ROLLBACK). If error of any kind take place during a
> transaction I'd like to discard (rollback) all actions and complete
> them (commit) otherwise. My question is how to do that on CL properly.

I'm guessing you're thinking in C++ (or maybe Java?).  In lisp, we
don't do:

try { ... }
catch (...) { ... throw; }

Instead, we have UNWIND-PROTECT:

(unwind-protect
    (progn ...)
  ... )

If you find yourself thinking in terms of "catch this condition, do
something, then re-signal it so it can be handled however the calling
code wants to handle it," what you're really thinking of is a
hand-rolled UNWIND-PROTECT.  Figure out some way to do it with
UNWIND-PROTECT, and you've probably found the Right Way.  If you can't
some up with a way to transform what you're thinking of to an
UNWIND-PROTECT, then ask for help again :)

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Vladimir Zolotykh
Subject: Re: using HANDLER-CASE
Date: 
Message-ID: <3BD55AA9.3B98431F@eurocom.od.ua>
"Thomas F. Burdick" wrote:
> 
> I'm guessing you're thinking in C++ (or maybe Java?).  In lisp, we
> don't do:
> 
> try { ... }
> catch (...) { ... throw; }

You're right. I used C++ for a long time. And such experience
still dictates way of my programming thoughts.

> 
> Instead, we have UNWIND-PROTECT:
> 
> (unwind-protect
>     (progn ...)
>   ... )

I've end up with the following:

(defmacro with-transaction ((db) &body body)
  (let ((abort (gensym)))
    `(let ((,abort t))
       (unwind-protect
	   (progn
	     (begin-transaction ,db)
	     (multiple-value-prog1
		 (progn ,@body)
	       (commit-transaction ,db)
	       (setq ,abort nil)))
	 (when ,abort (rollback-transaction ,db))))))

Paul Foley suggested it and as I can decide this is pure
Lisp style.

-- 
Vladimir Zolotykh                         ······@eurocom.od.ua
From: ···@itasoftware.com
Subject: Re: using HANDLER-CASE
Date: 
Message-ID: <y9m272kj.fsf@itasoftware.com>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> I've end up with the following:
> 
> (defmacro with-transaction ((db) &body body)
>   (let ((abort (gensym)))
>     `(let ((,abort t))
>        (unwind-protect
> 	   (progn
> 	     (begin-transaction ,db)
> 	     (multiple-value-prog1
> 		 (progn ,@body)
> 	       (commit-transaction ,db)
> 	       (setq ,abort nil)))
> 	 (when ,abort (rollback-transaction ,db))))))
> 

You have a race condition between the commit-transaction and setting
the abort symbol to T.

You should make sure that calling rollback-transaction is ok if you
haven't begun one (you may get an asynchronous abort prior to
finishing begin).
From: Barry Margolin
Subject: Re: using HANDLER-CASE
Date: 
Message-ID: <f_gB7.11$mE.519@burlma1-snr2>
In article <············@itasoftware.com>,  <···@itasoftware.com> wrote:
>Vladimir Zolotykh <······@eurocom.od.ua> writes:
>
>> I've end up with the following:
>> 
>> (defmacro with-transaction ((db) &body body)
>>   (let ((abort (gensym)))
>>     `(let ((,abort t))
>>        (unwind-protect
>> 	   (progn
>> 	     (begin-transaction ,db)
>> 	     (multiple-value-prog1
>> 		 (progn ,@body)
>> 	       (commit-transaction ,db)
>> 	       (setq ,abort nil)))
>> 	 (when ,abort (rollback-transaction ,db))))))
>> 
>
>You have a race condition between the commit-transaction and setting
>the abort symbol to T.
>
>You should make sure that calling rollback-transaction is ok if you
>haven't begun one (you may get an asynchronous abort prior to
>finishing begin).

Depending on the features provided by the database system, you may be able
to simplify the macro *and* solve that race condition:

(defmacro with-transaction ((db) &body body)
  `(unwind-protect
       (multiple-value-prog1
         (progn (begin-transaction ,db)
                ,@body)
         (commit-transaction ,db))
     (when (transaction-in-progress ,db)
       (rollback-transaction ,db))))

The transaction itself serves as the flag indicating whether the code has
been aborted.

Note, however, that none of these will not do the right thing if you nest
WITH-TRANSACTION forms.  If BEGIN-TRANSACTION signals an error because of
the attempt, TRANSACTION-IN-PROGRESS will be true and then you'll roll back
the containing transaction.  And if BEGIN-TRANSACTION doesn't complain,
you'll end up committing the changes made in the outer transaction, so a
subsequent error in the calling function will not be able to roll back what
it did.

The simplest fix is to have WITH-TRANSACTION check whether a transaction is
in progress itself before entering into the UNWIND-PROTECT.

-- 
Barry Margolin, ······@genuity.net
Genuity, Woburn, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.
Please DON'T copy followups to me -- I'll assume it wasn't posted to the group.