From bf52746e5a94b4cb7a4117d982ab92c2838bf974 Mon Sep 17 00:00:00 2001 From: git Date: Mon, 31 Aug 2020 19:13:19 +0800 Subject: [PATCH 01/18] Reliable list_running_servers(runtime_dir) design --- notebook/notebookapp.py | 66 ++++++++++++++++++++---------- notebook/tests/test_notebookapp.py | 41 ++++++++++++++----- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index dc7c84c572..6b175e0741 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -429,20 +429,10 @@ def start(self): self.log.info("Wrote hashed password to %s" % self.config_file) -def shutdown_server(server_info, timeout=5, log=None): - """Shutdown a notebook server in a separate process. - - *server_info* should be a dictionary as produced by list_running_servers(). - - Will first try to request shutdown using /api/shutdown . - On Unix, if the server is still running after *timeout* seconds, it will - send SIGTERM. After another timeout, it escalates to SIGKILL. - - Returns True if the server was stopped by any means, False if stopping it - failed (on Windows). - """ +def kernel_request(server_info, path='/login', method='GET', body=None, headers=None, timeout=5, log=None): + """query a notebook server in a separate process.""" from tornado import gen - from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest + from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest, HTTPError from tornado.netutil import Resolver url = server_info['url'] pid = server_info['pid'] @@ -468,12 +458,45 @@ def resolve(self, host, port, *args, **kwargs): resolver = UnixSocketResolver(resolver=Resolver()) - req = HTTPRequest(url + 'api/shutdown', method='POST', body=b'', headers={ - 'Authorization': 'token ' + server_info['token'] - }) - if log: log.debug("POST request to %sapi/shutdown", url) - AsyncHTTPClient.configure(None, resolver=resolver) - HTTPClient(AsyncHTTPClient).fetch(req) + fullurl = urljoin(url, path) + headers = dict(headers) if headers is not None else {} + headers.setdefault('Authorization', 'token ' + server_info['token']) + req = HTTPRequest(fullurl, + method=method, body=body, headers=headers, + follow_redirects=True, + decompress_response=True, + allow_nonstandard_methods=False, + validate_cert=False + ) + if log: log.debug("%s request to %s", method, fullurl) + + savedAsyncHTTPClient = AsyncHTTPClient._save_configuration() + try: + AsyncHTTPClient.configure(None, resolver=resolver) + response = HTTPClient(AsyncHTTPClient).fetch(req) + except HTTPError as e: + if e.response is not None: + response = e.response + else: + raise + finally: + AsyncHTTPClient._restore_configuration(savedAsyncHTTPClient) + + return response + + +def shutdown_server(server_info, timeout=5, log=None): + """Shutdown a notebook server in a separate process. + *server_info* should be a dictionary as produced by list_running_servers(). + Will first try to request shutdown using /api/shutdown . + On Unix, if the server is still running after *timeout* seconds, it will + send SIGTERM. After another timeout, it escalates to SIGKILL. + Returns True if the server was stopped by any means, False if stopping it + failed (on Windows). + """ + url = server_info['url'] + pid = server_info['pid'] + kernel_request(server_info, path='api/shutdown', method='POST', body=b'', timeout=timeout, log=log) # Poll to see if it shut down. for _ in range(timeout*10): @@ -2294,9 +2317,10 @@ def list_running_servers(runtime_dir=None): with io.open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f: info = json.load(f) - # Simple check whether that process is really still running + # active check whether that process is really available via real HTTP request # Also remove leftover files from IPython 2.x without a pid field - if ('pid' in info) and check_pid(info['pid']): + response = kernel_request(info, path='/login') + if response.body.find(b'Jupyter Notebook') > 0: yield info else: # If the process has died, try to delete its info file diff --git a/notebook/tests/test_notebookapp.py b/notebook/tests/test_notebookapp.py index d8543feed9..a2d531af6d 100644 --- a/notebook/tests/test_notebookapp.py +++ b/notebook/tests/test_notebookapp.py @@ -30,21 +30,40 @@ def test_help_output(): check_help_all_output('notebook') def test_server_info_file(): + import threading, tornado.ioloop as iom, tornado.platform.asyncio as torio td = TemporaryDirectory() + + if iom.asyncio is not None: + iom.asyncio.set_event_loop_policy(torio.AnyThreadEventLoopPolicy()) + iom.IOLoop.configure("tornado.platform.asyncio.AsyncIOLoop") + nbapp = NotebookApp(runtime_dir=td.name, log=logging.getLogger()) - def get_servers(): - return list(notebookapp.list_running_servers(nbapp.runtime_dir)) nbapp.initialize(argv=[]) + nbapp.io_loop = iom.IOLoop.current() + nbapp.open_browser = False + super(NotebookApp, nbapp).start() nbapp.write_server_info_file() - servers = get_servers() - nt.assert_equal(len(servers), 1) - nt.assert_equal(servers[0]['port'], nbapp.port) - nt.assert_equal(servers[0]['url'], nbapp.connection_url) - nbapp.remove_server_info_file() - nt.assert_equal(get_servers(), []) - # The ENOENT error should be silenced. - nbapp.remove_server_info_file() + def check_thread(): + try: + servers = list(notebookapp.list_running_servers(nbapp.runtime_dir)) + nt.assert_equal(len(servers), 1) + nt.assert_equal(servers[0]['port'], nbapp.port) + nt.assert_equal(servers[0]['url'], nbapp.connection_url) + finally: + nbapp.stop() + + nbapp.io_loop.add_callback(nbapp.io_loop.run_in_executor, executor=None, func=check_thread) + + if sys.platform.startswith("win"): + pc = iom.PeriodicCallback(lambda: None, 5000) + pc.start() + try: + nbapp.io_loop.start() + except KeyboardInterrupt: + print("Interrupted...") + finally: + nbapp.remove_server_info_file() def test_nb_dir(): with TemporaryDirectory() as td: @@ -200,4 +219,4 @@ def test_run(self): def test_list_running_sock_servers(self): servers = list(notebookapp.list_running_servers()) assert len(servers) >= 1 - assert self.sock in {info['sock'] for info in servers} + assert self.sock in {info['sock'] for info in servers} \ No newline at end of file From 7ae97ffcad346b88f9c880edf3759f060dd8ab99 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 02:39:54 +0800 Subject: [PATCH 02/18] both TCP and UNIX socket are works. --- notebook/notebookapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 6b175e0741..30f0798d2f 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2320,7 +2320,8 @@ def list_running_servers(runtime_dir=None): # active check whether that process is really available via real HTTP request # Also remove leftover files from IPython 2.x without a pid field response = kernel_request(info, path='/login') - if response.body.find(b'Jupyter Notebook') > 0: + if response.body.find(b'Jupyter Notebook') > 0 or \ + response.body.find(b'Home Page - Select or create a notebook') > 0: yield info else: # If the process has died, try to delete its info file From 9cbf95f8d7fa8938c84b38d16e383e67befc93bb Mon Sep 17 00:00:00 2001 From: Abael He Date: Wed, 2 Sep 2020 05:05:00 +0800 Subject: [PATCH 03/18] Update notebook/notebookapp.py Accepted, Thanks. Co-authored-by: Kevin Bates --- notebook/notebookapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 30f0798d2f..a717bd9285 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -430,7 +430,7 @@ def start(self): def kernel_request(server_info, path='/login', method='GET', body=None, headers=None, timeout=5, log=None): - """query a notebook server in a separate process.""" + """Query a notebook server in a separate process.""" from tornado import gen from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest, HTTPError from tornado.netutil import Resolver From d774b3e89a743f49d50f265c9007ca04f9435bfd Mon Sep 17 00:00:00 2001 From: Abael He Date: Wed, 2 Sep 2020 05:06:32 +0800 Subject: [PATCH 04/18] Update notebook/notebookapp.py Accepted. Co-authored-by: Kevin Bates --- notebook/notebookapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index a717bd9285..6b2a426d02 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2317,7 +2317,7 @@ def list_running_servers(runtime_dir=None): with io.open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f: info = json.load(f) - # active check whether that process is really available via real HTTP request + # Actively check whether that process is really available via an HTTP request # Also remove leftover files from IPython 2.x without a pid field response = kernel_request(info, path='/login') if response.body.find(b'Jupyter Notebook') > 0 or \ From ca17f0b38bcfc24cfd9ca8528da0331ec6ff8a8b Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 06:00:57 +0800 Subject: [PATCH 05/18] consistent persistent file naming rule --- notebook/notebookapp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 6b2a426d02..d8157adf1a 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -494,7 +494,6 @@ def shutdown_server(server_info, timeout=5, log=None): Returns True if the server was stopped by any means, False if stopping it failed (on Windows). """ - url = server_info['url'] pid = server_info['pid'] kernel_request(server_info, path='api/shutdown', method='POST', body=b'', timeout=timeout, log=log) From a4aae8fe6b3129bae6337255678e0d34c29a6df0 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 12:11:25 +0800 Subject: [PATCH 06/18] consistent persistent file naming rule and same version check rule --- notebook/notebookapp.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index d8157adf1a..1d1d5e999d 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -1421,18 +1421,25 @@ def _update_mathjax_config(self, change): "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")) ) + file_id = Unicode() + + @default('file_id') + def _default_file_id(self): + int_hash = "%s" % (hash(self.sock) if self.sock else self.port) + return str(int_hash) + info_file = Unicode() @default('info_file') def _default_info_file(self): - info_file = "nbserver-%s.json" % os.getpid() + info_file = "nbserver-%s.json" % self.file_id return os.path.join(self.runtime_dir, info_file) browser_open_file = Unicode() @default('browser_open_file') def _default_browser_open_file(self): - basename = "nbserver-%s-open.html" % os.getpid() + basename = "nbserver-%s-open.html" % self.file_id return os.path.join(self.runtime_dir, basename) pylab = Unicode('disabled', config=True, @@ -2311,6 +2318,7 @@ def list_running_servers(runtime_dir=None): if not os.path.isdir(runtime_dir): return + api_version_json_bytes = json.dumps({"version":__version__}).encode() for file_name in os.listdir(runtime_dir): if re.match('nbserver-(.+).json', file_name): with io.open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f: @@ -2318,9 +2326,8 @@ def list_running_servers(runtime_dir=None): # Actively check whether that process is really available via an HTTP request # Also remove leftover files from IPython 2.x without a pid field - response = kernel_request(info, path='/login') - if response.body.find(b'Jupyter Notebook') > 0 or \ - response.body.find(b'Home Page - Select or create a notebook') > 0: + response = kernel_request(info, path='/api') + if response.body == api_version_json_bytes: yield info else: # If the process has died, try to delete its info file From 40796a67a77623d851fa335ef9af328b5e392803 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 14:16:15 +0800 Subject: [PATCH 07/18] updated list_running_servers() and write_browser_open_file() ONLY if not self.sock --- notebook/notebookapp.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 1d1d5e999d..af10009f56 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2244,7 +2244,8 @@ def start(self): "resources section at https://jupyter.org/community.html.")) self.write_server_info_file() - self.write_browser_open_file() + if not self.sock: + self.write_browser_open_file() if (self.open_browser or self.file_to_run) and not self.sock: self.launch_browser() @@ -2326,7 +2327,7 @@ def list_running_servers(runtime_dir=None): # Actively check whether that process is really available via an HTTP request # Also remove leftover files from IPython 2.x without a pid field - response = kernel_request(info, path='/api') + response = kernel_request(info, path=url_path_join(info.get('base_url') or '/','/api')) if response.body == api_version_json_bytes: yield info else: From 737a9636008b231a1b35dac412816c969841e85d Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 15:29:51 +0800 Subject: [PATCH 08/18] bind JUPYTER_PORT or random available port --- notebook/jstest.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/notebook/jstest.py b/notebook/jstest.py index 6849c9fd04..ea111b5793 100644 --- a/notebook/jstest.py +++ b/notebook/jstest.py @@ -291,11 +291,25 @@ def will_run(self): should_run = all(have[a] for a in self.requirements + [self.engine]) return should_run + @property + def file_id(self): + if self.server_port == 0: + port_env = os.environ.get('JUPYTER_PORT') or '0' + port_env =int(port_env) if port_env.isdigit() else 0 + if port_env > 0: + self.server_port = port_env + else: + from tornado.netutil import bind_sockets + sockets = bind_sockets(0, '127.0.0.1') + self.server_port = sockets[0].getsockname()[:2][1] + return str(self.server_port) + def _init_server(self): "Start the notebook server in a separate process" self.server_command = command = [sys.executable, '-m', 'notebook', '--no-browser', + '--port=%s' % self.file_id, '--notebook-dir', self.nbdir.name, '--NotebookApp.token=', '--NotebookApp.base_url=%s' % self.base_url, @@ -317,7 +331,7 @@ def _init_server(self): with patch.dict('os.environ', {'HOME': self.home.name}): runtime_dir = jupyter_runtime_dir() self.server_info_file = os.path.join(runtime_dir, - 'nbserver-%i.json' % self.server.pid + 'nbserver-%i.json' % self.file_id ) self._wait_for_server() From 7139ad613ed8338dcfa094250e7be5010e2ea377 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 15:42:55 +0800 Subject: [PATCH 09/18] nbserver-%s.json % self.file_id --- notebook/jstest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/jstest.py b/notebook/jstest.py index ea111b5793..60def8853a 100644 --- a/notebook/jstest.py +++ b/notebook/jstest.py @@ -331,7 +331,7 @@ def _init_server(self): with patch.dict('os.environ', {'HOME': self.home.name}): runtime_dir = jupyter_runtime_dir() self.server_info_file = os.path.join(runtime_dir, - 'nbserver-%i.json' % self.file_id + 'nbserver-%s.json' % self.file_id ) self._wait_for_server() From 19210cb64ed8a5ae38e511a5f65ed10633a5dd6e Mon Sep 17 00:00:00 2001 From: git Date: Wed, 2 Sep 2020 16:34:15 +0800 Subject: [PATCH 10/18] tests update for new connection file naming rule --- notebook/jstest.py | 7 ++++--- notebook/tests/selenium/conftest.py | 5 ++++- notebook/utils.py | 6 ++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/notebook/jstest.py b/notebook/jstest.py index 60def8853a..57a7784a57 100644 --- a/notebook/jstest.py +++ b/notebook/jstest.py @@ -31,6 +31,8 @@ from ipython_genutils.tempdir import TemporaryDirectory from subprocess import TimeoutExpired +from notebook.utils import available_port + def popen_wait(p, timeout): return p.wait(timeout) @@ -299,9 +301,7 @@ def file_id(self): if port_env > 0: self.server_port = port_env else: - from tornado.netutil import bind_sockets - sockets = bind_sockets(0, '127.0.0.1') - self.server_port = sockets[0].getsockname()[:2][1] + self.server_port = available_port() return str(self.server_port) def _init_server(self): @@ -321,6 +321,7 @@ def _init_server(self): self.stream_capturer = c = StreamCapturer() c.start() env = os.environ.copy() + env['PYTHONPATH']= ':'.join(sys.path) env.update(self.env) self.server = subprocess.Popen(command, stdout = c.writefd, diff --git a/notebook/tests/selenium/conftest.py b/notebook/tests/selenium/conftest.py index 64cdfa23bd..b27ab5383b 100644 --- a/notebook/tests/selenium/conftest.py +++ b/notebook/tests/selenium/conftest.py @@ -11,6 +11,7 @@ import time from urllib.parse import urljoin +from notebook.utils import available_port from selenium.webdriver import Firefox, Remote, Chrome from .utils import Notebook @@ -41,6 +42,7 @@ def notebook_server(): nbdir = info['nbdir'] = pjoin(td, 'notebooks') os.makedirs(pjoin(nbdir, u'sub ∂ir1', u'sub ∂ir 1a')) os.makedirs(pjoin(nbdir, u'sub ∂ir2', u'sub ∂ir 1b')) + port = available_port() info['extra_env'] = { 'JUPYTER_CONFIG_DIR': pjoin(td, 'jupyter_config'), @@ -52,6 +54,7 @@ def notebook_server(): command = [sys.executable, '-m', 'notebook', '--no-browser', + '--port=%s' % port, '--notebook-dir', nbdir, # run with a base URL that would be escaped, # to test that we don't double-escape URLs @@ -60,7 +63,7 @@ def notebook_server(): print("command=", command) proc = info['popen'] = Popen(command, cwd=nbdir, env=env) info_file_path = pjoin(td, 'jupyter_runtime', - 'nbserver-%i.json' % proc.pid) + 'nbserver-%s.json' % port) info.update(_wait_for_server(proc, info_file_path)) print("Notebook server info:", info) diff --git a/notebook/utils.py b/notebook/utils.py index c8bae60543..389be7abd0 100644 --- a/notebook/utils.py +++ b/notebook/utils.py @@ -22,6 +22,7 @@ # tornado.concurrent.Future is asyncio.Future # in tornado >=5 with Python 3 from tornado.concurrent import Future as TornadoFuture +from tornado.netutil import bind_sockets from tornado import gen from ipython_genutils import py3compat @@ -30,6 +31,11 @@ UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768) +def available_port(host='127.0.0.1'): + sockets = bind_sockets(0, host) + return sockets[0].getsockname()[:2][1] + + def exists(path): """Replacement for `os.path.exists` which works for host mapped volumes on Windows containers From 281966d8d5218f942ef6e9082045440c4bfe4ae5 Mon Sep 17 00:00:00 2001 From: Abael He Date: Thu, 3 Sep 2020 08:05:38 +0800 Subject: [PATCH 11/18] Update notebookapp.py --- notebook/notebookapp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index af10009f56..d2870fbaa3 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -435,7 +435,6 @@ def kernel_request(server_info, path='/login', method='GET', body=None, headers= from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest, HTTPError from tornado.netutil import Resolver url = server_info['url'] - pid = server_info['pid'] resolver = None # UNIX Socket handling. From 07570c67bb6c84194298907aea9253199da2ae3f Mon Sep 17 00:00:00 2001 From: Abael He Date: Thu, 3 Sep 2020 08:11:22 +0800 Subject: [PATCH 12/18] Update notebook/notebookapp.py Co-authored-by: Kevin Bates --- notebook/notebookapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index d2870fbaa3..c377cd0e89 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -1424,7 +1424,7 @@ def _update_mathjax_config(self, change): @default('file_id') def _default_file_id(self): - int_hash = "%s" % (hash(self.sock) if self.sock else self.port) + return str(hash(self.sock) if self.sock else self.port) return str(int_hash) info_file = Unicode() From 978d883c8cc3a9e7410eafd0b1cf0c5f2f558944 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 3 Sep 2020 21:19:26 +0800 Subject: [PATCH 13/18] Multi-instance and multi-version of Jupyter Notebook --- notebook/__init__.py | 3 +++ notebook/base/handlers.py | 3 ++- notebook/notebookapp.py | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/notebook/__init__.py b/notebook/__init__.py index c72096f26b..364effe4b0 100644 --- a/notebook/__init__.py +++ b/notebook/__init__.py @@ -1,6 +1,9 @@ """The Jupyter HTML Notebook""" import os + +JUPYTER_NOTEBOOK_TAG = "JupyterNotebook" + # Packagers: modify this line if you store the notebook static files elsewhere DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static") diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 743f7bac73..ccf455bfb5 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -34,6 +34,7 @@ from notebook.i18n import combine_translations from notebook.utils import is_hidden, url_path_join, url_is_absolute, url_escape, urldecode_unix_socket_path from notebook.services.security import csp_report_uri +from notebook import JUPYTER_NOTEBOOK_TAG #----------------------------------------------------------------------------- # Top-level handlers @@ -849,7 +850,7 @@ class APIVersionHandler(APIHandler): def get(self): # not authenticated, so give as few info as possible - self.finish(json.dumps({"version":notebook.__version__})) + self.finish(json.dumps({"version": notebook.__version__, "module": JUPYTER_NOTEBOOK_TAG})) class TrailingSlashHandler(web.RequestHandler): diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index c377cd0e89..6eb733356e 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -71,6 +71,7 @@ from tornado.netutil import bind_unix_socket from notebook import ( + JUPYTER_NOTEBOOK_TAG, DEFAULT_NOTEBOOK_PORT, DEFAULT_STATIC_FILES_PATH, DEFAULT_TEMPLATE_PATH_LIST, @@ -2327,9 +2328,10 @@ def list_running_servers(runtime_dir=None): # Actively check whether that process is really available via an HTTP request # Also remove leftover files from IPython 2.x without a pid field response = kernel_request(info, path=url_path_join(info.get('base_url') or '/','/api')) - if response.body == api_version_json_bytes: + try: + assert JUPYTER_NOTEBOOK_TAG == json.loads(response.body)["module"] yield info - else: + except: # If the process has died, try to delete its info file try: os.unlink(os.path.join(runtime_dir, file_name)) From 958151a7b8df4f1a0cd13d96c751a961f416ce38 Mon Sep 17 00:00:00 2001 From: git Date: Thu, 3 Sep 2020 22:43:55 +0800 Subject: [PATCH 14/18] json.loads(response.body) ---> json.loads(response.body.decode()) --- notebook/notebookapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 6eb733356e..2cae38c31c 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2329,7 +2329,7 @@ def list_running_servers(runtime_dir=None): # Also remove leftover files from IPython 2.x without a pid field response = kernel_request(info, path=url_path_join(info.get('base_url') or '/','/api')) try: - assert JUPYTER_NOTEBOOK_TAG == json.loads(response.body)["module"] + assert JUPYTER_NOTEBOOK_TAG == json.loads(response.body.decode())["module"] yield info except: # If the process has died, try to delete its info file From a305e0d57f5cced26201a27ec69a892e8107487a Mon Sep 17 00:00:00 2001 From: git Date: Fri, 4 Sep 2020 00:18:59 +0800 Subject: [PATCH 15/18] User Notifications for policy change of list_running_servers() --- notebook/notebookapp.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 2cae38c31c..961345f0ce 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2319,7 +2319,6 @@ def list_running_servers(runtime_dir=None): if not os.path.isdir(runtime_dir): return - api_version_json_bytes = json.dumps({"version":__version__}).encode() for file_name in os.listdir(runtime_dir): if re.match('nbserver-(.+).json', file_name): with io.open(os.path.join(runtime_dir, file_name), encoding='utf-8') as f: @@ -2328,15 +2327,36 @@ def list_running_servers(runtime_dir=None): # Actively check whether that process is really available via an HTTP request # Also remove leftover files from IPython 2.x without a pid field response = kernel_request(info, path=url_path_join(info.get('base_url') or '/','/api')) - try: - assert JUPYTER_NOTEBOOK_TAG == json.loads(response.body.decode())["module"] - yield info - except: - # If the process has died, try to delete its info file + def flush_info_file(): try: os.unlink(os.path.join(runtime_dir, file_name)) except OSError: pass # TODO: This should warn or log or something + + version_dict = {} + erro = False + try: + version_dict.update(json.loads(response.body.decode())) + except: + flush_info_file() + erro = True + else: + received_version = version_dict.get('version', '0.0.0') + msg_special = "Jupyter Notebook HIGHER VERSION detected: %s, current:%s" %( + received_version, __version__) + msg_upgrade = "!!! Jupyter Notebook Upgrade REQUIRED: >=%s, got:%s(info file removed) !!!" % ( + __version__, received_version) + + if JUPYTER_NOTEBOOK_TAG != version_dict.get('module'): + print(msg_upgrade) + flush_info_file() + else: + if __version__ < received_version: + print(msg_special) + yield info + + + #----------------------------------------------------------------------------- # Main entry point #----------------------------------------------------------------------------- From 908a630761aa849e0824b2710d61560d7df278d3 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 4 Sep 2020 00:20:37 +0800 Subject: [PATCH 16/18] User Notifications for policy change of list_running_servers() --- notebook/notebookapp.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 961345f0ce..3baea2cfdc 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2331,15 +2331,13 @@ def flush_info_file(): try: os.unlink(os.path.join(runtime_dir, file_name)) except OSError: - pass # TODO: This should warn or log or something + pass version_dict = {} - erro = False try: version_dict.update(json.loads(response.body.decode())) except: flush_info_file() - erro = True else: received_version = version_dict.get('version', '0.0.0') msg_special = "Jupyter Notebook HIGHER VERSION detected: %s, current:%s" %( From 57330ff68ddaac913c615263fb1240acfd74d1c6 Mon Sep 17 00:00:00 2001 From: git Date: Fri, 4 Sep 2020 00:37:45 +0800 Subject: [PATCH 17/18] let user make choices to their data, preference or environment. --- notebook/notebookapp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 3baea2cfdc..83b24d977f 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -107,6 +107,7 @@ Any, Dict, Unicode, Integer, List, Bool, Bytes, Instance, TraitError, Type, Float, observe, default, validate ) +from traitlets.config.configurable import MultipleInstanceError from ipython_genutils import py3compat from jupyter_core.paths import jupyter_runtime_dir, jupyter_path from notebook._sysinfo import get_sys_info @@ -2342,12 +2343,11 @@ def flush_info_file(): received_version = version_dict.get('version', '0.0.0') msg_special = "Jupyter Notebook HIGHER VERSION detected: %s, current:%s" %( received_version, __version__) - msg_upgrade = "!!! Jupyter Notebook Upgrade REQUIRED: >=%s, got:%s(info file removed) !!!" % ( + msg_upgrade = "Jupyter Notebook Upgrade REQUIRED: >=%s, got:%s(info file removed)" % ( __version__, received_version) if JUPYTER_NOTEBOOK_TAG != version_dict.get('module'): - print(msg_upgrade) - flush_info_file() + raise MultipleInstanceError(msg_upgrade) else: if __version__ < received_version: print(msg_special) From 40850c46a80f7a1b34c09d9eeb1ae84bcc3643ce Mon Sep 17 00:00:00 2001 From: git Date: Sat, 5 Sep 2020 01:00:01 +0800 Subject: [PATCH 18/18] better message to users --- notebook/notebookapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 83b24d977f..289eb03425 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -2341,7 +2341,7 @@ def flush_info_file(): flush_info_file() else: received_version = version_dict.get('version', '0.0.0') - msg_special = "Jupyter Notebook HIGHER VERSION detected: %s, current:%s" %( + msg_special = "WARNING: Jupyter Notebook HIGHER VERSION was detected: %s, current:%s" %( received_version, __version__) msg_upgrade = "Jupyter Notebook Upgrade REQUIRED: >=%s, got:%s(info file removed)" % ( __version__, received_version)