From d0dcab33e0b02bf3299deea40f96ef5fff10fe73 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 10 Jun 2020 07:05:12 +0000 Subject: imap: STATUS/EXAMINE: rely on SQLite overview We can get exact values for EXISTS, UIDNEXT using SQLite rather than calculating off $ibx->mm->max ourselves. Furthermore, $ibx->mm is less useful than $ibx->over for IMAP (and for our read-only daemons in general) so do not depend on $ibx->mm outside of startup/reload to save FDs and reduce kernel page cache footprint. --- lib/PublicInbox/DummyInbox.pm | 11 ++++++----- lib/PublicInbox/IMAP.pm | 30 ++++++++++++++++++------------ lib/PublicInbox/IMAPD.pm | 2 +- lib/PublicInbox/Over.pm | 30 ++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 18 deletions(-) (limited to 'lib/PublicInbox') diff --git a/lib/PublicInbox/DummyInbox.pm b/lib/PublicInbox/DummyInbox.pm index b6c48db1..c3f4f5c6 100644 --- a/lib/PublicInbox/DummyInbox.pm +++ b/lib/PublicInbox/DummyInbox.pm @@ -9,13 +9,14 @@ use strict; sub created_at { 0 } # Msgmap::created_at sub mm { shift } -sub max { undef } # Msgmap::max -sub msg_range { [] } # Msgmap::msg_range +sub uid_range { [] } # Over::uid_range +sub subscribe_unlock { undef }; no warnings 'once'; -*uid_range = *query_xover = \&msg_range; +*max = \&created_at; +*query_xover = \&uid_range; *over = \&mm; -*subscribe_unlock = *unsubscribe_unlock = - *get_art = *description = *base_url = \&max; +*unsubscribe_unlock = + *get_art = *description = *base_url = \&subscribe_unlock; 1; diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm index d0683530..41c80664 100644 --- a/lib/PublicInbox/IMAP.pm +++ b/lib/PublicInbox/IMAP.pm @@ -183,7 +183,7 @@ sub cmd_noop ($$) { "$_[1] OK Noop done\r\n" } # called by PublicInbox::InboxIdle sub on_inbox_unlock { my ($self, $ibx) = @_; - my $new = $ibx->mm->max; + my $new = $ibx->over->max; my $uid_base = $self->{uid_base} // 0; my $uid_end = $uid_base + UID_BLOCK; defined(my $old = $self->{-idle_max}) or die 'BUG: -idle_max unset'; @@ -223,7 +223,7 @@ sub cmd_idle ($$) { # IDLE seems allowed by dovecot w/o a mailbox selected *shrug* my $ibx = $self->{ibx} or return "$tag BAD no mailbox selected\r\n"; $self->{-idle_tag} = $tag; - my $max = $ibx->mm->max // 0; + my $max = $ibx->over->max; my $uid_end = $self->{uid_base} + UID_BLOCK; my $sock = $self->{sock} or return; my $fd = fileno($sock); @@ -283,14 +283,20 @@ sub inbox_lookup ($$) { my $mb_top = $1; $uid_base = $2 * UID_BLOCK; $ibx = $self->{imapd}->{mailboxes}->{lc $mailbox} or return; - $exists = $ibx->mm->max // 0; - ensure_ranges_exist($self->{imapd}, $ibx, $exists); - my $uid_end = $uid_base + UID_BLOCK; - $exists = $uid_end if $exists > $uid_end; - $uidnext = $exists + 1; - $exists -= $uid_base; + my $max; + ($exists, $uidnext, $max) = $ibx->over->imap_status($uid_base, + $uid_base + UID_BLOCK); + ensure_ranges_exist($self->{imapd}, $ibx, $max); } else { # check for dummy inboxes - $ibx = $self->{imapd}->{mailboxes}->{lc $mailbox} or return; + $mailbox = lc $mailbox; + $ibx = $self->{imapd}->{mailboxes}->{$mailbox} or return; + + # if "INBOX.foo.bar" is selected and "INBOX.foo.bar.0", + # check for new UID ranges (e.g. "INBOX.foo.bar.1") + if (my $z = $self->{imapd}->{mailboxes}->{"$mailbox.0"}) { + ensure_ranges_exist($self->{imapd}, $z, $z->over->max); + } + $uid_base = $exists = 0; $uidnext = 1; } @@ -590,7 +596,7 @@ sub range_step ($$) { ($beg, $end) = ($1 + 0, $2 + 0); } elsif ($range =~ /\A([0-9]+):\*\z/) { $beg = $1 + 0; - $end = $self->{ibx}->mm->max // 0; + $end = $self->{ibx}->over->max; my $uid_end = $self->{uid_base} + UID_BLOCK; $end = $uid_end if $end > $uid_end; $beg = $end if $beg > $end; @@ -1093,14 +1099,14 @@ sub cmd_uid_search ($$$;) { if (!scalar(keys %$q)) { $self->msg_more('* SEARCH'); my $beg = 1; - my $end = $ibx->mm->max // 0; + my $end = $ibx->over->max; uid_clamp($self, \$beg, \$end); long_response($self, \&uid_search_uid_range, $tag, \$beg, $end, $sql); } elsif (my $uid = $q->{uid}) { if ($uid =~ /\A([0-9]+):([0-9]+|\*)\z/s) { my ($beg, $end) = ($1, $2); - $end = $ibx->mm->max if $end eq '*'; + $end = $ibx->over->max if $end eq '*'; uid_clamp($self, \$beg, \$end); $self->msg_more('* SEARCH'); long_response($self, \&uid_search_uid_range, diff --git a/lib/PublicInbox/IMAPD.pm b/lib/PublicInbox/IMAPD.pm index 186ec7b0..e4dc90e8 100644 --- a/lib/PublicInbox/IMAPD.pm +++ b/lib/PublicInbox/IMAPD.pm @@ -44,7 +44,7 @@ sub imapd_refresh_ibx { # pi_config->each_inbox cb # this case is a 32-bit representation of the creation # date/time of the mailbox" defined($ibx->{uidvalidity} = $mm->created_at) or return; - PublicInbox::IMAP::ensure_ranges_exist($imapd, $ibx, $mm->max // 1); + PublicInbox::IMAP::ensure_ranges_exist($imapd, $ibx, $mm->max // 0); # preload to avoid fragmentation: $ibx->description; diff --git a/lib/PublicInbox/Over.pm b/lib/PublicInbox/Over.pm index 1faeff41..e0f20ea6 100644 --- a/lib/PublicInbox/Over.pm +++ b/lib/PublicInbox/Over.pm @@ -229,4 +229,34 @@ sub uid_range { $dbh->selectcol_arrayref($q, undef, $beg, $end); } +sub max { + my ($self) = @_; + my $sth = $self->connect->prepare_cached(<<'', undef, 1); +SELECT MAX(num) FROM over WHERE num > 0 + + $sth->execute; + $sth->fetchrow_array // 0; +} + +sub imap_status { + my ($self, $uid_base, $uid_end) = @_; + my $dbh = $self->connect; + my $sth = $dbh->prepare_cached(<<'', undef, 1); +SELECT COUNT(num) FROM over WHERE num > ? AND num <= ? + + $sth->execute($uid_base, $uid_end); + my $exists = $sth->fetchrow_array; + + $sth = $dbh->prepare_cached(<<'', undef, 1); +SELECT MAX(num) + 1 FROM over WHERE num <= ? + + $sth->execute($uid_end); + my $uidnext = $sth->fetchrow_array; + + $sth = $dbh->prepare_cached(<<'', undef, 1); +SELECT MAX(num) FROM over WHERE num > 0 + + ($exists, $uidnext, $sth->fetchrow_array // 0); +} + 1; -- cgit v1.2.3-24-ge0c7