[Haskell] Re: ANN: HDBC (Haskell Database Connectivity)
A.Simon at kent.ac.uk
Mon Jan 9 04:03:13 EST 2006
Keean et al,
On Sun, 2006-01-08 at 14:51 +0000, Keean Schupke wrote:
> My solution to this when developing a database library for my own use
> was to define the API
> in a bracket notation style, and only provide safe functions. The idea
> is that the function obtains the resource, calls a function passed as an
> argument, then frees the resource, so all resouces are guaranteed to be
> freed in the correct order... for example:
> dbConnectWith :: DbName -> (DbHandle -> IO Result) -> Result
> dbConnectWith name workFn = do
> handle <- dbConnectTo name
> workFn handle `finally` dbDisconnect handle
> In this way you avoid finalizers... and everthing is safe providing you
> only export the "with" style functions from the library... Here's an
> example from the library, the connect function:
I suppose you meant to write "result" rather than "Result". This style
of functions is only safe if the user ensures that DbHandle is never
returned as part of the result. You should have that in your
As far as I can tell, the only general solution is to use finalizers
and, if you really need to enforce a sequence of finialization,
touchForeignPtr. A practical issue with touchForeignPtr is that it
cannot be conveniently called from another finalizer, since the latter
live in C. I can think of three solutions:
a) You keep all pointers to your database (handles, connections, etc) in
one C structure and keep one ForeignPtr to that. On deallocation, you
call a custom C function that frees all the objects in the C data
structure in the right sequence.
b) You wrap all C data structures in reference-counting objects. On
creation of a connection you increment the reference count of the
database. Each foreign pointer you create will also increment the
reference count. On deallocation of either, the reference count is
decremented and the object freed if it has dropped to zero.
c) In your special case you could wrap the data base in a StablePtr
whenever you create the first connection and subsequently count the
number of connections you create. You free the StablePtr when there are
no more connections.
Solution b) might be impossible to implement if the library itself frees
the pointers without consulting your reference count first. If the
library frees objects without asking you first, but allows some sort of
finalizers, then solution a) makes it possible to at least mark the
object as invalid (which all your Haskell functions would have to
check). Otherwise solution a) is a bit inefficient, especially if your
library calls Haskell functions and passes points to them -- in this
case you need to check if this points is already in your data structure
and insert it if necessary. Solution c) might be the easiest in your
case where the data base always outlives connections and pointers to
connections are never passed to Haskell except when they are created.
> Chris Kuklewicz wrote:
> >Benjamin Franksen wrote:
> >>On Wednesday 04 January 2006 20:13, John Goerzen wrote:
> >>>Well, yes and no. It would be impossible to garbage collect (and
> >>>thus finalize) any object for which references to it still exist.
> >>>Statement handles in HDBC maintain references to the database handle
> >>>pointers, either directly or indirectly, so I can't see how it is
> >>>possible for a database handle to be finalized before the statement
> >>>handle in this situation.
> >>Hi John,
> >>I fear it /is/ possible. This is a very unfortunate situation and one I
> >>had quite some difficulties to understand, when Simon Marlow explained
> >>it to me.
> >>The problem is that finalization of the statement handle might be
> >>delayed indefinitely. The data dependencies between statement and
> >>connection handle only ensures that whenever the statement handle is
> >>alive, then too is the connection handle. But it does not say anything
> >>about what happens in which order after /both/ are dead (garbage). As
> >>soon as the connection handle to garbage, too, bothe handles can be
> >>finalized in /any/ order.
> >>As I pointed out before, this is a very bad thing, because it makes
> >>finalizers a whole lot less useful than they could be if an order
> >>between finalizations could be specified (directly or indirectly). The
> >>arguments against such a solution are mostly: (1) it is difficult to
> >>implement efficienty and (2) the programmer could accidentally cause
> >>finalizer deadlocks by specifying circular dependencies.
> >This is also mentioned in the documentation:
> >>touchForeignPtr :: ForeignPtr a -> IO ()
> >>This function ensures that the foreign object in question is alive at the given place in the sequence of IO actions. In particular withForeignPtr does a touchForeignPtr after it executes the user action.
> >>Note that this function should not be used to express liveness dependencies between ForeignPtrs. For example, if the finalizer for a ForeignPtr F1 calls touchForeignPtr on a second ForeignPtr F2, then the only guarantee is that the finalizer for F2 is never started before the finalizer for F1. They might be started together if for example both F1 and F2 are otherwise unreachable, and in that case the scheduler might end up running the finalizer for F2 first.
> >>In general, it is not recommended to use finalizers on separate objects with ordering constraints between them. To express the ordering robustly requires explicit synchronisation using MVars between the finalizers, but even then the runtime sometimes runs multiple finalizers sequentially in a single thread (for performance reasons), so synchronisation between finalizers could result in artificial deadlock.
> >Haskell mailing list
> >Haskell at haskell.org
> Haskell mailing list
> Haskell at haskell.org
More information about the Haskell