[PEAK] Re: Trellis: Sensor.listening and iter_listeners()

Sergey Schetinin maluke at gmail.com
Mon Nov 17 16:42:58 EST 2008


from peak.events import trellis

class TestInsonsistentInit(trellis.Component):
    trellis.attrs(v1=False, v2=True)

    @trellis.maintain(optional=True)
    def rule_a(self):
        self.v2 = False

    @trellis.maintain
    def rule_b(self):
       if self.v1 and self.v2:
           self.rule_a
           return True

tc = TestInsonsistentInit()
tc.v1 = True
print tc.v2, tc.rule_b # False, True

What happens here is that rule_a is initialized before rule_b reads
are processed, so there's no dependency of rule_b on v2 and the rule_b
is not rescheduled. The obvious thing to do it seems is to make Value
change processing (and of other settable cells) a little different.
Instead of calling changed(self) in set_value we could schedule(self,
current_listener) and the .run would call changed(self), this would
make sure that by the time changed() is called the reads for the
parent rule are processed and rule_b would be rescheduled again.
However, as it has run already, that would result in rollback which
would also undo rule_a initialization and v2 change, so the retry
would be useless. The change described would also require removing
ctrl.writes / _process_writes which would also make current
circularity detection stop working.

Which surprised me, as there are clearly circularities possible
without writes, i.e.:

class TestOrder(trellis.Component):
    trellis.compute.attrs(
        rule_a = lambda self: [self.rule_b, 'a'],
        rule_b = lambda self: [self.rule_a, 'b']
    )

print TestOrder().rule_a # [[None, 'b'], 'a']


But what if we allowed rules to run on access, not just uninitialized
rules, but any rule: if it is scheduled already it runs, if not, just
gets added to "has_run", same applies to Values as well. So access to
a cell would adjust the reader's layer and the rule would run nested
as necessary. If that schedules a rule that has already run (or is
running currently), that would raise an exception to be caught in
_process and result in retry.

The circularity then is a case when a cell schedules itself, directly
or indirectly. The simplest case is when the .schedule happens with
the rule already in the current stack. The other case being setting a
Value that was already read/run, which is normally resolved by retry.
One possible circular case of this type would be: A reads B and C; B
writes C; C writes B. Non-circular would be A reads B and C; C writes
B.

The former can be detected by the fact that B, being rescheduled was
*initially* scheduled in the current rule stack. Which BTW would
detect circularity in the latter case as well in the case when C
wasn't initialized (as opposed to being scheduled for the same reasons
C was scheduled), which would be the correct thing to do.

There's a more complex case when
A depends on B and C and G
C depends on E and writes B
E depends on G
G is changed, which schedules A and E. So the algorithm above would
give a false positive on circularity.

This probably can be solved as well, but the root of the problem seems
to be that maintain rules can both change values and be depended upon.
What if the maintain rules were replaced by two other cell types: one
would be prohibited to have any listeners and couldn't be optional or
settable and have the rule that shouldn't return anything, the other
would be a non-optional, settable, with read-only rule and capable of
reading its previous value. @make cells would be capable of being
optional and/or settable, with run-once read-only rules (we have the
machinery to initialize components inside read-only rules anyway).


TestInsonsistentInit.rule_b would be invalid then, which it should be.
But TestOrder would still need to be dealt with.



More information about the PEAK mailing list