mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-16 06:22:59 +00:00
Add extended docs for LocalProcessSpawner too
This commit is contained in:
@@ -658,48 +658,97 @@ def set_user_setuid(username):
|
|||||||
|
|
||||||
|
|
||||||
class LocalProcessSpawner(Spawner):
|
class LocalProcessSpawner(Spawner):
|
||||||
"""A Spawner that just uses Popen to start local processes as users.
|
"""
|
||||||
|
A Spawner that uses `subprocess.Popen` to start single-user servers as local processes.
|
||||||
|
|
||||||
Requires users to exist on the local system.
|
Requires local UNIX users matching the authenticated users to exist. Does not work on
|
||||||
|
Windows.
|
||||||
|
|
||||||
This is the default spawner for JupyterHub.
|
This is the default spawner for JupyterHub.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
INTERRUPT_TIMEOUT = Integer(10,
|
INTERRUPT_TIMEOUT = Integer(
|
||||||
help="Seconds to wait for process to halt after SIGINT before proceeding to SIGTERM"
|
10,
|
||||||
).tag(config=True)
|
help="""
|
||||||
TERM_TIMEOUT = Integer(5,
|
Seconds to wait for single-user server process to halt after SIGINT.
|
||||||
help="Seconds to wait for process to halt after SIGTERM before proceeding to SIGKILL"
|
|
||||||
).tag(config=True)
|
If the process has not exited cleanly after this many seconds, a SIGTERM is sent.
|
||||||
KILL_TIMEOUT = Integer(5,
|
"""
|
||||||
help="Seconds to wait for process to halt after SIGKILL before giving up"
|
|
||||||
).tag(config=True)
|
).tag(config=True)
|
||||||
|
|
||||||
proc = Instance(Popen, allow_none=True)
|
TERM_TIMEOUT = Integer(
|
||||||
pid = Integer(0)
|
5,
|
||||||
|
help="""
|
||||||
|
Seconds to wait for single-user server process to halt after SIGTERM.
|
||||||
|
|
||||||
|
If the process does not exit cleanly after this many seconds of SIGTERM, a SIGKILL is sent.
|
||||||
|
"""
|
||||||
|
).tag(config=True)
|
||||||
|
|
||||||
|
KILL_TIMEOUT = Integer(5,
|
||||||
|
help="""
|
||||||
|
Seconds to wait for process to halt after SIGKILL before giving up.
|
||||||
|
|
||||||
|
If the process does not exit cleanly after this many seconds of SIGKILL, it becomes a zombie
|
||||||
|
process. The hub process will log a warning and then give up.
|
||||||
|
"""
|
||||||
|
).tag(config=True)
|
||||||
|
|
||||||
|
proc = Instance(
|
||||||
|
Popen,
|
||||||
|
allow_none=True,
|
||||||
|
help="""
|
||||||
|
The process representing the single-user server process spawned for current user.
|
||||||
|
|
||||||
|
Is None if no process has been spawned yet.
|
||||||
|
""")
|
||||||
|
pid = Integer(
|
||||||
|
0,
|
||||||
|
help="""
|
||||||
|
The process id (pid) of the single-user server process spawned for current user.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
def make_preexec_fn(self, name):
|
def make_preexec_fn(self, name):
|
||||||
|
"""
|
||||||
|
Return a function that can be used to set the userid of the spawned process to user with name `name`
|
||||||
|
|
||||||
|
This function can be safely passed to `preexec_fn` of `Popen`
|
||||||
|
"""
|
||||||
return set_user_setuid(name)
|
return set_user_setuid(name)
|
||||||
|
|
||||||
def load_state(self, state):
|
def load_state(self, state):
|
||||||
"""load pid from state"""
|
"""
|
||||||
|
Restore state about spawned single-user server after a hub restart.
|
||||||
|
|
||||||
|
We currently only store/restore the process id.
|
||||||
|
"""
|
||||||
super(LocalProcessSpawner, self).load_state(state)
|
super(LocalProcessSpawner, self).load_state(state)
|
||||||
if 'pid' in state:
|
if 'pid' in state:
|
||||||
self.pid = state['pid']
|
self.pid = state['pid']
|
||||||
|
|
||||||
def get_state(self):
|
def get_state(self):
|
||||||
"""add pid to state"""
|
"""
|
||||||
|
Save state that is needed to restore this spawner instance after a hub restore.
|
||||||
|
|
||||||
|
We currently only store/restore the process id.
|
||||||
|
"""
|
||||||
state = super(LocalProcessSpawner, self).get_state()
|
state = super(LocalProcessSpawner, self).get_state()
|
||||||
if self.pid:
|
if self.pid:
|
||||||
state['pid'] = self.pid
|
state['pid'] = self.pid
|
||||||
return state
|
return state
|
||||||
|
|
||||||
def clear_state(self):
|
def clear_state(self):
|
||||||
"""clear pid state"""
|
"""
|
||||||
|
Clear stored state about this spawner.
|
||||||
|
"""
|
||||||
super(LocalProcessSpawner, self).clear_state()
|
super(LocalProcessSpawner, self).clear_state()
|
||||||
self.pid = 0
|
self.pid = 0
|
||||||
|
|
||||||
def user_env(self, env):
|
def user_env(self, env):
|
||||||
|
"""
|
||||||
|
Augment environment of spawned process with user specific env variables.
|
||||||
|
"""
|
||||||
env['USER'] = self.user.name
|
env['USER'] = self.user.name
|
||||||
home = pwd.getpwnam(self.user.name).pw_dir
|
home = pwd.getpwnam(self.user.name).pw_dir
|
||||||
shell = pwd.getpwnam(self.user.name).pw_shell
|
shell = pwd.getpwnam(self.user.name).pw_shell
|
||||||
@@ -712,14 +761,18 @@ class LocalProcessSpawner(Spawner):
|
|||||||
return env
|
return env
|
||||||
|
|
||||||
def get_env(self):
|
def get_env(self):
|
||||||
"""Add user environment variables"""
|
"""
|
||||||
|
Get the complete set of environment variables to be set in the spawned process.
|
||||||
|
"""
|
||||||
env = super().get_env()
|
env = super().get_env()
|
||||||
env = self.user_env(env)
|
env = self.user_env(env)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Start the process"""
|
"""
|
||||||
|
Start the single-user server.
|
||||||
|
"""
|
||||||
self.port = random_port()
|
self.port = random_port()
|
||||||
cmd = []
|
cmd = []
|
||||||
env = self.get_env()
|
env = self.get_env()
|
||||||
@@ -756,7 +809,12 @@ class LocalProcessSpawner(Spawner):
|
|||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def poll(self):
|
def poll(self):
|
||||||
"""Poll the process"""
|
"""
|
||||||
|
Poll the spawned process to see if it is still running.
|
||||||
|
|
||||||
|
If the process is still running, we return None. If it is not running,
|
||||||
|
we return the exit code of the process if we have access to it, or 0 otherwise.
|
||||||
|
"""
|
||||||
# if we started the process, poll with Popen
|
# if we started the process, poll with Popen
|
||||||
if self.proc is not None:
|
if self.proc is not None:
|
||||||
status = self.proc.poll()
|
status = self.proc.poll()
|
||||||
@@ -767,7 +825,6 @@ class LocalProcessSpawner(Spawner):
|
|||||||
|
|
||||||
# if we resumed from stored state,
|
# if we resumed from stored state,
|
||||||
# we don't have the Popen handle anymore, so rely on self.pid
|
# we don't have the Popen handle anymore, so rely on self.pid
|
||||||
|
|
||||||
if not self.pid:
|
if not self.pid:
|
||||||
# no pid, not running
|
# no pid, not running
|
||||||
self.clear_state()
|
self.clear_state()
|
||||||
@@ -784,7 +841,13 @@ class LocalProcessSpawner(Spawner):
|
|||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def _signal(self, sig):
|
def _signal(self, sig):
|
||||||
"""simple implementation of signal, which we can use when we are using setuid (we are root)"""
|
"""
|
||||||
|
Send given signal to a single-user server's process.
|
||||||
|
|
||||||
|
Returns True if the process still exists, False otherwise.
|
||||||
|
|
||||||
|
The hub process is assumed to be root and hence have enough privilages to do this.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
os.kill(self.pid, sig)
|
os.kill(self.pid, sig)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@@ -796,9 +859,10 @@ class LocalProcessSpawner(Spawner):
|
|||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def stop(self, now=False):
|
def stop(self, now=False):
|
||||||
"""stop the subprocess
|
"""
|
||||||
|
Stop the single-user server process for the current user.
|
||||||
|
|
||||||
if `now`, skip waiting for clean shutdown
|
If `now` is set to True, do not wait for the process to die. Otherwise, it'll wait.
|
||||||
"""
|
"""
|
||||||
if not now:
|
if not now:
|
||||||
status = yield self.poll()
|
status = yield self.poll()
|
||||||
@@ -828,4 +892,3 @@ class LocalProcessSpawner(Spawner):
|
|||||||
if status is None:
|
if status is None:
|
||||||
# it all failed, zombie process
|
# it all failed, zombie process
|
||||||
self.log.warning("Process %i never died", self.pid)
|
self.log.warning("Process %i never died", self.pid)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user