2424import socket
2525import sys
2626import threading
27+ import time
2728import warnings
2829import webbrowser
2930
@@ -351,6 +352,51 @@ def start(self):
351352set_password (config_file = self .config_file )
352353self .log .info ("Wrote hashed password to %s" % self .config_file )
353354
355+ def shutdown_server (server_info ,timeout = 5 ,log = None ):
356+ """Shutdown a notebook server in a separate process.
357+
358+ *server_info* should be a dictionary as produced by list_running_servers().
359+
360+ Will first try to request shutdown using /api/shutdown .
361+ On Unix, if the server is still running after *timeout* seconds, it will
362+ send SIGTERM. After another timeout, it escalates to SIGKILL.
363+
364+ Returns True if the server was stopped by any means, False if stopping it
365+ failed (on Windows).
366+ """
367+ from tornado .httpclient import HTTPClient ,HTTPRequest
368+ url = server_info ['url' ]
369+ pid = server_info ['pid' ]
370+ req = HTTPRequest (url + 'api/shutdown' ,method = 'POST' ,body = b'' ,headers = {
371+ 'Authorization' :'token ' + server_info ['token' ]
372+ })
373+ if log :log .debug ("POST request to %sapi/shutdown" ,url )
374+ HTTPClient ().fetch (req )
375+
376+ # Poll to see if it shut down.
377+ for _ in range (timeout * 10 ):
378+ if check_pid (pid ):
379+ if log :log .debug ("Server PID %s is gone" ,pid )
380+ return True
381+ time .sleep (0.1 )
382+
383+ if sys .platform .startswith ('win' ):
384+ return False
385+
386+ if log :log .debug ("SIGTERM to PID %s" ,pid )
387+ os .kill (pid ,signal .SIGTERM )
388+
389+ # Poll to see if it shut down.
390+ for _ in range (timeout * 10 ):
391+ if check_pid (pid ):
392+ if log :log .debug ("Server PID %s is gone" ,pid )
393+ return True
394+ time .sleep (0.1 )
395+
396+ if log :log .debug ("SIGKILL to PID %s" ,pid )
397+ os .kill (pid ,signal .SIGKILL )
398+ return True # SIGKILL cannot be caught
399+
354400
355401class NbserverStopApp (JupyterApp ):
356402version = __version__
@@ -364,14 +410,18 @@ def parse_command_line(self, argv=None):
364410if self .extra_args :
365411self .port = int (self .extra_args [0 ])
366412
413+ def shutdown_server (self ,server ):
414+ return shutdown_server (server ,log = self .log )
415+
367416def start (self ):
368417servers = list (list_running_servers (self .runtime_dir ))
369418if not servers :
370419self .exit ("There are no running servers" )
371420for server in servers :
372421if server ['port' ]== self .port :
373- self .log .debug ("Shutting down notebook server with PID: %i" ,server ['pid' ])
374- os .kill (server ['pid' ],signal .SIGTERM )
422+ print ("Shutting down server on port" ,self .port ,"..." )
423+ if not self .shutdown_server (server ):
424+ sys .exit ("Could not stop server" )
375425return
376426else :
377427print ("There is currently no server running on port {}" .format (self .port ),file = sys .stderr )