ObjectReferenceProperty is hard. Don't use it.

looper
Ben Niemann 7 years ago
parent f96737cf7f
commit 7939509cf4

@ -9,7 +9,9 @@ class Error(Exception):
pass
class ObjectNotAttachedError(Error):
pass
def __init__(self, obj):
super().__init__(str(obj))
class NotListMemberError(Error):
pass
@ -307,22 +309,15 @@ class ObjectReferenceProperty(PropertyBase):
current = self.__get__(instance, instance.__class__)
logger.info("old: %s (%s)", current, id(current))
logger.info("new: %s (%s)", value, id(value))
if (current is not None
and not isinstance(current, (tuple, DeferredReference))):
assert current.ref_count > 0
logger.info("-refcount(%s) = %d", current.id, current.ref_count)
current.ref_count -= 1
logger.info("-refcount(%s) = %d", current.id, current.ref_count)
super().__set__(instance, value)
if value is not None and not isinstance(value, DeferredReference):
logger.info("+refcount(%s) = %d", value.id, value.ref_count)
value.ref_count += 1
logger.info("+refcount(%s) = %d", value.id, value.ref_count)
class ObjectMeta(type):
@ -365,7 +360,7 @@ class ObjectBase(object, metaclass=ObjectMeta):
if self.parent is None:
if self._is_root:
return self
raise ObjectNotAttachedError
raise ObjectNotAttachedError(self)
return self.parent.root
@property
@ -399,13 +394,13 @@ class ObjectBase(object, metaclass=ObjectMeta):
def set_index(self, index):
if self.__parent_container is None:
raise ObjectNotAttachedError(self.id)
raise ObjectNotAttachedError(self)
self.__index = index
@property
def index(self):
if self.__parent_container is None:
raise ObjectNotAttachedError(self.id)
raise ObjectNotAttachedError(self)
assert self.__index is not None
return self.__index

@ -213,8 +213,8 @@ class ProcessManager(object):
# handler to pipe all log messages back to the manager
# process.
root_logger = logging.getLogger()
for handler in root_logger.handlers:
root_logger.removeHandler(handler)
while root_logger.handlers:
root_logger.removeHandler(root_logger.handlers[0])
root_logger.addHandler(ChildLogHandler(logger_out))
if isinstance(cls, str):
@ -393,7 +393,9 @@ class ProcessImpl(object):
logger.info("Entering run method.")
return await self.run(*args, **kwargs)
except Exception as exc:
logger.error("Exception encountered: %s", exc)
logger.error(
"Unhandled exception in process %s:\n%s",
self.name, traceback.format_exc())
raise
finally:
await self.cleanup()

@ -159,8 +159,25 @@ class Command(state.RootMixin, state.StateBase):
return
if isinstance(change, core.PropertyValueChange):
old_slot_id = self.log.add_slot(change.old_value)
new_slot_id = self.log.add_slot(change.new_value)
if isinstance(
obj.get_property(change.prop_name),
core.ObjectReferenceProperty):
old_value = (
change.old_value.id
if change.old_value is not None
else None)
new_value = (
change.old_value.id
if change.old_value is not None
else None)
else:
old_value = change.old_value
new_value = change.new_value
old_slot_id = self.log.add_slot(old_value)
new_slot_id = self.log.add_slot(new_value)
self.log.add_operation(
'SET_PROPERTY', obj.id, change.prop_name,
old_slot_id, new_slot_id)

@ -43,7 +43,11 @@ class Measure(core.ObjectBase):
class MeasureReference(core.ObjectBase):
measure = core.ObjectReferenceProperty(allow_none=True)
measure_id = core.Property(str)
@property
def measure(self):
return self.root.get_object(self.measure_id)
@property
def track(self):
@ -160,7 +164,6 @@ class BeatMeasure(Measure):
class BeatTrack(Track):
instrument = core.ObjectProperty(cls=Instrument)
pitch = core.Property(pitch.Pitch)
measure_sequence = core.ObjectReferenceProperty(BeatMeasure)
class SheetPropertyMeasure(Measure):

@ -155,7 +155,6 @@ class BasePipelineGraphNode(model.BasePipelineGraphNode, state.StateBase):
(p.name, p.value) for p in self.port_property_values
if p.port_name == port.name)
logger.info("%s", port_property_values)
if port_property_values:
self.sheet.handle_pipeline_mutation(
mutations.SetPortProperty(

@ -67,8 +67,7 @@ class ProjectClientMixin(object):
def __set_project(self, root_id):
assert self.project is None
self.project = self._object_map[root_id]
self.project.is_root = True
self.project.node_db = self._node_db
self.project.init(self._node_db, self._object_map)
async def setup(self):
await super().setup()
@ -144,50 +143,48 @@ class ProjectClientMixin(object):
def handle_project_mutation(self, mutation):
logger.info("Mutation received: %s" % mutation)
try:
if isinstance(mutation, mutations.SetProperties):
obj = self._object_map[mutation.id]
assert not isinstance(obj, core.DeferredReference)
self.apply_properties(obj, mutation.properties)
elif isinstance(mutation, mutations.AddObject):
cls = self.cls_map[mutation.cls]
obj = cls(mutation.id)
self.apply_properties(obj, mutation.properties)
# TODO: We should assert that mutation.id is either not
# in _object_map or points at a DeferredReference.
# But we don't delete entries from _object_map when
# objects are removed, so we leave zombies behind, which
# we just override here.
if (mutation.id in self._object_map
and isinstance(
self._object_map[mutation.id],
core.DeferredReference)):
self._object_map[mutation.id].dereference(obj)
self._object_map[mutation.id] = obj
elif isinstance(mutation, mutations.ListInsert):
obj = self._object_map[mutation.id]
lst = getattr(obj, mutation.prop_name)
if mutation.value_type == 'obj':
child = self._object_map[mutation.value]
lst.insert(mutation.index, child)
elif mutation.value_type == 'scalar':
lst.insert(mutation.index, mutation.value)
else:
raise ValueError(
"Value type %s not supported."
% mutation.value_type)
elif isinstance(mutation, mutations.ListDelete):
obj = self._object_map[mutation.id]
lst = getattr(obj, mutation.prop_name)
del lst[mutation.index]
if isinstance(mutation, mutations.SetProperties):
obj = self._object_map[mutation.id]
assert not isinstance(obj, core.DeferredReference)
self.apply_properties(obj, mutation.properties)
elif isinstance(mutation, mutations.AddObject):
cls = self.cls_map[mutation.cls]
obj = cls(mutation.id)
self.apply_properties(obj, mutation.properties)
# TODO: We should assert that mutation.id is either not
# in _object_map or points at a DeferredReference.
# But we don't delete entries from _object_map when
# objects are removed, so we leave zombies behind, which
# we just override here.
if (mutation.id in self._object_map
and isinstance(
self._object_map[mutation.id],
core.DeferredReference)):
self._object_map[mutation.id].dereference(obj)
self._object_map[mutation.id] = obj
elif isinstance(mutation, mutations.ListInsert):
obj = self._object_map[mutation.id]
lst = getattr(obj, mutation.prop_name)
if mutation.value_type == 'obj':
child = self._object_map[mutation.value]
lst.insert(mutation.index, child)
elif mutation.value_type == 'scalar':
lst.insert(mutation.index, mutation.value)
else:
raise ValueError("Unknown mutation %s received." % mutation)
except Exception as exc:
logger.exception("Handling of mutation %s failed:", mutation)
raise ValueError(
"Value type %s not supported."
% mutation.value_type)
elif isinstance(mutation, mutations.ListDelete):
obj = self._object_map[mutation.id]
lst = getattr(obj, mutation.prop_name)
del lst[mutation.index]
else:
raise ValueError("Unknown mutation %s received." % mutation)
def handle_project_closed(self):
logger.info("Project closed received.")

@ -6,6 +6,7 @@ import logging
import pprint
import threading
import time
import traceback
import uuid
from noisicaa import core
@ -46,8 +47,9 @@ class Session(object):
try:
await self.callback_stub.call('PROJECT_MUTATION', mutation)
except Exception:
logger.exception(
"PROJECT_MUTATION %s failed with exception", mutation)
logger.error(
"PROJECT_MUTATION %s failed with exception:\n%s",
mutation, traceback.format_exc())
class AudioProcClientImpl(object):

@ -211,8 +211,10 @@ class PasteMeasuresAsLink(commands.Command):
for target, src in zip(
target_measures, itertools.cycle(src_measures)):
old = target.measure
old.ref_count -= 1
target.measure = src
target.measure_id = src.id
target.measure.ref_count += 1
if old.ref_count == 0:
logger.info("GC measure %s", old.id)

@ -24,9 +24,9 @@ class StateBase(model_base.ObjectBase):
else:
self.id = uuid.uuid4().hex
logger.info("<%s id=%s> created (%s) by",
type(self).__name__, self.id, id(self))
logger.info("%s", ''.join(traceback.format_list(traceback.extract_stack())))
# logger.info("<%s id=%s> created (%s) by",
# type(self).__name__, self.id, id(self))
# logger.info("%s", ''.join(traceback.format_list(traceback.extract_stack())))
def __eq__(self, other):
if self.__class__ is not other.__class__:

@ -71,11 +71,11 @@ class Measure(model.Measure, state.StateBase):
class MeasureReference(model.MeasureReference, state.StateBase):
def __init__(self, measure=None, state=None):
def __init__(self, measure_id=None, state=None):
super().__init__(state)
if state is None:
self.measure = measure
self.measure_id = measure_id
state.StateBase.register_class(MeasureReference)
@ -112,22 +112,33 @@ class Track(model.Track, state.StateBase):
idx = len(self.measure_list)
if idx == 0 and len(self.measure_list) > 0:
ref = self.measure_list[0].measure
ref_id = self.measure_list[0].measure_id
elif idx > 0:
ref = self.measure_list[idx-1].measure
ref_id = self.measure_list[idx-1].measure_id
else:
ref_id = None
if ref_id is not None:
for ref in self.measure_heap:
if ref.id == ref_id:
break
else:
raise ValueError(ref_id)
else:
ref = None
measure = self.create_empty_measure(ref)
self.measure_heap.append(measure)
self.measure_list.insert(idx, MeasureReference(measure=measure))
self.measure_list.insert(idx, MeasureReference(measure_id=measure.id))
measure.ref_count += 1
def remove_measure(self, idx):
measure = self.measure_list[idx].measure
assert measure.ref_count > 0
self.measure_list[idx].measure = None
del self.measure_list[idx]
measure.ref_count -= 1
if measure.ref_count == 0:
logger.info("GC measure %s", measure.id)
del self.measure_heap[measure.index]

@ -46,18 +46,7 @@ class ExceptHook(object):
msg = ''.join(traceback.format_exception(exc_type, exc_value, tb))
logger.error("Uncaught exception:\n%s", msg)
self._show_crash_dialog(msg)
os._exit(EXIT_EXCEPTION)
def _show_crash_dialog(self, msg):
errorbox = QMessageBox()
errorbox.setWindowTitle("noisicaä crashed")
errorbox.setText("Uncaught exception")
errorbox.setInformativeText(msg)
errorbox.setIcon(QMessageBox.Critical)
errorbox.addButton("Exit", QMessageBox.AcceptRole)
errorbox.exec_()
self.app.crashWithMessage("Uncaught exception", msg)
class AudioProcClientImpl(object):
@ -348,3 +337,21 @@ class EditorApp(BaseEditorApp):
if self.pipeline_perf_monitor is not None:
self.pipeline_perf_monitor.addPerfData(
status['perf_data'])
def crashWithMessage(self, title, msg):
logger.error('%s: %s', title, msg)
try:
errorbox = QMessageBox()
errorbox.setWindowTitle("noisicaä crashed")
errorbox.setText(title)
errorbox.setInformativeText(msg)
errorbox.setIcon(QMessageBox.Critical)
errorbox.addButton("Exit", QMessageBox.AcceptRole)
errorbox.exec_()
except:
logger.error(
"Failed to show crash dialog: %s", traceback.format_exc())
os._exit(EXIT_EXCEPTION)

@ -59,11 +59,27 @@ class Metadata(model.Metadata, project_client.ObjectProxy): pass
class Project(model.Project, project_client.ObjectProxy):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._is_root = True
self.node_db = None
self.__node_db = None
self.__obj_map = None
def init(self, node_db, obj_map):
self.__node_db = node_db
self.__obj_map = obj_map
def get_node_description(self, uri):
return self.node_db.get_node_description(uri)
return self.__node_db.get_node_description(uri)
def get_object(self, obj_id):
assert self.__obj_map is not None
return self.__obj_map[obj_id]
def add_object(self, obj):
pass
def remove_object(self, obj):
pass
cls_map = {

@ -73,7 +73,7 @@ class MeasureItemImpl(QtWidgets.QGraphicsItem):
if self._measure_reference is not None:
self._measure = measure_reference.measure
self._measure_listener = self._measure_reference.listeners.add(
'measure', self.measureChanged)
'measure_id', self.measureChanged)
else:
self._measure = None
self._measure_listener = None
@ -113,10 +113,7 @@ class MeasureItemImpl(QtWidgets.QGraphicsItem):
self._measure_listener.remove()
def measureChanged(self, old_value, new_value):
for m in self._measure_reference.track.measure_heap:
print(m.index, m)
print(self._measure, old_value, new_value)
self._measure = new_value
self._measure = self._measure_reference.measure
self.recomputeLayout()
def recomputeLayout(self):

@ -1,6 +1,9 @@
#!/usr/bin/python3
import functools
import logging
logger = logging.getLogger(__name__)
UNSET = object()
@ -45,7 +48,9 @@ class CommonMixin(object):
def __call_async_cb(self, task, callback):
if task.exception() is not None:
self.__app.crashWithMessage("Exception in callback", str(task.exception()))
raise task.exception()
if callback is not None:
callback(task.result())
@ -77,14 +82,6 @@ class ProjectMixin(CommonMixin):
return self.__project_connection.client
def send_command_async(self, target_id, cmd, callback=None, **kwargs):
task = self.event_loop.create_task(
self.project_client.send_command(target_id, cmd, **kwargs))
task.add_done_callback(
functools.partial(
self.__send_command_async_cb, callback=callback))
def __send_command_async_cb(self, task, callback):
if task.exception() is not None:
raise task.exception()
if callback is not None:
callback(task.result())
self.call_async(
self.project_client.send_command(target_id, cmd, **kwargs),
callback=callback)

Loading…
Cancel
Save