You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ORPA-pyOpenRPA/Resources/WPy64-3720/python-3.7.2.amd64/Lib/site-packages/jupyterlab_server/process.py

290 lines
8.1 KiB

# coding: utf-8
"""JupyterLab Server process handler"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import atexit
import logging
import os
import re
from shutil import which as _which
import signal
import subprocess
import sys
import threading
import time
import weakref
from tornado import gen
try:
import pty
except ImportError:
pty = False
if sys.platform == 'win32':
list2cmdline = subprocess.list2cmdline
else:
def list2cmdline(cmd_list):
import pipes
return ' '.join(map(pipes.quote, cmd_list))
logging.basicConfig(format='%(message)s', level=logging.INFO)
def which(command, env=None):
"""Get the full path to a command.
Parameters
----------
command: str
The command name or path.
env: dict, optional
The environment variables, defaults to `os.environ`.
"""
env = env or os.environ
path = env.get('PATH') or os.defpath
command_with_path = _which(command, path=path)
# Allow nodejs as an alias to node.
if command == 'node' and not command_with_path:
command = 'nodejs'
command_with_path = _which('nodejs', path=path)
if not command_with_path:
if command in ['nodejs', 'node', 'npm']:
msg = 'Please install Node.js and npm before continuing installation. You may be able to install Node.js from your package manager, from conda, or directly from the Node.js website (https://nodejs.org).'
raise ValueError(msg)
raise ValueError('The command was not found or was not ' +
'executable: %s.' % command)
return os.path.abspath(command_with_path)
class Process(object):
"""A wrapper for a child process.
"""
_procs = weakref.WeakSet()
_pool = None
def __init__(self, cmd, logger=None, cwd=None, kill_event=None,
env=None, quiet=False):
"""Start a subprocess that can be run asynchronously.
Parameters
----------
cmd: list
The command to run.
logger: :class:`~logger.Logger`, optional
The logger instance.
cwd: string, optional
The cwd of the process.
env: dict, optional
The environment for the process.
kill_event: :class:`~threading.Event`, optional
An event used to kill the process operation.
quiet: bool, optional
Whether to suppress output.
"""
if not isinstance(cmd, (list, tuple)):
raise ValueError('Command must be given as a list')
if kill_event and kill_event.is_set():
raise ValueError('Process aborted')
self.logger = logger = logger or logging.getLogger('jupyterlab')
self._last_line = ''
if not quiet:
self.logger.info('> ' + list2cmdline(cmd))
self.cmd = cmd
kwargs = {}
if quiet:
kwargs['stdout'] = subprocess.DEVNULL
self.proc = self._create_process(cwd=cwd, env=env, **kwargs)
self._kill_event = kill_event or threading.Event()
Process._procs.add(self)
def terminate(self):
"""Terminate the process and return the exit code.
"""
proc = self.proc
# Kill the process.
if proc.poll() is None:
os.kill(proc.pid, signal.SIGTERM)
# Wait for the process to close.
try:
proc.wait(timeout=2.)
except subprocess.TimeoutExpired:
if os.name == 'nt':
sig = signal.SIGBREAK
else:
sig = signal.SIGKILL
if proc.poll() is None:
os.kill(proc.pid, sig)
finally:
Process._procs.remove(self)
return proc.wait()
def wait(self):
"""Wait for the process to finish.
Returns
-------
The process exit code.
"""
proc = self.proc
kill_event = self._kill_event
while proc.poll() is None:
if kill_event.is_set():
self.terminate()
raise ValueError('Process was aborted')
time.sleep(1.)
return self.terminate()
@gen.coroutine
def wait_async(self):
"""Asynchronously wait for the process to finish.
"""
proc = self.proc
kill_event = self._kill_event
while proc.poll() is None:
if kill_event.is_set():
self.terminate()
raise ValueError('Process was aborted')
yield gen.sleep(1.)
raise gen.Return(self.terminate())
def _create_process(self, **kwargs):
"""Create the process.
"""
cmd = self.cmd
kwargs.setdefault('stderr', subprocess.STDOUT)
cmd[0] = which(cmd[0], kwargs.get('env'))
if os.name == 'nt':
kwargs['shell'] = True
proc = subprocess.Popen(cmd, **kwargs)
return proc
@classmethod
def _cleanup(cls):
"""Clean up the started subprocesses at exit.
"""
for proc in list(cls._procs):
proc.terminate()
class WatchHelper(Process):
"""A process helper for a watch process.
"""
def __init__(self, cmd, startup_regex, logger=None, cwd=None,
kill_event=None, env=None):
"""Initialize the process helper.
Parameters
----------
cmd: list
The command to run.
startup_regex: string
The regex to wait for at startup.
logger: :class:`~logger.Logger`, optional
The logger instance.
cwd: string, optional
The cwd of the process.
env: dict, optional
The environment for the process.
kill_event: callable, optional
A function to call to check if we should abort.
"""
super(WatchHelper, self).__init__(cmd, logger=logger,
cwd=cwd, kill_event=kill_event, env=env)
if not pty:
self._stdout = self.proc.stdout
while 1:
line = self._stdout.readline().decode('utf-8')
if not line:
raise RuntimeError('Process ended improperly')
print(line.rstrip())
if re.match(startup_regex, line):
break
self._read_thread = threading.Thread(target=self._read_incoming)
self._read_thread.setDaemon(True)
self._read_thread.start()
def terminate(self):
"""Terminate the process.
"""
proc = self.proc
if proc.poll() is None:
if os.name != 'nt':
# Kill the process group if we started a new session.
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
else:
os.kill(proc.pid, signal.SIGTERM)
# Wait for the process to close.
try:
proc.wait()
finally:
Process._procs.remove(self)
return proc.returncode
def _read_incoming(self):
"""Run in a thread to read stdout and print"""
fileno = self._stdout.fileno()
while 1:
try:
buf = os.read(fileno, 1024)
except OSError as e:
self.logger.debug('Read incoming error %s', e)
return
if not buf:
return
print(buf.decode('utf-8'), end='')
def _create_process(self, **kwargs):
"""Create the watcher helper process.
"""
kwargs['bufsize'] = 0
if pty:
master, slave = pty.openpty()
kwargs['stderr'] = kwargs['stdout'] = slave
kwargs['start_new_session'] = True
self._stdout = os.fdopen(master, 'rb')
else:
kwargs['stdout'] = subprocess.PIPE
if os.name == 'nt':
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
kwargs['startupinfo'] = startupinfo
kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
kwargs['shell'] = True
return super(WatchHelper, self)._create_process(**kwargs)
# Register the cleanup handler.
atexit.register(Process._cleanup)