PY-18594: Slow commands may freeze manage.py, fixing it

* Give each command 2 seconds to complete
* Kill it and report if it fails
* Give user ability to skip such commands
This commit is contained in:
Ilya.Kazakevich
2016-04-29 01:31:06 +03:00
parent 1994c08bcc
commit 524e119de5
2 changed files with 51 additions and 17 deletions
@@ -4,13 +4,18 @@ This is an entry point of this helper.
It fetches data from Django manage commands delegating calles to _parser package report it via _xml module.
See _xml module and readme.txt for more info.
One may also add list of commands, separated with comma as argument. This is a list of commands to skip.
Could be useful if you know command may lead to freeze
Module can be called directly, but be sure env var DJANGO_SETTINGS_MODULE is set to something like "mysite.settings"
"""
import django
from django_manage_commands_provider._parser import parser
from django_manage_commands_provider import _xml
import sys
__author__ = 'Ilya.Kazakevich'
@@ -19,5 +24,6 @@ __author__ = 'Ilya.Kazakevich'
if hasattr(django, 'setup'):
django.setup()
dumper = _xml.XmlDumper()
parser.report_data(dumper)
commands_to_skip = str(sys.argv[1]).split(",") if len(sys.argv) > 1 else []
parser.report_data(dumper, commands_to_skip)
print(dumper.xml)
@@ -3,33 +3,61 @@
Exports data from optparse or argparse based manage.py commands and reports it to _xml.XmlDumper.
This module encapsulates Django semi-public API knowledge, and not very stable because of it.
"""
from django.core.exceptions import ImproperlyConfigured
from django.core.management import ManagementUtility, get_commands, BaseCommand
from django_manage_commands_provider._parser import _optparse, _argparse
from _jb_utils import VersionAgnosticUtils
import sys
import threading
from _jb_utils import VersionAgnosticUtils
from django.core.management import ManagementUtility, get_commands
from django_manage_commands_provider._parser import _optparse, _argparse
__author__ = 'Ilya.Kazakevich'
def report_data(dumper):
class _Fetcher(threading.Thread):
def __init__(self, utility, command_name):
super(_Fetcher, self).__init__()
self.result = None
self.__utility = utility
self.__command_name = command_name
self.command_lead_to_exception = False
def run(self):
try:
self.result = self.__utility.fetch_command(self.__command_name)
except Exception as e:
sys.stderr.write("Error fetching command '{0}': {1}\n".format(self.__command_name, e))
self.command_lead_to_exception = True
def report_data(dumper, commands_to_skip):
"""
Fetches data from management commands and reports it to dumper.
:type dumper _xml.XmlDumper
:type commands_to_skip list
:param commands_to_skip list of commands to skip
:param dumper: destination to report
"""
utility = ManagementUtility()
for command_name in get_commands().keys():
try:
command = utility.fetch_command(command_name)
except Exception as e:
sys.stderr.write("Error fetching command {0}: {1}\n".format(command_name, e))
continue
assert isinstance(command, BaseCommand)
if command_name in commands_to_skip:
sys.stderr.write("Skipping command '{0}' due to config\n".format(command_name))
continue
fetcher = _Fetcher(utility, command_name)
fetcher.daemon = True
fetcher.start()
fetcher.join(2)
command = fetcher.result
if not command:
if fetcher.command_lead_to_exception:
sys.stderr.write("Command '{0}' skipped\n".format(command_name))
continue
else:
sys.stderr.write(
"Command '{0}' took too long and may freeze everything. Consider adding it to 'skip commands' list\n".format(
command_name))
sys.exit(1)
use_argparse = False
try:
@@ -44,8 +72,8 @@ def report_data(dumper):
continue
dumper.start_command(command_name=command_name,
command_help_text=VersionAgnosticUtils().to_unicode(command.usage("")).replace("%prog", command_name))
module_to_use = _argparse if use_argparse else _optparse # Choose appropriate module: argparse, optparse
command_help_text=VersionAgnosticUtils().to_unicode(command.usage("")).replace("%prog",
command_name))
module_to_use = _argparse if use_argparse else _optparse # Choose appropriate module: argparse, optparse
module_to_use.process_command(dumper, command, parser)
dumper.close_command()