Repository: jupyterhub/jupyterhub

Issue:
Better Timeout Error
I though there was a better page, but I just get a `500: Internal Server Error`

Of course it works if I reload

```
[I 2015-03-12 21:23:30.412 JupyterHub web:1825] 200 GET /hub/login (127.0.0.1) 26.16ms
[I 2015-03-12 21:23:38.358 JupyterHub spawner:351] Spawning jupyterhub-singleuser --user=bussonniermatthias --port=54038 --cookie-name=jupyter-hub-token-bussonniermatthias --base-url=/user/bussonniermatthias --hub-prefix=/hub/ --hub-api-url=http://localhost:8081/hub/api --ip=localhost --profile=mixed
[I 2015-03-12 21:23:46.800 bussonniermatthias application:297] Using existing profile dir: '/Users/bussonniermatthias/.ipython/profile_mixed'
[W 2015-03-12 21:23:46.836 bussonniermatthias loader:402] Unrecognized JSON config file version, assuming version 1
[W 2015-03-12 21:23:48.477 JupyterHub orm:378] bussonniermatthias's server never showed up at http://localhost:54038/user/bussonniermatthias after 10 seconds. Giving up
Traceback (most recent call last):
  File "/usr/local/bin/jupyterhub-singleuser", line 6, in <module>
    exec(compile(open(__file__).read(), __file__, 'exec'))
  File "/Users/bussonniermatthias/dev/jupyterhub/scripts/jupyterhub-singleuser", line 4, in <module>
    main()
  File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/singleuser.py", line 152, in main
    return SingleUserNotebookApp.launch_instance()
  File "/Users/bussonniermatthias/ipython/IPython/config/application.py", line 573, in launch_instance
    app.initialize(argv)
  File "<string>", line 2, in initialize
  File "/Users/bussonniermatthias/ipython/IPython/config/application.py", line 75, in catch_config_error
    return method(app, *args, **kwargs)
  File "/Users/bussonniermatthias/ipython/IPython/html/notebookapp.py", line 1004, in initialize
    self.init_components()
  File "/Users/bussonniermatthias/ipython/IPython/html/notebookapp.py", line 970, in init_components
    status = submodule.check_submodule_status()
  File "/Users/bussonniermatthias/ipython/IPython/utils/submodule.py", line 69, in check_submodule_status
    if not is_repo(root):
  File "/Users/bussonniermatthias/ipython/IPython/utils/submodule.py", line 49, in is_repo
    status, _ = proc.communicate()
  File "/usr/local/Cellar/python3/HEAD/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py", line 971, in communicate
    stdout, stderr = self._communicate(input, endtime, timeout)
  File "/usr/local/Cellar/python3/HEAD/Frameworks/Python.framework/Versions/3.5/lib/python3.5/subprocess.py", line 1601, in _communicate
    ready = selector.select(timeout)
  File "/usr/local/Cellar/python3/HEAD/Frameworks/Python.framework/Versions/3.5/lib/python3.5/selectors.py", line 367, in select
    fd_event_list = self._poll.poll(timeout)
KeyboardInterrupt

If you suspect this is an IPython bug, please report it at:
    https://github.com/ipython/ipython/issues
or send an email to the mailing list at ipython-dev@scipy.org

You can print a more detailed traceback right now with "%tb", or use "%debug"
to interactively debug it.

Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
    c.Application.verbose_crash=True
[E 2015-03-12 21:23:48.560 JupyterHub web:1421] Uncaught exception POST /hub/login?next= (127.0.0.1)
    HTTPServerRequest(protocol='http', host='localhost:8888', method='POST', uri='/hub/login?next=', version='HTTP/1.1', remote_ip='127.0.0.1', headers={'Content-Length': '48', 'Referer': 'http://localhost:8888/hub/login', 'Host': 'localhost:8888', 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.74 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Cache-Control': 'max-age=0', 'Connection': 'close', 'Origin': 'http://localhost:8888', 'Accept-Language': 'fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', 'Accept-Encoding': 'gzip, deflate', 'Cookie': 'jupyter-hub-token="2|1:0|10:1425925357|17:jupyter-hub-token|44:MjFkZWVlZWUzODc1NDg1Y2FjZDI4ODg1NzE2Mjk3OTM=|fc8252c1e20e3ca785b0db5990aca0310e5bccc7bd347f568c733e74a9e4b489"'})
    Traceback (most recent call last):
      File "/usr/local/Cellar/python3/HEAD/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/tornado/web.py", line 1348, in _execute
        result = yield result
      File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/handlers/login.py", line 60, in post
        yield self.spawn_single_user(user)
      File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/handlers/base.py", line 214, in spawn_single_user
        yield gen.with_timeout(timedelta(seconds=self.slow_spawn_timeout), f)
    tornado.gen.TimeoutError: Timeout


[E 2015-03-12 21:23:49.011 JupyterHub web:1825] 500 POST /hub/login?next= (127.0.0.1) 11866.06ms
[E 2015-03-12 21:23:49.380 JupyterHub gen:673] Exception in Future <tornado.concurrent.Future object at 0x108e21eb8> after timeout
    Traceback (most recent call last):
      File "/usr/local/Cellar/python3/HEAD/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/tornado/gen.py", line 669, in error_callback
        future.result()
      File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/orm.py", line 392, in spawn
        raise e
      File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/orm.py", line 370, in spawn
        yield self.server.wait_up(http=True, timeout=spawner.http_timeout)
      File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/orm.py", line 95, in wait_up
        yield wait_for_http_server(self.url, timeout=timeout)
      File "/Users/bussonniermatthias/dev/jupyterhub/jupyterhub/utils.py", line 83, in wait_for_http_server
        **locals()
    TimeoutError: Server at http://localhost:54038/user/bussonniermatthias didn't respond in 10 seconds
```



Discussion:

Resolving patch:
diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py
--- a/jupyterhub/handlers/base.py
+++ b/jupyterhub/handlers/base.py
@@ -22,6 +22,13 @@
 # pattern for the authentication token header
 auth_header_pat = re.compile(r'^token\s+([^\s]+)$')
 
+# mapping of reason: reason_message
+reasons = {
+    'timeout': "Failed to reach your server."
+        "  Please try again later."
+        "  Contact admin if the issue persists.",
+    'error': "Failed to start your server.  Please contact admin.",
+}
 
 class BaseHandler(RequestHandler):
     """Base Handler class with access to common methods and properties."""
@@ -310,7 +317,7 @@ def write_error(self, status_code, **kwargs):
             # construct the custom reason, if defined
             reason = getattr(exception, 'reason', '')
             if reason:
-                status_message = reason
+                message = reasons.get(reason, reason)
 
         # build template namespace
         ns = dict(
diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py
--- a/jupyterhub/orm.py
+++ b/jupyterhub/orm.py
@@ -348,10 +348,12 @@ def spawn(self, spawner_class, base_url='/', hub=None, config=None):
                 self.log.warn("{user}'s server failed to start in {s} seconds, giving up".format(
                     user=self.name, s=spawner.start_timeout,
                 ))
+                e.reason = 'timeout'
             else:
                 self.log.error("Unhandled error starting {user}'s server: {error}".format(
                     user=self.name, error=e,
                 ))
+                e.reason = 'error'
             try:
                 yield self.stop()
             except Exception:
@@ -378,7 +380,9 @@ def spawn(self, spawner_class, base_url='/', hub=None, config=None):
                         http_timeout=spawner.http_timeout,
                     )
                 )
+                e.reason = 'timeout'
             else:
+                e.reason = 'error'
                 self.log.error("Unhandled error waiting for {user}'s server to show up at {url}: {error}".format(
                     user=self.name, url=self.server.url, error=e,
                 ))
diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py
--- a/jupyterhub/spawner.py
+++ b/jupyterhub/spawner.py
@@ -54,7 +54,7 @@ class Spawner(LoggingConfigurable):
     )
 
     http_timeout = Integer(
-        10, config=True,
+        30, config=True,
         help="""Timeout (in seconds) before giving up on a spawned HTTP server
 
         Once a server has successfully been spawned, this is the amount of time


Test patch: