From: Vladimir Zolotykh
Subject: design question
Date: 
Message-ID: <3C74CA88.A14A615@eurocom.od.ua>
Let me consider some package contents of which I can't change (I have
no sources for it or it is too hard for me to understand). This
package has functions and macros which deal with database.

For example, function QUERY performs query on database object. The
macro DO-QUERY also performs query on database and iterate thru all
read tuples binding specified variables.

Suppose I have some background process I don't want pay much attention
to. This process repeatedly does something with database. If for
example I reboot database server the connection to database will
lost. I must manually reestablish it in the client.

My intention is. In minimal efforts and at regular manner solve this
problem. I mean modify this client program such way that it could be
possible for it to reestablish connection to database when it sees it
is lost.

Solution that doesn't modify all calls to QUERY, DO-QUERY etc. seems
preferable.

Now I could think about two solutions:

1. Create some subprocess in client that each say 5 minutes tests the
   connection. If test fails it reestablish it.

2. Write macros for QUERY, DO-QUERY which wrap actual calls to QUERY,
   DO-QUERY with HANDLER-CASE catching database errors and
   reestablishing connections. This of course has drawbacks: I need
   check all calls to database in my sources.

-- 
Vladimir Zolotykh

From: Jochen Schmidt
Subject: Re: design question
Date: 
Message-ID: <a52j4n$faq$1@rznews2.rrze.uni-erlangen.de>
Vladimir Zolotykh wrote:

> 1. Create some subprocess in client that each say 5 minutes tests the
>    connection. If test fails it reestablish it.
> 
> 2. Write macros for QUERY, DO-QUERY which wrap actual calls to QUERY,
>    DO-QUERY with HANDLER-CASE catching database errors and
>    reestablishing connections. This of course has drawbacks: I need
>    check all calls to database in my sources.

Another approach could be to let the database server notify all clients
when it shuts down and starts up. Your application could than stop and 
reestablish the connections. How well this works depends on the 
notification mechanism you choose. It depends on how your application is 
organized too. If you cannot centrally reestablish all database connections
this will not work.

Yet another approach would be to inspect the db interface for generic 
functions that define a kind of querying protocol. Then you could subclass
the database class and mix in an :before method which reestablishes the 
connection if it is down. You could find this generic functions by 
macroexpanding DO-QUERY for example.

ciao,
Jochen

--
http://www.dataheaven.de
From: Pierre R. Mai
Subject: Re: design question
Date: 
Message-ID: <87wux63awu.fsf@orion.bln.pmsf.de>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> My intention is. In minimal efforts and at regular manner solve this
> problem. I mean modify this client program such way that it could be
> possible for it to reestablish connection to database when it sees it
> is lost.
> 
> Solution that doesn't modify all calls to QUERY, DO-QUERY etc. seems
> preferable.
> 
> Now I could think about two solutions:
> 
> 1. Create some subprocess in client that each say 5 minutes tests the
>    connection. If test fails it reestablish it.
> 
> 2. Write macros for QUERY, DO-QUERY which wrap actual calls to QUERY,
>    DO-QUERY with HANDLER-CASE catching database errors and
>    reestablishing connections. This of course has drawbacks: I need
>    check all calls to database in my sources.

You can use the package system to do this transparently, i.e. without
the need to modify your sources.  Shadow the symbols QUERY, DO-QUERY,
etc. in your application package (which uses the package that QUERY,
etc. reside in).  Then define the replacements in terms of the
original macros/functions.

I.e. assume that QUERY, etc. reside in package MAISQL, and that your
application package is named MY-APP.  Then

(defpackage :MY-APP (:use :CL :MAISQL)
  (:shadow #:QUERY #:DO-QUERY ...)
  ...)

(in-package :MY-APP)

(defmacro query (&rest args)
  (let ((repeat-tag (gensym)))
    `(tagbody
      ,repeat-tag
        (restart-case (maisql:query ,@args)
          (retry ()
            :report "Retry query operation."
            (go ,repeat-tag))))))

...

Now each occurence of QUERY in that package will wrap the real query
operation with a restart that can be used to reexecute the query.  A
global condition handler can then be used to handle errors that result
from dead db connections, which reestablish the connection, and then
invoke the retry restart, to continue execution.

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Vladimir Zolotykh
Subject: Re: design question
Date: 
Message-ID: <3C761F4D.A4999EA3@eurocom.od.ua>
"Pierre R. Mai" wrote:
> 
> Now each occurence of QUERY in that package will wrap the real query
> operation with a restart that can be used to reexecute the query.  A
> global condition handler can then be used to handle errors that result
> from dead db connections, which reestablish the connection, and then
> invoke the retry restart, to continue execution.

Are global condition handlers exist ? Probably exist if you say
so. Would you mind to explain how it could be done ? As I know all
such handlers exist in dynamic environment relative to the scope of
the binding form (e.g HANDLER-CASE, HANDLER-BIND).

Probably condition handler should be made manually and putted in
dynamic environment but I don't know how.

-- 
Vladimir Zolotykh
From: Martin Simmons
Subject: Re: design question
Date: 
Message-ID: <3c762a71$0$231$ed9e5944@reading.news.pipex.net>
"Vladimir Zolotykh" <······@eurocom.od.ua> wrote in message
······················@eurocom.od.ua...
> "Pierre R. Mai" wrote:
> >
> > Now each occurence of QUERY in that package will wrap the real query
> > operation with a restart that can be used to reexecute the query.  A
> > global condition handler can then be used to handle errors that result
> > from dead db connections, which reestablish the connection, and then
> > invoke the retry restart, to continue execution.
>
> Are global condition handlers exist ? Probably exist if you say
> so. Would you mind to explain how it could be done ? As I know all
> such handlers exist in dynamic environment relative to the scope of
> the binding form (e.g HANDLER-CASE, HANDLER-BIND).
>
> Probably condition handler should be made manually and putted in
> dynamic environment but I don't know how.

CL doesn't have global condition handlers.
--
Martin Simmons, Xanalys Software Tools
······@xanalys.com
rot13 to reply
From: Pierre R. Mai
Subject: Re: design question
Date: 
Message-ID: <87sn7ty89p.fsf@orion.bln.pmsf.de>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> "Pierre R. Mai" wrote:
> > 
> > Now each occurence of QUERY in that package will wrap the real query
> > operation with a restart that can be used to reexecute the query.  A
> > global condition handler can then be used to handle errors that result
> > from dead db connections, which reestablish the connection, and then
> > invoke the retry restart, to continue execution.
> 
> Are global condition handlers exist ? Probably exist if you say
> so. Would you mind to explain how it could be done ? As I know all
> such handlers exist in dynamic environment relative to the scope of
> the binding form (e.g HANDLER-CASE, HANDLER-BIND).

Sorry for being not as clear as I could:  With global handlers I
didn't mean something like "define-handler", but rather a handler-bind
form that you wrap around the call to your "toplevel" function(s), so
that its dynamic extent covers the whole application run-time (is
"global", so to speak).

E.g. if your application is started by a call to start-me, you can do

(defun start-me ()
  (handler-bind ((database-error #'handle-database-error))
    ;; The stuff that you normally do in start-me
    ))

(defun handle-database-error (condition)
  ;; Find applicable retry restart
  (let ((restart (find-restart 'retry condition)))
    ;; Check if restart is available and error was caused by lost connection:
    (when (and restart ...)
      ;; Reestablish connection
      ...
      ;; Invoke retry restart
      (invoke-restart restart))))

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Vladimir Zolotykh
Subject: Re: design question
Date: 
Message-ID: <3C765997.A56B384A@eurocom.od.ua>
"Pierre R. Mai" wrote:
> 
> (defun handle-database-error (condition)
>   ;; Find applicable retry restart
>   (let ((restart (find-restart 'retry condition)))

Is 

  (find-restart 'retry condition)

correct ? 

I could think MY-APP:QUERY macro doesn't associate
restart RETRY with any condition. Please correct me if
I've mistaken.

-- 
Vladimir Zolotykh
From: Kent M Pitman
Subject: Re: design question
Date: 
Message-ID: <sfwr8nd8sfb.fsf@shell01.TheWorld.com>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> "Pierre R. Mai" wrote:
> > 
> > (defun handle-database-error (condition)
> >   ;; Find applicable retry restart
> >   (let ((restart (find-restart 'retry condition)))
> 
> Is 
> 
>   (find-restart 'retry condition)
> 
> correct ? 
> 
> I could think MY-APP:QUERY macro doesn't associate
> restart RETRY with any condition. Please correct me if
> I've mistaken.

It doesn't matter.  And, by the way, I don't think the CLHS entry for
FIND-RESTART could be much more clear on this issue, so I'll just
quote what it says rather than try to do better:
  
 | When condition is non-nil, only those restarts are considered that
 | are either explicitly associated with that condition, or not
 | associated with any condition; that is, the excluded restarts are
 | those that are associated with a non-empty set of conditions of
 | which the given condition is not an element. If condition is nil,
 | all restarts are considered.

I've never found a case where always specifying the condition argument 
to FIND-RESTART got me into trouble.
From: Vladimir Zolotykh
Subject: Re: design question
Date: 
Message-ID: <3C766353.A068D5E4@eurocom.od.ua>
Kent M Pitman wrote:

>  | When condition is non-nil, only those restarts are considered that
>  | are either explicitly associated with that condition, or not
>  | associated with any condition; that is, the excluded restarts are
>  | those that are associated with a non-empty set of conditions of
>  | which the given condition is not an element. If condition is nil,
>  | all restarts are considered.

Of course it is clear. But each clearness take some time to become
obvious. Now I see that Pierre's form is quite correct.

-- 
Vladimir Zolotykh
From: Alain Picard
Subject: Re: design question
Date: 
Message-ID: <86vgcp6840.fsf@gondolin.local.net>
Vladimir Zolotykh <······@eurocom.od.ua> writes:

> 2. Write macros for QUERY, DO-QUERY which wrap actual calls to QUERY,
>    DO-QUERY with HANDLER-CASE catching database errors and
>    reestablishing connections. This of course has drawbacks: I need
>    check all calls to database in my sources.

But that's not too bad, is it?  You may have more than one type
of DB, for example, so in my app most "entry points" tend to
look like this;

(corba:define-method foo ((obj some-servant-class) args)
  (with-global-database
    (with-transaction
        (frob obj args))))

Now, the DB may get used many calls down from FROB; we don't care.
The design pretty much says that at the nuts and bolts level, methods
like FROB, FRAB, etc can just assume that *default-database* is always
available and bound to the "correct" one.

This would expand into something like:

(let ((*default-database* *some-global-db*))
   (handler-bind ((sql:fatal-error 
                    #'(lambda (error)
                        (declare (ignore error))
                        (reconnect-some-global-db))))
  (frob obj args))

The call fails, of course, but causes the process to obtain a new
connection.  (reconnect-some-global-db can spawn a new thread to
retry until it succeeds).

If you need to retry the FROB form, you can do the restart games
described by Pierre Mai.

What do you think?

-- 
It would be difficult to construe        Larry Wall, in  article
this as a feature.			 <·····················@netlabs.com>
From: Vladimir Zolotykh
Subject: Re: design question
Date: 
Message-ID: <3C7661D7.578623CF@eurocom.od.ua>
I'd say Pierre's solution suits me better for now. Probably because
the modifications seems be less. Well planned program shouldn't come
to needs of hiding symbols etc. Here I agree with you. If I had had
chance to think of it before I could choose more regular approach.

-- 
Vladimir Zolotykh