Skip to content

Commit 5aa6e0d

Browse files
committed
Feedback: add socket usage check on bind and test.
1 parent 02cd1fa commit 5aa6e0d

File tree

3 files changed

+53
-1
lines changed

3 files changed

+53
-1
lines changed

notebook/notebookapp.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
check_pid,
116116
pathname2url,
117117
run_sync,
118+
unix_socket_in_use,
118119
url_escape,
119120
url_path_join,
120121
urldecode_unix_socket_path,
@@ -1638,12 +1639,16 @@ def _bind_http_server(self):
16381639
return self._bind_http_server_unix() if self.sock else self._bind_http_server_tcp()
16391640

16401641
def _bind_http_server_unix(self):
1642+
if unix_socket_in_use(self.sock):
1643+
self.log.warning(_('The socket %s is already in use.') % self.sock)
1644+
return False
1645+
16411646
try:
16421647
sock = bind_unix_socket(self.sock, mode=int(self.sock_mode.encode(), 8))
16431648
self.http_server.add_socket(sock)
16441649
except socket.error as e:
16451650
if e.errno == errno.EADDRINUSE:
1646-
self.log.info(_('The socket %s is already in use.') % self.sock)
1651+
self.log.warning(_('The socket %s is already in use.') % self.sock)
16471652
return False
16481653
elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
16491654
self.log.warning(_("Permission to listen on sock %s denied") % self.sock)

notebook/tests/test_notebookapp_integration.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,32 @@ def test_stop_multi_integration():
135135
p1.wait()
136136
p2.wait()
137137
p3.wait()
138+
139+
140+
@skip_win32
141+
def test_launch_socket_collision():
142+
"""Tests UNIX socket in-use detection for lifecycle correctness."""
143+
sock = UNIXSocketNotebookTestBase.sock
144+
check_msg = 'socket %s is already in use' % sock
145+
146+
_ensure_stopped()
147+
148+
# Start a server.
149+
cmd = ['jupyter-notebook', '--sock=%s' % sock]
150+
p1 = subprocess.Popen(cmd)
151+
time.sleep(3)
152+
153+
# Try to start a server bound to the same UNIX socket.
154+
try:
155+
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
156+
except subprocess.CalledProcessError as e:
157+
assert check_msg in e.output.decode()
158+
else:
159+
raise AssertionError('expected error, instead got %s' % e.output.decode())
160+
161+
# Stop the background server, ensure it's stopped and wait on the process to exit.
162+
subprocess.check_call(['jupyter-notebook', 'stop', sock])
163+
164+
_ensure_stopped()
165+
166+
p1.wait()

notebook/utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import errno
1212
import inspect
1313
import os
14+
import socket
1415
import stat
1516
import sys
1617
from distutils.version import LooseVersion
@@ -22,6 +23,7 @@
2223
# in tornado >=5 with Python 3
2324
from tornado.concurrent import Future as TornadoFuture
2425
from tornado import gen
26+
import requests_unixsocket
2527
from ipython_genutils import py3compat
2628

2729
# UF_HIDDEN is a stat flag not defined in the stat module.
@@ -382,3 +384,19 @@ def urldecode_unix_socket_path(socket_path):
382384
def urlencode_unix_socket(socket_path):
383385
"""Encodes a UNIX socket URL from a socket path for the `http+unix` URI form."""
384386
return 'http+unix://%s' % urlencode_unix_socket_path(socket_path)
387+
388+
389+
def unix_socket_in_use(socket_path):
390+
"""Checks whether a UNIX socket path on disk is in use by attempting to connect to it."""
391+
if not os.path.exists(socket_path):
392+
return False
393+
394+
try:
395+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
396+
sock.connect(socket_path)
397+
except socket.error:
398+
return False
399+
else:
400+
return True
401+
finally:
402+
sock.close()

0 commit comments

Comments
 (0)