source: gs2-extensions/tdb-edit/trunk/src/perllib/SocketsSwimmingThreadPoolClient.pm@ 26993

Last change on this file since 26993 was 26993, checked in by jmt12, 11 years ago

Changed client to retry connections every second, but also to timeout after five minutes of failing to connect (according to RFC793 there may be a waiting time of up to four minutes for TIME_WAIT sockets to be reaped... so after five minutes it shouldn't be an exhausted socket pool causing the issue)

File size: 3.4 KB
Line 
1# @author Donnie Cameron (macnod)
2# @url https://github.com/macnod/DcServer
3# @readme http://donnieknows.com/blog/perl-sockets-swimming-thread-pool
4
5package SocketsSwimmingThreadPoolClient;
6
7use IO::Socket;
8use Carp;
9use strict;
10use warnings;
11
12my $retry_limit = 300; # five minute
13my $retry_time = 1;
14
15sub new
16{
17 # Params: host, port
18 my ($proto, %param)= @_;
19 my $class= ref($proto) || $proto;
20 bless +{%param} => $class;
21}
22
23sub stop_server
24{
25 shift->query('$self->stop')
26}
27
28sub open_socket
29{
30 my $self = shift @_;
31 # There is, unfortunately, a complication when using TCP sockets - that being
32 # the amount of time sockets linger after being closed. This TIME_WAIT period
33 # is required to ensure the terminating ACKs (kernel level) are recieved (in
34 # a graceful disconnect) and to allow time for 'wandering duplicates' to
35 # finally arrive. While there are (platform/OS/hardware) methods for getting
36 # rid of / shortening this grace period they aren't recommended (for both
37 # stability and security reasons).
38 # Thus you have the issue that, very occasionally and somewhat based upon the
39 # current turnover rate of TCP connections, you may exhaust the available
40 # pool of TCP sockets (as the rest are stuck waiting on TIME_WAIT timeouts).
41 # While the 'reuse' flag might lead you to expect the ability to 'reuse' the
42 # socket - there is one glaring caveat: you can't reuse the port from the
43 # same origin address as this would be a major security flaw. So in reality
44 # the reuse flag does nothing for us (which is why I removed it).
45 # Instead I am forced to put the socket connection in a loop and, if the
46 # first attempt to create the socket fails, wait around for SO_LINGER time
47 # in the hope that the TCP socket pool will have finally purged a number of
48 # the stranded TIME_WAIT connections.
49 # One last kick in the daddy-bags - the *default* SO_LINGER is set to 2*MSL
50 # (Maximum Segment Lifetime - the 'Time To Live of TCP' packets). This means
51 # (according to RFC793) we may be waiting up to 4 minutes for TIME_WAITs to
52 # be reaped. Sigh. Still - ever an optimist - I'll retry the socket
53 # connection every seconds.
54 # References:
55 # http://www.perlmonks.org/?node_id=771242
56 # http://hea-www.harvard.edu/~fine/Tech/addrinuse.html
57 # http://blog.port80software.com/2004/12/07/hurry-up-and-time_wait/
58 # http://www.isi.edu/touch/pubs/infocomm99/infocomm99-web/
59 # http://www.faqs.org/rfcs/rfc793.html
60 while($retry_limit > 0)
61 {
62 my $socket= new IO::Socket::INET(PeerAddr => $self->{host} || 'localhost',
63 PeerPort => $self->{port} || 8190,
64 Proto => 'tcp');
65 if ($socket)
66 {
67 return $socket;
68 }
69 print "Failed to create client socket: $!\n";
70 print "=> Most likely cause - TCP ephemeral ports exhausted (stuck in TIME_WAITs)\n";
71 print "=> Retry in " . $retry_time . " seconds.\n";
72 sleep($retry_time);
73 $retry_limit--;
74 }
75 print "Error! Failed to create client socket within 5 minutes timeout.\n";
76}
77
78sub query
79{
80 my ($self, $query)= @_;
81 my $socket = $self->open_socket();
82 croak "Fatal Error! $!\nDetails: " . $self->{'host'} . ":" . $self->{'port'} . "\n" unless $socket;
83 binmode($socket, ":utf8");
84 print $socket $query . "\n.\n";
85 my $buffer = '';
86 my $reply = '';
87 while($buffer = <$socket>)
88 {
89 $reply.= $buffer;
90 last if $reply =~ s/\n\.\n$//;
91 }
92 close($socket);
93 return $reply;
94}
95
961;
Note: See TracBrowser for help on using the repository browser.