Feedback: add socket usage check on bind and test.

Kris Wilson 6 years ago
parent 02cd1fa8a0
commit 5aa6e0dfd5

@ -115,6 +115,7 @@ from .utils import (
check_pid,
pathname2url,
run_sync,
unix_socket_in_use,
url_escape,
url_path_join,
urldecode_unix_socket_path,
@ -1638,12 +1639,16 @@ class NotebookApp(JupyterApp):
return self._bind_http_server_unix() if self.sock else self._bind_http_server_tcp()
def _bind_http_server_unix(self):
if unix_socket_in_use(self.sock):
self.log.warning(_('The socket %s is already in use.') % self.sock)
return False
try:
sock = bind_unix_socket(self.sock, mode=int(self.sock_mode.encode(), 8))
self.http_server.add_socket(sock)
except socket.error as e:
if e.errno == errno.EADDRINUSE:
self.log.info(_('The socket %s is already in use.') % self.sock)
self.log.warning(_('The socket %s is already in use.') % self.sock)
return False
elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
self.log.warning(_("Permission to listen on sock %s denied") % self.sock)

@ -135,3 +135,32 @@ def test_stop_multi_integration():
p1.wait()
p2.wait()
p3.wait()
@skip_win32
def test_launch_socket_collision():
"""Tests UNIX socket in-use detection for lifecycle correctness."""
sock = UNIXSocketNotebookTestBase.sock
check_msg = 'socket %s is already in use' % sock
_ensure_stopped()
# Start a server.
cmd = ['jupyter-notebook', '--sock=%s' % sock]
p1 = subprocess.Popen(cmd)
time.sleep(3)
# Try to start a server bound to the same UNIX socket.
try:
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
assert check_msg in e.output.decode()
else:
raise AssertionError('expected error, instead got %s' % e.output.decode())
# Stop the background server, ensure it's stopped and wait on the process to exit.
subprocess.check_call(['jupyter-notebook', 'stop', sock])
_ensure_stopped()
p1.wait()

@ -11,6 +11,7 @@ import ctypes
import errno
import inspect
import os
import socket
import stat
import sys
from distutils.version import LooseVersion
@ -22,6 +23,7 @@ from urllib.request import pathname2url
# in tornado >=5 with Python 3
from tornado.concurrent import Future as TornadoFuture
from tornado import gen
import requests_unixsocket
from ipython_genutils import py3compat
# UF_HIDDEN is a stat flag not defined in the stat module.
@ -382,3 +384,19 @@ def urldecode_unix_socket_path(socket_path):
def urlencode_unix_socket(socket_path):
"""Encodes a UNIX socket URL from a socket path for the `http+unix` URI form."""
return 'http+unix://%s' % urlencode_unix_socket_path(socket_path)
def unix_socket_in_use(socket_path):
"""Checks whether a UNIX socket path on disk is in use by attempting to connect to it."""
if not os.path.exists(socket_path):
return False
try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(socket_path)
except socket.error:
return False
else:
return True
finally:
sock.close()

Loading…
Cancel
Save