A proper fix for async variables loading in Console (PY-27356)

This commit is contained in:
Elizaveta Shashkova
2018-03-13 15:25:41 +03:00
parent 3884b6c2c6
commit b680029e57
6 changed files with 186 additions and 68 deletions
@@ -6,8 +6,7 @@ from _pydev_bundle._pydev_calltip_util import get_description
from _pydev_imps._pydev_saved_modules import thread
from _pydevd_bundle import pydevd_vars
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle.pydevd_constants import IS_JYTHON, dict_iter_items
from _pydevd_bundle.pydevd_utils import to_string
from _pydevd_bundle.pydevd_constants import IS_JYTHON, dict_iter_items, NEXT_VALUE_SEPARATOR
try:
import cStringIO as StringIO #may not always be available @UnusedImport
@@ -484,14 +483,13 @@ class BaseInterpreterInterface:
def getVariable(self, attributes):
xml = StringIO.StringIO()
xml.write("<xml>")
valDict = pydevd_vars.resolve_var(self.get_namespace(), attributes)
if valDict is None:
valDict = {}
keys = valDict.keys()
val_dict = pydevd_vars.resolve_compound_var_object_fields(self.get_namespace(), attributes)
if val_dict is None:
val_dict = {}
keys = val_dict.keys()
for k in keys:
val = valDict[k]
val = val_dict[k]
evaluate_full_value = pydevd_xml.should_evaluate_full_value(val)
xml.write(pydevd_vars.var_to_xml(val, k, evaluate_full_value=evaluate_full_value))
@@ -512,14 +510,34 @@ class BaseInterpreterInterface:
xml.write("</xml>")
return xml.getvalue()
def loadFullValue(self, expressions):
xml = StringIO.StringIO()
xml.write("<xml>")
for expression in expressions:
result = pydevd_vars.eval_in_context(expression, self.get_namespace(), self.get_namespace())
xml.write(pydevd_vars.var_to_xml(result, expression, evaluate_full_value=True))
xml.write("</xml>")
return xml.getvalue()
def loadFullValue(self, seq, scope_attrs):
"""
Evaluate full value for async Console variables in a separate thread and send results to IDE side
:param seq: id of command
:param scope_attrs: a sequence of variables with their attributes separated by NEXT_VALUE_SEPARATOR
(i.e.: obj\tattr1\tattr2NEXT_VALUE_SEPARATORobj2\attr1\tattr2)
:return:
"""
frame_variables = self.get_namespace()
var_objects = []
vars = scope_attrs.split(NEXT_VALUE_SEPARATOR)
for var_attrs in vars:
if '\t' in var_attrs:
name, attrs = var_attrs.split('\t', 1)
else:
name = var_attrs
attrs = None
if name in frame_variables.keys():
var_object = pydevd_vars.resolve_var_object(frame_variables[name], attrs)
var_objects.append((var_object, name))
else:
var_object = pydevd_vars.eval_in_context(name, frame_variables, frame_variables)
var_objects.append((var_object, name))
from _pydevd_bundle.pydevd_comm import GetValueAsyncThreadConsole
t = GetValueAsyncThreadConsole(self.get_server(), seq, var_objects)
t.start()
def changeVariable(self, attr, value):
def do_change_variable():
@@ -1026,7 +1026,7 @@ class InternalGetVariable(InternalThreadCommand):
try:
xml = StringIO.StringIO()
xml.write("<xml>")
_typeName, val_dict = pydevd_vars.resolve_compound_variable(self.thread_id, self.frame_id, self.scope, self.attributes)
_typeName, val_dict = pydevd_vars.resolve_compound_variable_fields(self.thread_id, self.frame_id, self.scope, self.attributes)
if val_dict is None:
val_dict = {}
@@ -1482,11 +1482,10 @@ class InternalLoadFullValue(InternalThreadCommand):
else:
scope, attrs = (variable, None)
name = scope
var_obj = pydevd_vars.getVariable(self.thread_id, self.frame_id, scope, attrs)
var_objects.append((var_obj, name))
t = GetValueAsyncThread(dbg, self.thread_id, self.frame_id, self.sequence, var_objects)
t = GetValueAsyncThreadDebug(dbg, self.sequence, var_objects)
t.start()
except:
exc = get_exception_traceback_str()
@@ -1495,16 +1494,20 @@ class InternalLoadFullValue(InternalThreadCommand):
dbg.writer.add_command(cmd)
class GetValueAsyncThread(PyDBDaemonThread):
def __init__(self, py_db, thread_id, frame_id, seq, var_objects):
class AbstractGetValueAsyncThread(PyDBDaemonThread):
"""
Abstract class for a thread, which evaluates values for async variables
"""
def __init__(self, frame_accessor, seq, var_objects):
PyDBDaemonThread.__init__(self)
self.py_db = py_db
self.thread_id = thread_id
self.frame_id = frame_id
self.frame_accessor = frame_accessor
self.seq = seq
self.var_objs = var_objects
self.cancel_event = threading.Event()
def send_result(self, xml):
raise NotImplementedError()
def _on_run(self):
start = time.time()
xml = StringIO.StringIO()
@@ -1515,9 +1518,29 @@ class GetValueAsyncThread(PyDBDaemonThread):
break
xml.write(pydevd_xml.var_to_xml(var_obj, name, evaluate_full_value=True))
xml.write("</xml>")
cmd = self.py_db.cmd_factory.make_load_full_value_message(self.seq, xml.getvalue())
self.send_result(xml)
xml.close()
self.py_db.writer.add_command(cmd)
class GetValueAsyncThreadDebug(AbstractGetValueAsyncThread):
"""
A thread for evaluation async values, which returns result for debugger
Create message and send it via writer thread
"""
def send_result(self, xml):
if self.frame_accessor is not None:
cmd = self.frame_accessor.cmd_factory.make_load_full_value_message(self.seq, xml.getvalue())
self.frame_accessor.writer.add_command(cmd)
class GetValueAsyncThreadConsole(AbstractGetValueAsyncThread):
"""
A thread for evaluation async values, which returns result for Console
Send result directly to Console's server
"""
def send_result(self, xml):
if self.frame_accessor is not None:
self.frame_accessor.ReturnFullValue(self.seq, xml.getvalue())
#=======================================================================================================================
@@ -229,8 +229,17 @@ def getVariable(thread_id, frame_id, scope, attrs):
return var
def resolve_compound_variable(thread_id, frame_id, scope, attrs):
""" returns the value of the compound variable as a dictionary"""
def resolve_compound_variable_fields(thread_id, frame_id, scope, attrs):
"""
Resolve compound variable in debugger scopes by its name and attributes
:param thread_id: id of the variable's thread
:param frame_id: id of the variable's frame
:param scope: can be BY_ID, EXPRESSION, GLOBAL, LOCAL, FRAME
:param attrs: after reaching the proper scope, we have to get the attributes until we find
the proper location (i.e.: obj\tattr1\tattr2)
:return: a dictionary of variables's fields
"""
var = getVariable(thread_id, frame_id, scope, attrs)
@@ -243,12 +252,36 @@ def resolve_compound_variable(thread_id, frame_id, scope, attrs):
traceback.print_exc()
def resolve_var(var, attrs):
attrList = attrs.split('\t')
def resolve_var_object(var, attrs):
"""
Resolve variable's attribute
for k in attrList:
:param var: an object of variable
:param attrs: a sequence of variable's attributes separated by \t (i.e.: obj\tattr1\tattr2)
:return: a value of resolved variable's attribute
"""
if attrs is not None:
attr_list = attrs.split('\t')
else:
attr_list = []
for k in attr_list:
type, _typeName, resolver = get_type(var)
var = resolver.resolve(var, k)
return var
def resolve_compound_var_object_fields(var, attrs):
"""
Resolve compound variable by its object and attributes
:param var: an object of variable
:param attrs: a sequence of variable's attributes separated by \t (i.e.: obj\tattr1\tattr2)
:return: a dictionary of variables's fields
"""
attr_list = attrs.split('\t')
for k in attr_list:
type, _typeName, resolver = get_type(var)
var = resolver.resolve(var, k)
try: