+my $EXPMAP; # fd -> [ idle_time, $self ]
+my $expt;
+our $EXPTIME = 180; # 3 minutes
+my $nextt;
+
+my $nextq = [];
+sub next_tick () {
+ $nextt = undef;
+ my $q = $nextq;
+ $nextq = [];
+ foreach my $nntp (@$q) {
+ # for request && response protocols, always finish writing
+ # before finishing reading:
+ if (my $long_cb = $nntp->{long_res}) {
+ $nntp->write($long_cb);
+ } elsif (&Danga::Socket::POLLIN & $nntp->{event_watch}) {
+ event_read($nntp);
+ }
+ }
+}
+
+sub update_idle_time ($) {
+ my ($self) = @_;
+ my $fd = $self->{fd};
+ defined $fd and $EXPMAP->{$fd} = [ now(), $self ];
+}
+
+sub expire_old () {
+ my $now = now();
+ my $exp = $EXPTIME;
+ my $old = $now - $exp;
+ my $nr = 0;
+ my %new;
+ while (my ($fd, $v) = each %$EXPMAP) {
+ my ($idle_time, $nntp) = @$v;
+ if ($idle_time < $old) {
+ $nntp->close; # idempotent
+ } else {
+ ++$nr;
+ $new{$fd} = $v;
+ }
+ }
+ $EXPMAP = \%new;
+ if ($nr) {
+ $expt = PublicInbox::EvCleanup::later(*expire_old);
+ } else {
+ $expt = undef;
+ # noop to kick outselves out of the loop ASAP so descriptors
+ # really get closed
+ PublicInbox::EvCleanup::asap(sub {});
+ }
+}
+