Run script in a new thread with timeout

* Also fixes script output not sent to logger
This commit is contained in:
JonnyWong16 2016-09-29 23:31:15 -07:00
parent 0b10e68c60
commit 93a1d9c164
2 changed files with 54 additions and 24 deletions

View file

@ -509,6 +509,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_CONCURRENT_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_NEWDEVICE_SCRIPT': (unicode, 'Scripts', ''), 'SCRIPTS_ON_NEWDEVICE_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_TIMEOUT': (int, 'Scripts', 30),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''), 'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0), 'TELEGRAM_ENABLED': (int, 'Telegram', 0),
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''), 'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),

View file

@ -26,6 +26,7 @@ import requests
import shlex import shlex
import smtplib import smtplib
import subprocess import subprocess
import threading
import time import time
import urllib import urllib
from urllib import urlencode from urllib import urlencode
@ -2116,6 +2117,7 @@ class Scripts(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.ps1', '.py', '.pyw', '.rb', '.sh') self.script_exts = ('.bat', '.cmd', '.exe', '.php', '.pl', '.ps1', '.py', '.pyw', '.rb', '.sh')
self.script_folder = plexpy.CONFIG.SCRIPTS_FOLDER self.script_folder = plexpy.CONFIG.SCRIPTS_FOLDER
self.script_timeout = plexpy.CONFIG.SCRIPTS_TIMEOUT
self.scripts = {'play': plexpy.CONFIG.SCRIPTS_ON_PLAY_SCRIPT, self.scripts = {'play': plexpy.CONFIG.SCRIPTS_ON_PLAY_SCRIPT,
'stop': plexpy.CONFIG.SCRIPTS_ON_STOP_SCRIPT, 'stop': plexpy.CONFIG.SCRIPTS_ON_STOP_SCRIPT,
'pause': plexpy.CONFIG.SCRIPTS_ON_PAUSE_SCRIPT, 'pause': plexpy.CONFIG.SCRIPTS_ON_PAUSE_SCRIPT,
@ -2160,6 +2162,48 @@ class Scripts(object):
return scripts return scripts
def run_script(self, script):
output = error = ''
try:
def kill_script(process):
logger.warn(u"PlexPy Notifiers :: Script exceeded timeout limit of %d seconds. "
"Script killed." % self.script_timeout)
process.kill()
self.script_killed = True
process = subprocess.Popen(script,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=plexpy.CONFIG.SCRIPTS_FOLDER)
timer = threading.Timer(self.script_timeout, kill_script, (process,))
self.script_killed = False
try:
timer.start()
output, error = process.communicate()
status = process.returncode
finally:
timer.cancel()
except OSError as e:
logger.error(u"PlexPy Notifiers :: Failed to run script: %s" % e)
return False
if error:
err = '\n '.join([l for l in error.splitlines()])
logger.error(u"PlexPy Notifiers :: Script error: \n %s" % err)
return False
if output:
out = '\n '.join([l for l in output.splitlines()])
logger.debug(u"PlexPy Notifiers :: Script returned: \n %s" % out)
if not self.script_killed:
logger.info(u"PlexPy Notifiers :: Script notification sent.")
return True
def notify(self, subject='', message='', notify_action='', script_args=None, *args, **kwargs): def notify(self, subject='', message='', notify_action='', script_args=None, *args, **kwargs):
""" """
Args: Args:
@ -2231,31 +2275,10 @@ class Scripts(object):
script.extend(script_args) script.extend(script_args)
logger.debug(u"PlexPy Notifiers :: Full script is: %s" % script) logger.debug(u"PlexPy Notifiers :: Full script is: %s" % script)
logger.debug(u"PlexPy Notifiers :: Executing script in a new thread.")
thread = threading.Thread(target=self.run_script, args=(script,)).start()
try: return True
p = subprocess.Popen(script, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=plexpy.CONFIG.SCRIPTS_FOLDER)
out, error = p.communicate()
status = p.returncode
if out and status:
out = out.strip()
logger.debug(u"PlexPy Notifiers :: Script returned: %s" % out)
if error:
error = error.strip()
logger.error(u"PlexPy Notifiers :: Script error: %s" % error)
return False
else:
logger.info(u"PlexPy Notifiers :: Script notification sent.")
return True
except OSError as e:
logger.error(u"PlexPy Notifiers :: Failed to run script: %s" % e)
return False
def return_config_options(self): def return_config_options(self):
config_option = [{'label': 'Supported File Types', config_option = [{'label': 'Supported File Types',
@ -2365,6 +2388,12 @@ class Scripts(object):
'description': 'Choose the script for user new device.', 'description': 'Choose the script for user new device.',
'input_type': 'select', 'input_type': 'select',
'select_options': self.list_scripts() 'select_options': self.list_scripts()
},
{'label': 'Script Timeout',
'value': self.script_timeout,
'name': 'scripts_timeout',
'description': 'The number of seconds to wait before killing the script.',
'input_type': 'number'
} }
] ]