-
Notifications
You must be signed in to change notification settings - Fork 1.7k
server: when no port is specified, try to find one #1851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,13 +45,15 @@ | |
|
|
||
| import six | ||
| from six.moves import urllib | ||
| from six.moves import xrange # pylint: disable=redefined-builtin | ||
| from werkzeug import serving | ||
|
|
||
| from tensorboard import manager | ||
| from tensorboard import version | ||
| from tensorboard.backend import application | ||
| from tensorboard.backend.event_processing import event_file_inspector as efi | ||
| from tensorboard.plugins import base_plugin | ||
| from tensorboard.plugins.core import core_plugin | ||
| from tensorboard.util import tb_logging | ||
| from tensorboard.util import util | ||
|
|
||
|
|
@@ -347,36 +349,67 @@ class WerkzeugServer(serving.ThreadedWSGIServer, TensorBoardServer): | |
| def __init__(self, wsgi_app, flags): | ||
| self._flags = flags | ||
| host = flags.host | ||
|
|
||
| # base_port: what's the first port to which we should try to bind? | ||
| # should_scan: if that fails, shall we try additional ports? | ||
| (base_port, should_scan) = ( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A multi-valued, multi-line ternary is kind of a complicated beast to read. Maybe consider just doing should_scan = flags.port is None
base_port = flags.port if flags.port is not None else core_plugin.DEFAULT_PORT
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed; that’s cleaner. Will patch. |
||
| (flags.port, False) | ||
| if flags.port is not None | ||
| else (core_plugin.DEFAULT_PORT, True) | ||
| ) | ||
| if base_port > 0xFFFF: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard cannot bind to port %d > %d' % (base_port, 0xFFFF) | ||
| ) | ||
| max_attempts = 10 if should_scan else 1 | ||
| base_port = min(base_port + max_attempts, 65536) - max_attempts | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you use either 0xFFFF or 65536 consistently?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure: |
||
|
|
||
| self._auto_wildcard = False | ||
| if not host: | ||
| # Without an explicit host, we default to serving on all interfaces, | ||
| # and will attempt to serve both IPv4 and IPv6 traffic through one socket. | ||
| host = self._get_wildcard_address(flags.port) | ||
| host = self._get_wildcard_address(base_port) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strictly speaking this should be done per-port right? Though I agree it would be exotic to have the different ports have different wildcard addresses.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That’s true. I can move this, I think. |
||
| self._auto_wildcard = True | ||
| try: | ||
| super(WerkzeugServer, self).__init__(host, flags.port, wsgi_app) | ||
| except socket.error as e: | ||
| if hasattr(errno, 'EACCES') and e.errno == errno.EACCES: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard must be run as superuser to bind to port %d' % | ||
| flags.port) | ||
| elif hasattr(errno, 'EADDRINUSE') and e.errno == errno.EADDRINUSE: | ||
| if flags.port == 0: | ||
|
|
||
| for (attempt_index, port) in ( | ||
| enumerate(xrange(base_port, base_port + max_attempts))): | ||
| try: | ||
| # Yes, this invokes the super initializer potentially many | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is kind of scary... invoking super() multiple times seems like asking for trouble if the base class changes so this isn't safe. Maybe add a TODO to revisit this in a way that doesn't require that? (i.e. use a nested server class)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that it’s scary—not that there’s anything inherently I will add the TODO (and probably should have done that originally). FWIW, I actually did check every supertype in the mro, so I think it’s |
||
| # times. This seems to work fine, and looking at the superclass | ||
| # chain (type(self).__mro__) it doesn't seem that anything | ||
| # _should_ go wrong (nor does any superclass provide a facility | ||
| # to do this natively). | ||
| super(WerkzeugServer, self).__init__(host, port, wsgi_app) | ||
| break | ||
| except socket.error as e: | ||
| if hasattr(errno, 'EACCES') and e.errno == errno.EACCES: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason you're not using os.strerror()?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is not new in this PR; it just got indented by two extra To the question: I didn’t write the original code, but using
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need to check both the attr and e.errno? The examples I'm seeing just do e.errno == errno.EACCES
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code in question is not new in this PR, but I agree with the if e.errno == getattr(errno, "EADDRINUSE", None):because it’s simpler, faster, and avoids |
||
| raise TensorBoardServerException( | ||
| 'TensorBoard must be run as superuser to bind to port %d' % | ||
| port) | ||
| elif hasattr(errno, 'EADDRINUSE') and e.errno == errno.EADDRINUSE: | ||
| if attempt_index < max_attempts - 1: | ||
| continue | ||
| if port == 0: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard unable to find any open port') | ||
| elif should_scan: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard could not bind to any port around %s ' | ||
| '(tried %d times)' | ||
| % (base_port, max_attempts)) | ||
| else: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard could not bind to port %d, it was already in use' % | ||
| port) | ||
| elif hasattr(errno, 'EADDRNOTAVAIL') and e.errno == errno.EADDRNOTAVAIL: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard unable to find any open port') | ||
| else: | ||
| 'TensorBoard could not bind to unavailable address %s' % host) | ||
| elif hasattr(errno, 'EAFNOSUPPORT') and e.errno == errno.EAFNOSUPPORT: | ||
| raise TensorBoardServerException( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unfortunate Python2 doesn't have exception chaining. It would be ideal to capture the true cause and not swallow it. Any ideas on how to do that?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code in question is not new in this PR, but: no, unfortunately. In this particular case, I think that it’s probably fine. We’ve |
||
| 'TensorBoard could not bind to port %d, it was already in use' % | ||
| flags.port) | ||
| elif hasattr(errno, 'EADDRNOTAVAIL') and e.errno == errno.EADDRNOTAVAIL: | ||
| raise TensorBoardServerException( | ||
| 'TensorBoard could not bind to unavailable address %s' % host) | ||
| elif hasattr(errno, 'EAFNOSUPPORT') and e.errno == errno.EAFNOSUPPORT: | ||
| raise TensorBoardServerException( | ||
| 'Tensorboard could not bind to unsupported address family %s' % | ||
| host) | ||
| # Raise the raw exception if it wasn't identifiable as a user error. | ||
| raise | ||
| 'Tensorboard could not bind to unsupported address family %s' % | ||
| host) | ||
| # Raise the raw exception if it wasn't identifiable as a user error. | ||
| raise | ||
|
|
||
| def _get_wildcard_address(self, port): | ||
| """Returns a wildcard address for the port in question. | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.