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

Hi monks,

This is my first post to this web site, and I'm very excited to be here. I'm hoping that my problem will be so elusive that it will take weeks even for the experts to solve!

I use the MLDBM format to store some data in a file. I am writing a web application using Catalyst that needs to read and modify this data, so I started out by writing a proper Model class. In the class, I have a few lines of code that initializes the tied hash. It looks like this :

sub hash_init { my $dbm_file = "file.db"; my %hash; local *DBM; my $db = tie %hash, 'MLDBM', $dbm_file, O_CREAT | O_RDWR, 0644 or +die "Couldn't not tie to $dbm_file: $!"; my $fd = $db->fd; open DBM, "+<&=$fd" or die "Could not dup DBM for lock : $!"; flock DBM, "LOCK_EX"; undef $db; return %hash; }

I call this function in my other subroutines that manipulate the hash, deleting or adding a items. For instance, my deleteItem subroutine looks like this:

my %hash = hash_init(); print Dumper(%hash); delete( $hash{$del_id} ); print Dumper(%hash); untie (%hash);

In the above code, I can see that the item has been deleted between the two Dumper calls. And yet when I list the content at a later time, the entry is still there! When I just copy the same initialization code in the delete subroutine, everything works perfectly. But I don't want to keep copying the same lines over and over again, in case I want to change them later. Plus I want to find out how to best deal with these "tie" beasts.

I suspect the problem has to do with the reference count on the hash. I figured the changes weren't being committed to the file because there were some other variables pointing there that weren't destroyed. I am not sure whether the main %hash reference in hash_init() is still existent after the subroutine returns. However, I couldn't find a way to solve the problem. I tried just returning a pointer to the hash from hash_init() instead of the actual variable, but I couldn't get that to work either.

Any ideas?

Replies are listed 'Best First'.
Re: Proper way of passing around tie'd variable
by Somni (Friar) on Jan 07, 2008 at 02:36 UTC
    In this respect tying is no different from passing any other hash around. If you want a subroutine to modify the original you have to pass a reference. As it stands now you're just copying the contents of the hash around.

    my %hash = qw(foo 1 bar 2); foo(%hash); bar(\%hash);

    The foo() subroutine gets a list, ('foo', 1, 'bar', 2). It can copy this list into a hash, but changes to that hash have no effect on %hash. bar() gets a reference to the original hash; changes to the reference affect %hash in the outer code.

    Similarly, if you want to maintain the tie when you pass your hash out of hash_init(), you need to return a reference. Otherwise you're just copying the contents of your hash into another, non-tied, hash. You could also pass a hash reference for it to tie, i.e. hash_init(\%hash)

    See perldoc perlreftut, perldoc perlref, and Beginning Perl's Chapter on References.

Re: Proper way of passing around tie'd variable
by kyle (Abbot) on Jan 07, 2008 at 05:12 UTC

    Somni is correct about using a reference to pass the tied hash around. In the code you have, you'd return \%hash inside hash_init. Outside, you'd say my $hash_ref = hash_init() and then $hash_ref->{$id} to access some part of it.

    That aside, I notice you flock after the hash has been tied. I think it would be better to get a lock on something before the hash is tied. You may want to use a separate lock file for that purpose. I don't know for a fact that what you're doing now leaves room for a problem, but it looks suspicious to me.

      Thank you both for your replies. I had actually tried passing back a reference first, but I run into a very odd problem. So I changed my hash_init function to something like this:

      sub hash_init2 { #### DBM Configuration ####################### my $dbm_file = "file.db"; my %hash; local *DBM; my $db = tie %hash, 'MLDBM', $dbm_file, O_CREAT | O_RDWR, 0644 or +die "Couldn't not tie to $dbm_file: $!"; my $fd = $db->fd; open DBM, "+<&=$fd" or die "Could not dup DBM for lock : $!"; flock DBM, "LOCK_EX"; undef $db; #Dumper(%hash); return \%hash; }

      And then I call it from deleteItem as follows:

      sub deleteItem { my ($self, $del_id) = @_; my $hash = hash_init2(); print Dumper($hash); # check if the item actually exists in the database if ( exists($hash->{$del_id}) ) { delete( $hash->{$del_id} ); } untie($hash); }

      The odd thing is, the print Dumper call in deleteItem normally doesn't print anything (so the reference points to a null hash, and the delete fails). However, when I uncomment the line Dumper(%hash) in hash_init2, then everything works. As you see, I am not even printing anything, but just reading the values.

      Any ideas why this might be happening? Does one need to read from the tied hash in the same scope or something?

        I'm guessing that the code you posted isn't the code you're using. To get it to run, I had to make some changes. Here's what I have:

        use Data::Dumper; use MLDBM qw( DB_File ); use Fcntl qw( :DEFAULT :flock ); sub hash_init2 { #### DBM Configuration ####################### my $dbm_file = "file.db"; my %hash; local *DBM; my $db = tie %hash, 'MLDBM', $dbm_file, O_CREAT | O_RDWR, 0644 or die "Couldn't not tie to $dbm_file: $!"; my $fd = $db->fd; open DBM, "+<&=$fd" or die "Could not dup DBM for lock : $!"; flock DBM, LOCK_EX; undef $db; return \%hash; } my $h = hash_init2(); print Dumper $h;

        I notice that things work as I expect if I throw out all the stuff to do with file locking (everything between the tie line and the return line). My advice is to pick another file to lock (say, "file.db.lock"), and do not much with your "file.db" directly at all. Let MLDBM own that. There's then no reason to catch the value, $db, and no reason to undef it (which might be another problem).

Re: Proper way of passing around tie'd variable
by dragonchild (Archbishop) on Jan 07, 2008 at 20:54 UTC
    You might want to consider converting to DBM::Deep. Not only does dbm-deep actually support useful features (like transactions, concurrency, and proper Perlish multi-level data structures), but mst and others in the gang like it a lot. Enough so that you'd probably get a good amount of help writing a proper model around it. In addition, there's been a lot of around-a-beer discussion of making a proper DBIx::Class plugin for dbm-deep, but no-one's actually had a need for it. Sounds like you might be the guinea p person to help out with that.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?