]> Sergey Matveev's repositories - public-inbox.git/blobdiff - lib/PublicInbox/SearchIdx.pm
searchidx: warn about vivifying multiple ghosts
[public-inbox.git] / lib / PublicInbox / SearchIdx.pm
index ae2544da18a32e5472077caee939080ca639637b..446cfb0d50afd8be723a24c4f6f5751de80ba548 100644 (file)
@@ -9,9 +9,8 @@
 package PublicInbox::SearchIdx;
 use strict;
 use warnings;
-use Fcntl qw(:flock :DEFAULT);
+use base qw(PublicInbox::Search PublicInbox::Lock);
 use PublicInbox::MIME;
-use base qw(PublicInbox::Search);
 use PublicInbox::MID qw/mid_clean id_compress mid_mime mids references/;
 use PublicInbox::MsgIter;
 use Carp qw(croak);
@@ -96,7 +95,7 @@ sub _xdb_release {
        my ($self) = @_;
        my $xdb = delete $self->{xdb} or croak 'not acquired';
        $xdb->close;
-       _lock_release($self) if $self->{creat};
+       $self->lock_release if $self->{creat};
        undef;
 }
 
@@ -107,33 +106,13 @@ sub _xdb_acquire {
        my $flag = Search::Xapian::DB_OPEN;
        if ($self->{creat}) {
                require File::Path;
-               _lock_acquire($self);
+               $self->lock_acquire;
                File::Path::mkpath($dir);
                $flag = Search::Xapian::DB_CREATE_OR_OPEN;
        }
        $self->{xdb} = Search::Xapian::WritableDatabase->new($dir, $flag);
 }
 
-# we only acquire the flock if creating or reindexing;
-# PublicInbox::Import already has the lock on its own.
-sub _lock_acquire {
-       my ($self) = @_;
-       croak 'already locked' if $self->{lockfh};
-       my $lock_path = $self->{lock_path} or return;
-       sysopen(my $lockfh, $lock_path, O_WRONLY|O_CREAT) or
-               die "failed to open lock $lock_path: $!\n";
-       flock($lockfh, LOCK_EX) or die "lock failed: $!\n";
-       $self->{lockfh} = $lockfh;
-}
-
-sub _lock_release {
-       my ($self) = @_;
-       return unless $self->{lock_path};
-       my $lockfh = delete $self->{lockfh} or croak 'not locked';
-       flock($lockfh, LOCK_UN) or die "unlock failed: $!\n";
-       close $lockfh or die "close failed: $!\n";
-}
-
 sub add_val ($$$) {
        my ($doc, $col, $num) = @_;
        $num = Search::Xapian::sortable_serialise($num);
@@ -155,7 +134,9 @@ sub add_values ($$) {
        my $lines = $values->[PublicInbox::Search::LINES];
        add_val($doc, PublicInbox::Search::LINES, $lines);
 
-       my $yyyymmdd = strftime('%Y%m%d', gmtime($ts));
+       my $ds = $values->[PublicInbox::Search::DS];
+       add_val($doc, PublicInbox::Search::DS, $ds);
+       my $yyyymmdd = strftime('%Y%m%d', gmtime($ds));
        add_val($doc, PublicInbox::Search::YYYYMMDD, $yyyymmdd);
 }
 
@@ -319,7 +300,7 @@ sub add_message {
                }
 
                my $lines = $mime->body_raw =~ tr!\n!\n!;
-               my @values = ($smsg->ts, $num, $bytes, $lines);
+               my @values = ($smsg->ds, $num, $bytes, $lines, $smsg->ts);
                add_values($doc, \@values);
 
                my $tg = $self->term_generator;
@@ -371,7 +352,7 @@ sub add_message {
 
                # populates smsg->references for smsg->to_doc_data
                my $refs = parse_references($smsg);
-               $mid0 = $mids->[0] unless defined $mid0;
+               $mid0 = $mids->[0] unless defined $mid0; # v1 compatibility
                my $data = $smsg->to_doc_data($oid, $mid0);
                foreach my $mid (@$mids) {
                        $tg->index_text($mid, 1, 'XM');
@@ -388,10 +369,12 @@ sub add_message {
                        }
                }
 
+               $self->delete_article($num) if defined $num; # for reindexing
                if ($skel) {
                        push @values, $mids, $xpath, $data;
                        $skel->index_skeleton(\@values);
                        $doc->add_boolean_term('Q' . $_) foreach @$mids;
+                       $doc->add_boolean_term('XNUM' . $num) if defined $num;
                        $doc_id = $self->{xdb}->add_document($doc);
                } else {
                        $doc_id = link_and_save($self, $doc, $mids, $refs,
@@ -440,6 +423,16 @@ sub remove_message {
        }
 }
 
+sub delete_article {
+       my ($self, $num) = @_;
+       my $ndel = 0;
+       batch_do($self, 'XNUM' . $num, sub {
+               my ($ids) = @_;
+               $ndel += scalar @$ids;
+               $self->{xdb}->delete_document($_) for @$ids;
+       });
+}
+
 # MID is a hint in V2
 sub remove_by_oid {
        my ($self, $oid, $mid) = @_;
@@ -544,6 +537,7 @@ sub link_and_save {
        $doc->add_boolean_term('Q' . $_) foreach @$mids;
 
        my $vivified = 0;
+       $self->{skel} and die "Should not have read-only skel here\n";;
        foreach my $mid (@$mids) {
                $self->each_smsg_by_mid($mid, sub {
                        my ($cur) = @_;
@@ -567,6 +561,10 @@ sub link_and_save {
                        1;
                });
        }
+       if ($vivified > 1) {
+               my $id = '<'.join('> <', @$mids).'>';
+               warn "BUG: vivified multiple ($vivified) ghosts for $id\n";
+       }
        # not really important, but we return any vivified ghost docid, here:
        return $doc_id if defined $doc_id;
        link_doc($self, $doc, $refs, $old_tid);
@@ -908,24 +906,59 @@ sub DESTROY {
 # remote_* subs are only used by SearchIdxPart and SearchIdxSkeleton
 sub remote_commit {
        my ($self) = @_;
-       print { $self->{w} } "commit\n" or die "failed to write commit: $!";
+       if (my $w = $self->{w}) {
+               print $w "commit\n" or die "failed to write commit: $!";
+       } else {
+               $self->commit_txn_lazy;
+               if (my $skel = $self->{skeleton}) {
+                       $skel->commit_txn_lazy;
+               }
+       }
 }
 
 sub remote_close {
        my ($self) = @_;
-       my $pid = delete $self->{pid} or die "no process to wait on\n";
-       my $w = delete $self->{w} or die "no pipe to write to\n";
-       print $w "close\n" or die "failed to write to pid:$pid: $!\n";
-       close $w or die "failed to close pipe for pid:$pid: $!\n";
-       waitpid($pid, 0) == $pid or die "remote process did not finish";
-       $? == 0 or die ref($self)." pid:$pid exited with: $?";
+       if (my $w = delete $self->{w}) {
+               my $pid = delete $self->{pid} or die "no process to wait on\n";
+               print $w "close\n" or die "failed to write to pid:$pid: $!\n";
+               close $w or die "failed to close pipe for pid:$pid: $!\n";
+               waitpid($pid, 0) == $pid or die "remote process did not finish";
+               $? == 0 or die ref($self)." pid:$pid exited with: $?";
+       } else {
+               die "transaction in progress $self\n" if $self->{txn};
+               $self->_xdb_release if $self->{xdb};
+       }
 }
 
-# triggers remove_by_oid in partition or skeleton
 sub remote_remove {
        my ($self, $oid, $mid) = @_;
-       print { $self->{w} } "D $oid $mid\n" or
-                       die "failed to write remove $!";
+       if (my $w = $self->{w}) {
+               # triggers remove_by_oid in partition or skeleton
+               print $w "D $oid $mid\n" or die "failed to write remove $!";
+       } else {
+               $self->begin_txn_lazy;
+               $self->remove_by_oid($oid, $mid);
+       }
+}
+
+sub begin_txn_lazy {
+       my ($self) = @_;
+       return if $self->{txn};
+       my $xdb = $self->{xdb} || $self->_xdb_acquire;
+       $xdb->begin_transaction;
+       $self->{txn} = 1;
+}
+
+sub commit_txn_lazy {
+       my ($self) = @_;
+       delete $self->{txn} or return;
+       $self->{xdb}->commit_transaction;
+}
+
+sub worker_done {
+       my ($self) = @_;
+       die "$$ $0 xdb not released\n" if $self->{xdb};
+       die "$$ $0 still in transaction\n" if $self->{txn};
 }
 
 1;