+# don't bother with shutdown($sock, 2), we don't fork+exec w/o CLOEXEC
+# or fork w/o exec, so no inadvertent socket sharing
+sub shutdn ($) {
+ my ($self) = @_;
+ my $sock = $self->{sock} or return;
+ if ($sock->can('stop_SSL')) {
+ shutdn_tls_step($self);
+ } else {
+ $self->close;
+ }
+}
+
+# must be called with eval, PublicInbox::DS may not be loaded (see t/qspawn.t)
+sub dwaitpid ($$$) {
+ die "Not in EventLoop\n" unless $in_loop;
+ push @$wait_pids, [ @_ ]; # [ $pid, $cb, $arg ]
+
+ # We could've just missed our SIGCHLD, cover it, here:
+ enqueue_reap();
+}
+
+sub _run_later () {
+ my $run = $later_queue or return;
+ $later_timer = $later_queue = undef;
+ $_->() for @$run;
+}
+
+sub later ($) {
+ push @$later_queue, $_[0]; # autovivifies @$later_queue
+ $later_timer //= add_timer(60, \&_run_later);
+}
+
+sub expire_old () {
+ my $now = now();
+ my $exp = $EXPTIME;
+ my $old = $now - $exp;
+ my %new;
+ while (my ($fd, $idle_at) = each %$EXPMAP) {
+ if ($idle_at < $old) {
+ my $ds_obj = $DescriptorMap{$fd};
+ $new{$fd} = $idle_at if !$ds_obj->shutdn;
+ } else {
+ $new{$fd} = $idle_at;
+ }
+ }
+ $EXPMAP = \%new;
+ $exp_timer = scalar(keys %new) ? later(\&expire_old) : undef;
+}
+
+sub update_idle_time {
+ my ($self) = @_;
+ my $sock = $self->{sock} or return;
+ $EXPMAP->{fileno($sock)} = now();
+ $exp_timer //= later(\&expire_old);
+}