oyasuminasai has asked for the wisdom of the Perl Monks concerning the following question:

Hello,

I am having 'problems' with a library for retrieving and saving objects from/to and relational database using DBI. The design in similar to, but simpler than the way that J2EE works.

My problem is this:
The main class in the whole library basically wraps a DBI connection to a database and provides additional services like object caching and maintaining uniqueness of objects based on their primary key fields. By default the 'gateway' object uses transactions and does a rollback when it is destroyed - this is so that you need to explicitly commit for your changes to take place. The problem is that if I use a global 'gateway' object it DESTROY method doesn't get called until the phase2 garbage collection. But if I don't use the global in any user defined subroutines it is cleaned up and the DESTROY method can successfully call rollback on the DBI handle.

The message I am getting otherwise is:

(in cleanup) cannot Can't call method "rollback" on an undefined value at /.../Gateway.pm line 94 during global destruction.

This is happening because the DBI object is gone when the DESTROY method of my gateway object is called. But this is curious because the gateway object has a reference to the DBI object.

If someone could point me to some detailed explanation of how GC works in perl I would greatly appreciate it - and please... I have read the perldocs and they are not sufficient.

Thanks - oyasuminasai

Replies are listed 'Best First'.
Re: Global objects and GC
by chromatic (Archbishop) on Mar 04, 2004 at 18:43 UTC

    When you say global, do you mean a lexical with package or file-level scope or do you mean a true global accessible through the symbol table?

    If the former, as I suspect, then Perl's reference counting GC may be tripping you up. In that case, every subroutine that refers to a lexical in an outer scope is a closure and increases the refcount of the lexical. At shutdown time, when Perl GCs the subroutines themselves, that reference count decreases.

    It sounds to me like you need a separate finalize() method to call explicitly, rather than waiting for DESTROY(). That way, you can avoid the rather messy and unspecified order of destruction errors I think you're having.

    Perl's global destruction is a race for the exit. Who knows what order things will happen?

      If oyasuminasai-san isn't using a lexical, that would be the first thing to try. Lexicals should always go away before globals.
      I had similar troubles with global destruction. I found this thread (Object reference disappearing during global destruction) and I thought I understood what was my error: to rely on the reference-count mechanism even during global destruction. Seeing the topic popping out again, I decided to do some experimentations in order to acquire a (?better) grasp on the subject. Here's the code:
      use strict; use warnings; package Bar; use Devel::Peek 'Dump'; sub new { bless [], shift } sub bar { print "Hi, I'm a bar instance\n\n" } sub dump { print "This is my ID card:\n"; Dump( shift ) } package Foo; sub new { my $bar = Bar->new(); bless { bar => $bar }, shift; } sub DESTROY { my $self = shift; $self->{ bar }->bar(); $self->{ bar }->dump(); } package main; my $f = Foo->new();
      My expectation was to see a message like Can't call method "bar" on an undefined value with unpredictable frequence. I run the script several times, without warnings. Since this is a semidecidible issue, and I don't have all that time :), I ask if my understanding is correct, and further explanations if appropriate. Thank you.

        At the end of the program, I'd expect $f to go out of scope first, calling its DESTROY. Since it contains a reference to a Bar, the contained object should still exist, so things are okay.

        If $f were global or if it had more than one reference, things might be stickier.

      It is a lexical with file level scope. And I suspected that the closures were incrementing the reference count... that's why I tried removing those closures to see what happenned. I guess I was trying to avoid forcing the user to remember to call the finalize method - the people that will use my library are not what you would call expert Perl programmers and I wanted to make it so that changes were not committed unless they knew what they were doing... Thank you for your help - I will most likely just throw in the finalize and perldoc it that way.

      oyasuminasai
Re: Global objects and GC
by waswas-fng (Curate) on Mar 04, 2004 at 18:22 UTC
    I may be missing the point, but wouldn't you want to have the transaction logic/rollback happen before the DESTROY? How are you informing the callers that the transaction failed? Or are you using rollback to enforce commits for the callers in their logic?


    -Waswas
      "Or are you using rollback to enforce commits for the callers in their logic?" -> Yes.
Re: Global objects and GC
by diotalevi (Canon) on Mar 04, 2004 at 22:59 UTC
    Gee, I'd just use Alzabo and be done with it. No re-implementation of a OO-RDBMS wrapper required.
      Thanks for the tip, I took a look at it, but one of the primary goals was to keep the library lightweight and targetted towards the enterprise's concerns. Alzabo looks very complete, but also very large not centered on this company's uses for it.

      By your reasoning it sounds as though you wouldn't agree to people writing and XML parser when there is already XML::Xerces and the like. Sometimes you need something designed in tandem with your specific problems in mind. But I don't want to sound ungrateful, I appreciate the tip.

      oyasuminasai
        Speaking as a user of Xerces (in Java) and now a handful of other parsers on perl, in general this stuff is best to hand off to someone else unless you're willing to go and produce a complete implementation. Eventually you look at long-term maintenance or feature creep and eventually you just end up reimplementing one of the already complete packages.

        To make a generalization anyway.