@ -39,6 +39,7 @@ import os
import socket
import sys
import time
import random
from SocketServer import ThreadingMixIn
import threading
@ -46,7 +47,7 @@ import threading
# increment this number whenever making a change to ensure that
# the changes are picked up by running CI servers
# note that all changes must be backwards compatible
_MY_VERSION = 14
_MY_VERSION = 19
if len ( sys . argv ) == 2 and sys . argv [ 1 ] == ' dump_version ' :
@ -72,10 +73,33 @@ pool = []
in_use = { }
mu = threading . Lock ( )
def can_connect ( port ) :
s = socket . socket ( )
try :
s . connect ( ( ' localhost ' , port ) )
return True
except socket . error , e :
return False
finally :
s . close ( )
def can_bind ( port , proto ) :
s = socket . socket ( proto , socket . SOCK_STREAM )
s . setsockopt ( socket . SOL_SOCKET , socket . SO_REUSEADDR , 1 )
try :
s . bind ( ( ' localhost ' , port ) )
return True
except socket . error , e :
return False
finally :
s . close ( )
def refill_pool ( max_timeout , req ) :
""" Scan for ports not marked for being in use """
for i in range ( 1025 , 32766 ) :
chk = list ( range ( 1025 , 32766 ) )
random . shuffle ( chk )
for i in chk :
if len ( pool ) > 100 : break
if i in in_use :
age = time . time ( ) - in_use [ i ]
@ -83,16 +107,9 @@ def refill_pool(max_timeout, req):
continue
req . log_message ( " kill old request %d " % i )
del in_use [ i ]
s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM )
s . setsockopt ( socket . SOL_SOCKET , socket . SO_REUSEADDR , 1 )
try :
s . bind ( ( ' localhost ' , i ) )
if can_bind ( i , socket . AF_INET ) and can_bind ( i , socket . AF_INET6 ) and not can_connect ( i ) :
req . log_message ( " found available port %d " % i )
pool . append ( i )
except :
pass # we really don't care about failures
finally :
s . close ( )
def allocate_port ( req ) :
@ -128,6 +145,7 @@ class Handler(BaseHTTPRequestHandler):
def do_GET ( self ) :
global keep_running
global mu
if self . path == ' /get ' :
# allocate a new port, it will stay bound for ten minutes and until
# it's unused
@ -142,12 +160,15 @@ class Handler(BaseHTTPRequestHandler):
self . send_header ( ' Content-Type ' , ' text/plain ' )
self . end_headers ( )
p = int ( self . path [ 6 : ] )
mu . acquire ( )
if p in in_use :
del in_use [ p ]
pool . append ( p )
self . log_message ( ' drop known port %d ' % p )
k = ' known '
else :
self . log_message ( ' drop unknown port %d ' % p )
k = ' unknown '
mu . release ( )
self . log_message ( ' drop %s port %d ' % ( k , p ) )
elif self . path == ' /version_number ' :
# fetch a version string and the current process pid
self . send_response ( 200 )
@ -161,8 +182,11 @@ class Handler(BaseHTTPRequestHandler):
self . send_response ( 200 )
self . send_header ( ' Content-Type ' , ' text/plain ' )
self . end_headers ( )
mu . acquire ( )
now = time . time ( )
self . wfile . write ( yaml . dump ( { ' pool ' : pool , ' in_use ' : dict ( ( k , now - v ) for k , v in in_use . items ( ) ) } ) )
out = yaml . dump ( { ' pool ' : pool , ' in_use ' : dict ( ( k , now - v ) for k , v in in_use . items ( ) ) } )
mu . release ( )
self . wfile . write ( out )
elif self . path == ' /quitquitquit ' :
self . send_response ( 200 )
self . end_headers ( )