From a419f53ca961d6be010cbc59cf0788a091cc1309 Mon Sep 17 00:00:00 2001 From: adam Date: Sat, 13 Dec 2014 13:04:21 +0800 Subject: [PATCH] Fix port checking code preventing startup directly after a SG restart --- CHANGES.md | 1 + SickBeard.py | 6 ++-- sickbeard/helpers.py | 74 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 97b6b20b..bab9555c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ * Overhaul all Add Show pages * Fix Display Show next/previous when show list is split * Change Display Show next/previous when show list is not split to loop around +* Fix port checking code preventing startup directly after a SG restart [develop changelog] * Add TVRage network name standardization diff --git a/SickBeard.py b/SickBeard.py index 63838433..684e6501 100755 --- a/SickBeard.py +++ b/SickBeard.py @@ -330,13 +330,11 @@ class SickGear(object): # start web server try: # used to check if existing SG instances have been started - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind((self.web_options['host'], self.web_options['port'])) - s.close() + sickbeard.helpers.wait_for_free_port(self.web_options['host'], self.web_options['port']) self.webserver = WebServer(self.web_options) self.webserver.start() - except IOError: + except Exception: logger.log(u"Unable to start web server, is something else running on port %d?" % self.startPort, logger.ERROR) if sickbeard.LAUNCH_BROWSER and not self.runAsDaemon: diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index 74805462..7f29e943 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -1428,5 +1428,77 @@ def get_size(start_path='.'): total_size += ek.ek(os.path.getsize, fp) return total_size + def remove_article(text=''): - return re.sub(r'(?i)^(?:(?:A(?!\s+to)n?)|The)\s(\w)', r'\1', text) \ No newline at end of file + return re.sub(r'(?i)^(?:(?:A(?!\s+to)n?)|The)\s(\w)', r'\1', text) + + +def client_host(server_host): + '''Extracted from cherrypy libs + Return the host on which a client can connect to the given listener.''' + if server_host == '0.0.0.0': + # 0.0.0.0 is INADDR_ANY, which should answer on localhost. + return '127.0.0.1' + if server_host in ('::', '::0', '::0.0.0.0'): + # :: is IN6ADDR_ANY, which should answer on localhost. + # ::0 and ::0.0.0.0 are non-canonical but common ways to write + # IN6ADDR_ANY. + return '::1' + return server_host + + +def wait_for_free_port(host, port): + '''Extracted from cherrypy libs + Wait for the specified port to become free (drop requests).''' + if not host: + raise ValueError("Host values of '' or None are not allowed.") + for trial in range(50): + try: + # we are expecting a free port, so reduce the timeout + check_port(host, port, timeout=0.1) + except IOError: + # Give the old server thread time to free the port. + time.sleep(0.1) + else: + return + + raise IOError("Port %r not free on %r" % (port, host)) + + +def check_port(host, port, timeout=1.0): + '''Extracted from cherrypy libs + Raise an error if the given port is not free on the given host.''' + if not host: + raise ValueError("Host values of '' or None are not allowed.") + host = client_host(host) + port = int(port) + + import socket + + # AF_INET or AF_INET6 socket + # Get the correct address family for our host (allows IPv6 addresses) + try: + info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, + socket.SOCK_STREAM) + except socket.gaierror: + if ':' in host: + info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "", (host, port, 0, 0))] + else: + info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (host, port))] + + for res in info: + af, socktype, proto, canonname, sa = res + s = None + try: + s = socket.socket(af, socktype, proto) + # See http://groups.google.com/group/cherrypy-users/ + # browse_frm/thread/bbfe5eb39c904fe0 + s.settimeout(timeout) + s.connect((host, port)) + s.close() + raise IOError("Port %s is in use on %s; perhaps the previous " + "httpserver did not shut down properly." % + (repr(port), repr(host))) + except socket.error: + if s: + s.close()