From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id C9A121FBEE for ; Wed, 10 Jun 2020 07:07:30 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 61/82] imap: IDLE: avoid extraneous wakeups, keep-alive Date: Wed, 10 Jun 2020 07:04:58 +0000 Message-Id: <20200610070519.18252-62-e@yhbt.net> In-Reply-To: <20200610070519.18252-1-e@yhbt.net> References: <20200610070519.18252-1-e@yhbt.net> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: We should not waste memory for IDLE unless it's used on the most recent inbox slice. We also need to keep the IDLE connection alive regardless of $PublicInbox::DS::EXPTIME. --- lib/PublicInbox/IMAP.pm | 56 ++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/PublicInbox/IMAP.pm b/lib/PublicInbox/IMAP.pm index 0d553da91f9..77e8af12fa9 100644 --- a/lib/PublicInbox/IMAP.pm +++ b/lib/PublicInbox/IMAP.pm @@ -151,7 +151,7 @@ sub cmd_close ($$) { sub cmd_logout ($$) { my ($self, $tag) = @_; - delete $self->{logged_in}; + delete @$self{qw(logged_in -idle_tag)}; $self->write(\"* BYE logging out\r\n$tag OK Logout done\r\n"); $self->shutdn; # PublicInbox::DS::shutdn undef; @@ -174,23 +174,63 @@ sub cmd_noop ($$) { "$_[1] OK Noop done\r\n" } sub on_inbox_unlock { my ($self, $ibx) = @_; my $new = $ibx->mm->max; + my $uid_end = ($self->{uid_min} // 1) - 1 + UID_BLOCK; defined(my $old = $self->{-idle_max}) or die 'BUG: -idle_max unset'; + $new = $uid_end if $new > $uid_end; if ($new > $old) { $self->{-idle_max} = $new; $self->msg_more("* $_ EXISTS\r\n") for (($old + 1)..($new - 1)); $self->write(\"* $new EXISTS\r\n"); + } elsif ($new == $uid_end) { # max exceeded $uid_end + # continue idling w/o inotify + delete $self->{-idle_max}; + my $sock = $self->{sock} or return; + $ibx->unsubscribe_unlock(fileno($sock)); } } +# called every X minute(s) or so by PublicInbox::DS::later +my $IDLERS = {}; +my $idle_timer; +sub idle_tick_all { + my $old = $IDLERS; + $IDLERS = {}; + for my $i (values %$old) { + next if ($i->{wbuf} || !exists($i->{-idle_tag})); + $i->update_idle_time or next; + $IDLERS->{fileno($i->{sock})} = $i; + $i->write(\"* OK Still here\r\n"); + } + $idle_timer = scalar keys %$IDLERS ? + PublicInbox::DS::later(\&idle_tick_all) : undef; +} + sub cmd_idle ($$) { my ($self, $tag) = @_; # IDLE seems allowed by dovecot w/o a mailbox selected *shrug* my $ibx = $self->{ibx} or return "$tag BAD no mailbox selected\r\n"; - $ibx->subscribe_unlock(fileno($self->{sock}), $self); - $self->{imapd}->idler_start; $self->{-idle_tag} = $tag; - $self->{-idle_max} = $ibx->mm->max // 0; - "+ idling\r\n" + my $max = $ibx->mm->max // 0; + my $uid_end = ($self->{uid_min} // 1) - 1 + UID_BLOCK; + my $sock = $self->{sock} or return; + my $fd = fileno($sock); + # only do inotify on most recent slice + if ($max < $uid_end) { + $ibx->subscribe_unlock($fd, $self); + $self->{imapd}->idler_start; + $self->{-idle_max} = $max; + } + $idle_timer //= PublicInbox::DS::later(\&idle_tick_all); + $IDLERS->{$fd} = $self; + \"+ idling\r\n" +} + +sub stop_idle ($$) { + my ($self, $ibx); + my $sock = $self->{sock} or return; + my $fd = fileno($sock); + delete $IDLERS->{$fd}; + $ibx->unsubscribe_unlock($fd); } sub cmd_done ($$) { @@ -201,7 +241,7 @@ sub cmd_done ($$) { warn "BUG: idle_tag set w/o inbox"; return "$tag BAD internal bug\r\n"; }; - $ibx->unsubscribe_unlock(fileno($self->{sock})); + stop_idle($self, $ibx); "$idle_tag OK Idle done\r\n"; } @@ -1185,9 +1225,7 @@ sub busy { sub close { my ($self) = @_; if (my $ibx = delete $self->{ibx}) { - if (my $sock = $self->{sock}) {; - $ibx->unsubscribe_unlock(fileno($sock)); - } + stop_idle($self, $ibx); } $self->SUPER::close; # PublicInbox::DS::close }