X-Git-Url: http://www.git.stargrave.org/?p=public-inbox.git;a=blobdiff_plain;f=lib%2FPublicInbox%2FDS.pm;h=fee31e3d132ab675ff11a9e16c9e623ddce8d1d3;hp=ba6c74d0ea97d51e069a85bdef6ad3123cc7e4d5;hb=d07ba9c30800225052d17ccca458afbfa05a8ff0;hpb=1c52f49354aa83e71fcceccae888da0c77f2391d diff --git a/lib/PublicInbox/DS.pm b/lib/PublicInbox/DS.pm index ba6c74d0..fee31e3d 100644 --- a/lib/PublicInbox/DS.pm +++ b/lib/PublicInbox/DS.pm @@ -37,14 +37,12 @@ our @EXPORT_OK = qw(now msg_more dwaitpid add_timer add_uniq_timer); my %Stack; my $nextq; # queue for next_tick my $wait_pids; # list of [ pid, callback, callback_arg ] -my $EXPMAP; # fd -> idle_time -our $EXPTIME = 180; # 3 minutes -my ($reap_armed); +my $reap_armed; my $ToClose; # sockets to close when event loop is done our ( %DescriptorMap, # fd (num) -> PublicInbox::DS object $Epoll, # Global epoll fd (or DSKQXS ref) - $_io, # IO::Handle for Epoll + $ep_io, # IO::Handle for Epoll $PostLoopCallback, # subref to call at the end of each loop, if defined (global) @@ -76,9 +74,8 @@ sub Reset { # we may be iterating inside one of these on our stack my @q = delete @Stack{keys %Stack}; for my $q (@q) { @$q = () } - $EXPMAP = undef; $wait_pids = $nextq = $ToClose = undef; - $_io = undef; # closes real $Epoll FD + $ep_io = undef; # closes real $Epoll FD $Epoll = undef; # may call DSKQXS::DESTROY } while (@Timers || keys(%Stack) || $nextq || $wait_pids || $ToClose || keys(%DescriptorMap) || @@ -127,32 +124,24 @@ sub add_uniq_timer { # ($name, $secs, $coderef, @args) = @_; $UniqTimer{$_[0]} //= _add_named_timer(@_); } -# keeping this around in case we support other FD types for now, -# epoll_create1(EPOLL_CLOEXEC) requires Linux 2.6.27+... -sub set_cloexec ($) { - my ($fd) = @_; - - open($_io, '+<&=', $fd) or return; - defined(my $fl = fcntl($_io, F_GETFD, 0)) or return; - fcntl($_io, F_SETFD, $fl | FD_CLOEXEC); -} - # caller sets return value to $Epoll -sub _InitPoller -{ - if (PublicInbox::Syscall::epoll_defined()) { - my $fd = epoll_create(); - set_cloexec($fd) if (defined($fd) && $fd >= 0); - $fd; - } else { - my $cls; - for (qw(DSKQXS DSPoll)) { - $cls = "PublicInbox::$_"; - last if eval "require $cls"; - } - $cls->import(qw(epoll_ctl epoll_wait)); - $cls->new; - } +sub _InitPoller () { + if (PublicInbox::Syscall::epoll_defined()) { + my $fd = epoll_create(); + die "epoll_create: $!" if $fd < 0; + open($ep_io, '+<&=', $fd) or return; + my $fl = fcntl($ep_io, F_GETFD, 0); + fcntl($ep_io, F_SETFD, $fl | FD_CLOEXEC); + $fd; + } else { + my $cls; + for (qw(DSKQXS DSPoll)) { + $cls = "PublicInbox::$_"; + last if eval "require $cls"; + } + $cls->import(qw(epoll_ctl epoll_wait)); + $cls->new; + } } sub now () { clock_gettime(CLOCK_MONOTONIC) } @@ -259,9 +248,6 @@ sub PostEventLoop () { $ToClose = undef; # will be autovivified on push @$close_now = map { fileno($_) } @$close_now; - # order matters, destroy expiry times, first: - delete @$EXPMAP{@$close_now}; - # ->DESTROY methods may populate ToClose delete @DescriptorMap{@$close_now}; } @@ -361,6 +347,24 @@ retry: $DescriptorMap{$fd} = $self; } +# for IMAP, NNTP, and POP3 which greet clients upon connect +sub greet { + my ($self, $sock) = @_; + my $ev = EPOLLIN; + my $wbuf; + if ($sock->can('accept_SSL') && !$sock->accept_SSL) { + return CORE::close($sock) if $! != EAGAIN; + $ev = PublicInbox::TLS::epollbit() or return CORE::close($sock); + $wbuf = [ \&accept_tls_step, $self->can('do_greet')]; + } + new($self, $sock, $ev | EPOLLONESHOT); + if ($wbuf) { + $self->{wbuf} = $wbuf; + } else { + $self->do_greet; + } + $self; +} ##################################################################### ### I N S T A N C E M E T H O D S @@ -502,16 +506,15 @@ sub drop { $self->close; } -# n.b.: use ->write/->read for this buffer to allow compatibility with -# PerlIO::mmap or PerlIO::scalar if needed sub tmpio ($$$) { my ($self, $bref, $off) = @_; my $fh = tmpfile('wbuf', $self->{sock}, O_APPEND) or return drop($self, "tmpfile $!"); $fh->autoflush(1); my $len = length($$bref) - $off; - print $fh substr($$bref, $off, $len) or + my $n = syswrite($fh, $$bref, $len, $off) // return drop($self, "write ($len): $!"); + $n == $len or return drop($self, "wrote $n < $len bytes"); [ $fh, 0 ] # [1] = offset, [2] = length, not set by us } @@ -645,6 +648,60 @@ sub shutdn ($) { } } +sub zflush {} # overridden by NNTPdeflate and IMAPdeflate + +sub long_response_done {} # overridden by Net::NNTP + +sub long_step { + my ($self) = @_; + # wbuf is unset or empty, here; {long} may add to it + my ($fd, $cb, $t0, @args) = @{$self->{long_cb}}; + my $more = eval { $cb->($self, @args) }; + if ($@ || !$self->{sock}) { # something bad happened... + delete $self->{long_cb}; + my $elapsed = now() - $t0; + $@ and $self->err("%s during long response[$fd] - %0.6f", + $@, $elapsed); + $self->out(" deferred[$fd] aborted - %0.6f", $elapsed); + $self->close; + } elsif ($more) { # $self->{wbuf}: + # control passed to ibx_async_cat if $more == \undef + requeue_once($self) if !ref($more); + } else { # all done! + delete $self->{long_cb}; + $self->long_response_done; + my $elapsed = now() - $t0; + my $fd = fileno($self->{sock}); + $self->out(" deferred[$fd] done - %0.6f", $elapsed); + my $wbuf = $self->{wbuf}; # do NOT autovivify + requeue($self) unless $wbuf && @$wbuf; + } +} + +sub requeue_once { + my ($self) = @_; + # COMPRESS users all share the same DEFLATE context. + # Flush it here to ensure clients don't see each other's data + $self->zflush; + + # no recursion, schedule another call ASAP, + # but only after all pending writes are done. + # autovivify wbuf. wbuf may be populated by $cb, + # no need to rearm if so: (push returns new size of array) + requeue($self) if push(@{$self->{wbuf}}, \&long_step) == 1; +} + +sub long_response ($$;@) { + my ($self, $cb, @args) = @_; # cb returns true if more, false if done + my $sock = $self->{sock} or return; + # make sure we disable reading during a long response, + # clients should not be sending us stuff and making us do more + # work while we are stream a response to them + $self->{long_cb} = [ fileno($sock), $cb, now(), @args ]; + long_step($self); # kick off! + undef; +} + sub dwaitpid ($;$$) { my ($pid, $cb, $arg) = @_; if ($in_loop) { @@ -664,35 +721,6 @@ sub dwaitpid ($;$$) { } } -sub expire_old () { - my $cur = $EXPMAP or return; - $EXPMAP = undef; - my $old = now() - $EXPTIME; - while (my ($fd, $idle_at) = each %$cur) { - if ($idle_at < $old) { - my $ds_obj = $DescriptorMap{$fd}; - $EXPMAP->{$fd} = $idle_at if !$ds_obj->shutdn; - } else { - $EXPMAP->{$fd} = $idle_at; - } - } - add_uniq_timer('expire', 60, \&expire_old) if $EXPMAP; -} - -sub update_idle_time { - my ($self) = @_; - my $sock = $self->{sock} or return; - $EXPMAP->{fileno($sock)} = now(); - add_uniq_timer('expire', 60, \&expire_old); -} - -sub not_idle_long { - my ($self, $now) = @_; - my $sock = $self->{sock} or return; - my $idle_at = $EXPMAP->{fileno($sock)} or return; - ($idle_at + $EXPTIME) > $now; -} - 1; =head1 AUTHORS (Danga::Socket)