Dataflow
This commit is contained in:
parent
46b5889ea4
commit
fd36cff912
|
@ -7,6 +7,7 @@ import traceback
|
||||||
from preserves import Embedded, preserve
|
from preserves import Embedded, preserve
|
||||||
|
|
||||||
from .idgen import IdGenerator
|
from .idgen import IdGenerator
|
||||||
|
from .dataflow import Graph, Field
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -57,6 +58,7 @@ class Actor:
|
||||||
self.exit_reason = None # None -> running, True -> terminated OK, exn -> error
|
self.exit_reason = None # None -> running, True -> terminated OK, exn -> error
|
||||||
self.exit_hooks = []
|
self.exit_hooks = []
|
||||||
self._log = None
|
self._log = None
|
||||||
|
self._dataflow_graph = None
|
||||||
Turn.run(Facet(self, self.root, set(self.outbound.keys())), stop_if_inert_after(boot_proc))
|
Turn.run(Facet(self, self.root, set(self.outbound.keys())), stop_if_inert_after(boot_proc))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -82,12 +84,22 @@ class Actor:
|
||||||
self._log = logging.getLogger('syndicate.Actor.%s' % (self.name,))
|
self._log = logging.getLogger('syndicate.Actor.%s' % (self.name,))
|
||||||
return self._log
|
return self._log
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dataflow_graph(self):
|
||||||
|
if self._dataflow_graph is None:
|
||||||
|
self._dataflow_graph = Graph()
|
||||||
|
return self._dataflow_graph
|
||||||
|
|
||||||
def at_exit(self, hook):
|
def at_exit(self, hook):
|
||||||
self.exit_hooks.append(hook)
|
self.exit_hooks.append(hook)
|
||||||
|
|
||||||
def cancel_at_exit(self, hook):
|
def cancel_at_exit(self, hook):
|
||||||
remove_noerror(self.exit_hooks, hook)
|
remove_noerror(self.exit_hooks, hook)
|
||||||
|
|
||||||
|
def _repair_dataflow_graph(self, turn):
|
||||||
|
if self._dataflow_graph is not None:
|
||||||
|
self._dataflow_graph.repair_damage(lambda a: a(turn))
|
||||||
|
|
||||||
def _terminate(self, turn, exit_reason):
|
def _terminate(self, turn, exit_reason):
|
||||||
if self.exit_reason is not None: return
|
if self.exit_reason is not None: return
|
||||||
self.log.debug('Terminating %r with exit_reason %r', self, exit_reason)
|
self.log.debug('Terminating %r with exit_reason %r', self, exit_reason)
|
||||||
|
@ -262,6 +274,7 @@ class Turn:
|
||||||
turn = cls(facet)
|
turn = cls(facet)
|
||||||
try:
|
try:
|
||||||
action(turn)
|
action(turn)
|
||||||
|
facet.actor._repair_dataflow_graph(turn)
|
||||||
except:
|
except:
|
||||||
ei = sys.exc_info()
|
ei = sys.exc_info()
|
||||||
facet.log.error('%s', ''.join(traceback.format_exception(*ei)))
|
facet.log.error('%s', ''.join(traceback.format_exception(*ei)))
|
||||||
|
@ -331,6 +344,24 @@ class Turn:
|
||||||
def crash(self, exn):
|
def crash(self, exn):
|
||||||
self._enqueue(self._facet.actor.root, lambda turn: self._facet.actor._terminate(turn, exn))
|
self._enqueue(self._facet.actor.root, lambda turn: self._facet.actor._terminate(turn, exn))
|
||||||
|
|
||||||
|
def field(self, initial_value=None, name=None):
|
||||||
|
return Field(self._facet.actor.dataflow_graph, initial_value, name)
|
||||||
|
|
||||||
|
# can also be used as a decorator
|
||||||
|
def dataflow(self, a):
|
||||||
|
f = self._facet
|
||||||
|
f.prevent_inert_check()
|
||||||
|
def subject(turn):
|
||||||
|
if not f.alive: return
|
||||||
|
with ActiveFacet(turn, f):
|
||||||
|
a(turn)
|
||||||
|
f.on_stop(lambda turn: f.actor.dataflow_graph.forget_subject(subject))
|
||||||
|
f.actor.dataflow_graph.with_subject(subject, lambda: subject(self))
|
||||||
|
|
||||||
|
def publish_dataflow(self, assertion_function):
|
||||||
|
endpoint = DataflowPublication(assertion_function)
|
||||||
|
self.dataflow(lambda turn: endpoint.update(turn))
|
||||||
|
|
||||||
def publish(self, ref, assertion):
|
def publish(self, ref, assertion):
|
||||||
handle = next(_next_handle)
|
handle = next(_next_handle)
|
||||||
self._publish(ref, assertion, handle)
|
self._publish(ref, assertion, handle)
|
||||||
|
@ -356,7 +387,10 @@ class Turn:
|
||||||
self._retract(e)
|
self._retract(e)
|
||||||
|
|
||||||
def replace(self, ref, handle, assertion):
|
def replace(self, ref, handle, assertion):
|
||||||
new_handle = None if assertion is None else self.publish(ref, assertion)
|
if assertion is None or ref is None:
|
||||||
|
new_handle = None
|
||||||
|
else:
|
||||||
|
new_handle = self.publish(ref, assertion)
|
||||||
self.retract(handle)
|
self.retract(handle)
|
||||||
return new_handle
|
return new_handle
|
||||||
|
|
||||||
|
@ -421,6 +455,20 @@ def stop_if_inert_after(action):
|
||||||
turn._enqueue(turn._facet, check_action)
|
turn._enqueue(turn._facet, check_action)
|
||||||
return wrapped_action
|
return wrapped_action
|
||||||
|
|
||||||
|
class DataflowPublication:
|
||||||
|
def __init__(self, assertion_function):
|
||||||
|
self.assertion_function = assertion_function
|
||||||
|
self.handle = None
|
||||||
|
self.target = None
|
||||||
|
self.assertion = None
|
||||||
|
|
||||||
|
def update(self, turn):
|
||||||
|
(next_target, next_assertion) = self.assertion_function(turn) or (None, None)
|
||||||
|
if next_target != self.target or next_assertion != self.assertion_function:
|
||||||
|
self.target = next_target
|
||||||
|
self.assertion = next_assertion
|
||||||
|
self.handle = turn.replace(self.target, self.handle, self.assertion)
|
||||||
|
|
||||||
class Ref:
|
class Ref:
|
||||||
def __init__(self, facet, entity):
|
def __init__(self, facet, entity):
|
||||||
self.facet = facet
|
self.facet = facet
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
from . import mapset
|
||||||
|
|
||||||
|
class Graph:
|
||||||
|
def __init__(self):
|
||||||
|
self.edges_forward = {}
|
||||||
|
self.edges_reverse = {}
|
||||||
|
self.damaged_nodes = set()
|
||||||
|
self.active_subject = None
|
||||||
|
|
||||||
|
def with_subject(self, subject_id, f):
|
||||||
|
old_subject = self.active_subject
|
||||||
|
self.active_subject = subject_id
|
||||||
|
try:
|
||||||
|
return f()
|
||||||
|
finally:
|
||||||
|
self.active_subject = old_subject
|
||||||
|
|
||||||
|
def record_observation(self, object_id):
|
||||||
|
if self.active_subject is not None:
|
||||||
|
mapset.add(self.edges_forward, object_id, self.active_subject)
|
||||||
|
mapset.add(self.edges_reverse, self.active_subject, object_id)
|
||||||
|
|
||||||
|
def record_damage(self, object_id):
|
||||||
|
self.damaged_nodes.add(object_id)
|
||||||
|
|
||||||
|
def forget_subject(self, subject_id):
|
||||||
|
for oid in self.edges_reverse.pop(subject_id, set()):
|
||||||
|
mapset.discard(self.edges_forward, oid, subject_id)
|
||||||
|
|
||||||
|
def observers_of(self, object_id):
|
||||||
|
return list(self.edges_forward.get(object_id, []))
|
||||||
|
|
||||||
|
def repair_damage(self, repair_fn):
|
||||||
|
repaired_this_round = set()
|
||||||
|
while True:
|
||||||
|
workset = self.damaged_nodes - repaired_this_round
|
||||||
|
self.damaged_nodes = set()
|
||||||
|
|
||||||
|
if not workset:
|
||||||
|
break
|
||||||
|
|
||||||
|
repaired_this_round = repaired_this_round | workset
|
||||||
|
|
||||||
|
for object_id in workset:
|
||||||
|
for subject_id in self.observers_of(object_id):
|
||||||
|
self.forget_subject(subject_id)
|
||||||
|
self.with_subject(subject_id, lambda: repair_fn(subject_id))
|
||||||
|
|
||||||
|
__nextFieldId = 0
|
||||||
|
|
||||||
|
class Field:
|
||||||
|
def __init__(self, graph, initial=None, name=None):
|
||||||
|
global __nextFieldId
|
||||||
|
self.id = name
|
||||||
|
if self.id is None:
|
||||||
|
self.id = str(__nextFieldId)
|
||||||
|
__nextFieldId = __nextFieldId + 1
|
||||||
|
self.graph = graph
|
||||||
|
self._value = initial
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
self.graph.record_observation(self)
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
@value.setter
|
||||||
|
def value(self, new_value):
|
||||||
|
if self._value != new_value:
|
||||||
|
self.graph.record_damage(self)
|
||||||
|
self._value = new_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def update(self):
|
||||||
|
self.graph.record_damage(self)
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def changed(self):
|
||||||
|
self.graph.record_damage(self)
|
|
@ -0,0 +1,14 @@
|
||||||
|
def add(m, k, v):
|
||||||
|
s = m.get(k)
|
||||||
|
if s is None:
|
||||||
|
s = set()
|
||||||
|
m[k] = s
|
||||||
|
s.add(v)
|
||||||
|
|
||||||
|
def discard(m, k, v):
|
||||||
|
s = m.get(k)
|
||||||
|
if s is None:
|
||||||
|
return
|
||||||
|
s.discard(v)
|
||||||
|
if not s:
|
||||||
|
m.pop(k)
|
Loading…
Reference in New Issue