Subject: | IO::Socket::IP holds on to addresses beyond object destruction |
Summary: When an object using an IO::Socket::IP-based socket object is destroyed, the address+port will appear to be available, but if a second object is created with the same address+port too quickly, it will fail with "Address already in use".
I recently got a series of FAIL reports from cpantesters for my RPC-XML distribution. Most of the failures all had the same problem, and also the same characteristic; HTTP::Daemon was failing to bind to a given address, and the tester had installed Acme::Override::INET to monkey-patch IO::Socket::IP over IO::Socket::INET. I installed these two modules, and suddenly I too was getting the same errors.
The errors were only occurring in places where I allocated more than one server object through the life of the test. It was always the second object that failed to instantiate, with the error message from HTTP::Daemon, "Unable to create HTTP::Daemon object: Address already in use". I found that if I forced the second instantiation to use a higher port number than the first one had, the tests then all passed. Note that at this time, the previous server object had been completely destroyed. Not just the HTTP::Daemon part of it, but the full object.
To make it more interesting, I also found that once I patched my test, if I ran it twice in rapid succession the *first* object allocation would fail on the second test-run. So not only does IO::Socket::IP hold on to the address too long within a running process, apparently it also causes the address to be "wedged" for some time after the process has exited.
In my test code, I have the following utility function that I use to find a free port on localhost:
sub find_port
{
my $start_at = $_[0] || 9000;
my ($port, $sock);
for ($port = $start_at; $port < ($start_at + 2000); $port++)
{
$sock = IO::Socket->new(Domain => AF_INET,
PeerAddr => 'localhost',
PeerPort => $port);
return $port unless ref $sock;
}
-1;
}
This code should return the first "available" port on localhost, that being the first port that IO::Socket->new() couldn't connect to. Without IO::Socket::IP, this code ends up using IO::Socket::INET and correctly returns an available port (both times it returns port 9000). But when IO::Socket::IP comes in to play, the second call returns port 9000 as being available, but when HTTP::Daemon tries to bind the address it fails, saying the address is already in use.
I haven't been able to distill this down to a smaller example that exhibits the same failure, but the following steps should reproduce it:
1. Have Acme::Override::INET installed (as well as IO::Socket::IP, of course)
2. Download and untar RPC-XML-0.78.tar.gz.
3. cd into the directory and "perl Makefile.PL".
4. Do a "make".
5. Do "prove -b t/40_server.t" (you need the "-b", or other tests will fail).
6. Test will fail on or near line 446, because the second allocation of an RPC::XML::Server object failed.
7. Immediately re-run prove.
8. This time, it should fail on/near line 176, because the *first* allocation has failed this time, because the address is still considered in use.
I will try to generate a smaller test script that exhibits the problem. If I can, I will update this ticket with the smaller test.
Randy
--
Randy J. Ray
rjray@blackperl.com
randy.j.ray@gmail.com