The following code demonstrates a server that leaks a file descriptor.
We cannot figure out how to fix that. Could you please provide some
guidance where the problem lies and what the correct way to deal with
it is?

To understand the problem we need three things:

- a mojo server with
  -- an asset handler
  -- a write callback (a subscriber to the Mojo::IOLoop::Stream write event)
- a mojo client that only takes a partial response
- a tool that watches the behaviour of the server

Let's start with the tool. I used inotifywait to watch the open
filehandles on the path and lsof to see the open filehandles on the
process.

Inotify shows me

% inotifywait -m /var/tmp/zero --format "%T %e %f" --timefmt "%FT%T"
[...]
2015-07-28T12:31:03 OPEN 
2015-07-28T12:31:03 ACCESS 
2015-07-28T12:31:03 ACCESS 
2015-07-28T12:31:03 ACCESS 

and no close. lsof shows me

% lsof -p 1643 | grep zero
perl    1643    k    5r   REG   254,8   409600     75 /var/tmp/zero

The server never closes the file according to these two tools, so
something in the program below must be wrong.

As the downloading client we use this small tool on github:
https://gist.github.com/53b164655b57deb993e1 . We call that program simply 
as

% perl streamres.pl -s 200000 http://127.0.0.1:3000/welcome

It downloads 200000 byte and then closes the connection without
waiting for the end of the connection.

The server side code is below. We have already identified two
places that that make the server work without an FD leak. The two lines
are:

# [1] weaken $s;
# [2] undef $s;

Either of the two will do to close the FD leak. Note that the server
works well when

- the client downloads the full file or
- there is no write_cb to unsubscribe from

What is the recommended approach, when we want to count the written
bytes and need to deal with clients that close their connections
prematurely?

% MOJO_IOLOOP_DEBUG=1 perl -Ilib -e ' 
  use Scalar::Util qw(weaken);
  0 == system dd => "if=/dev/zero", "of=/var/tmp/zero", "bs=4096", 
"count=100" or die;
  { package MyApp;
    use Mojo::Base "Mojolicious";
    sub startup {
      my $self = shift;
      $self->routes->get("/welcome")->to("foo#hello");
      $self->hook(after_build_tx => sub {
        my ($tx, $app) = @_;
        $tx->on(connection => sub {
          my ($tx, $connection) = @_;
          my $s = Mojo::IOLoop->stream($connection);
          # [1] weaken $s;
          my $sent_bytes = 0;
          my $write_cb = $s->on(
              write => sub {
                  $sent_bytes += length $_[1];
                  warn "+++ Total bytes sent: $sent_bytes";
          });
          $tx->on(finish => sub {
            $s->unsubscribe(write => $write_cb);
            # [2] undef $s;
          });
        });
      });
    }
  }
  { package MyApp::Controller::Foo;
    use Mojo::Base "Mojolicious::Controller";
    sub hello {
      my $self = shift;
      open my $fh, "/var/tmp/zero";
      my $asset = Mojo::Asset::File->new(
          path => "/var/tmp/zero", handle => $fh
      );
      $self->res->content->asset($asset);
      $self->rendered($self->res->code);
    }
  }
  package main;
  require Mojolicious::Commands;
  Mojolicious::Commands->start_app("MyApp");
  ' daemon                                                  
-- Reactor initialized (Mojo::Reactor::EV)
100+0 records in
100+0 records out
409600 bytes (410 kB) copied, 0.000488926 s, 838 MB/s
[Tue Jul 28 13:59:16 2015] [info] Listening at "http://*:3000";
Server available at http://127.0.0.1:3000
-- 821cfc3bf5c6d3914d29873d6eeb2308 >>> 9004 (1)
[Tue Jul 28 13:59:21 2015] [debug] Your secret passphrase needs to be 
changed
[Tue Jul 28 13:59:21 2015] [debug] GET "/welcome"
[Tue Jul 28 13:59:21 2015] [debug] Routing to controller 
"MyApp::Controller::Foo" and action "hello"
[Tue Jul 28 13:59:21 2015] [debug] 200 OK (0.000527s, 1897.533/s)
+++ Total bytes sent: 131199 at -e line 16.
+++ Total bytes sent: 262271 at -e line 16.
-- 821cfc3bf5c6d3914d29873d6eeb2308 <<< 9004 (0)

Thank you,

-- 
You received this message because you are subscribed to the Google Groups 
"Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to mojolicious+unsubscr...@googlegroups.com.
To post to this group, send email to mojolicious@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.

Reply via email to