I made it so that when that queue is flushed a special "disabled"
transaction is active, which means that if the effect tries to read or
write any transactional data (read or modify cells basically) it will
throw an error. I did that because @perform rules could have read
transient values ("discrete changes" in Trellis terminology) and
effects, running outside the txn could only see those values if they
were passed to them as an argument, stored in a queue. That is, the
effect would not get that value directly from in-txn data, it's up to
@maintain that queued it to read that data and pass it to txn.effect()
or @effect_method. So I thought that it would be a good idea to make
that limitation more explicit and disable txn-reads during effect
phase.
Now there's a different issue with starting new transactions from the
effect phase -- imagine a transaction queued something to be written
to a socket, the effect tries to do that and discovers that the socket
got disconnected -- it has to report it, to set some flag which would
write to a cell which is prohibited. So to allow that to work I added
yet another queue, only available from disabled transactions --
txn.post_effect(). Effects can add something to it and when all the
effects are done, if that queue is not empty it would start a new
transaction and call the post-effect callbacks in it setting all those
"disconnected" flags and whatnot.
Now, while that works fine, I figured that I could actually make those
queues the same -- just allow the effects to read / write txn but make
that happen in the new txn, that way effects still only get applied
after initial txn has succeeded with no possibility of retry yet they
can also report their results in a natural way. It is very similar to
how @tasks and top-level @atomic methods work -- they mix effects and
txn changes with no capability of retries and thanks to the trick I
explained earlier (
http://article.gmane.org/gmane.comp.python.peak/2453 ) they still can
happen concurrently. So I'm going to make effects happen in a regular
transaction, but not the one they got queued up from. This also meshes
nicely with newly introduced limitations on @track rule that may not
change their value more than once per txn (they still can run twice
due to dependency on transient values / computes). If there's a
cascade of effects to apply (like creating a window hierarchy) all
that can happen in a series of transactions via the txn.effect and all
the rules would still behave nicely inside those txns with txn-al
state being consistent at all time even if some effect fails at some
point.
I don't know if anyone used or uses Trellis / Reaction for something
that suffers from the hackish way Trellis handles effects, but trust
me, this is a big step forward.
--
Best Regards,
Sergey Schetinin
http://s3bk.com/ -- S3 Backup
http://word-to-html.com/ -- Word to HTML Converter
No, that was the intermediary solution. Now the queued effect() calls
are collected and after the transaction is completely finished (and
succeeded) a new transaction is immediately created where those queued
calls are executed. Those calls can modify state and reaction to those
modifications could create more effects that will be queued for yet
another cycle, it repeats until there were no effect calls in a
transaction or until a transaction fails, in the latter case only the
last transaction's changes are discarded.
> I has problems with implicit modification inside Trellis task and it
> was not easy to figure out.
I'm not sure I understand what you mean here.
> 2. effect can be called only from maintain or some other rule too?
> From my perspective @perform can be natively translated to @track +
> effect (not need maintain write access).
It can be called from non-readonly rules: from @maintain, from
top-level @atomic functions and from @task.
I don't see why you want to avoid using @maintain for effects, but
queuing effects from @track (given the fact that effects can change
the state, even if in the next txn) would break some properties. I
suppose we could have two kinds of effects, one capable of working
with state and another that runs in "disabled txn", the latter one
could probably be allowed to be queued from readonly rules, but I'm
not sure there's a need for that.
from mext.reaction import *
class C(Component):
v = attr(0)
@maintain
def x(self):
self.v += 1
print C().v
Instead, stuff like that would be accomplished via effects.
A perform rule might access a @compute or @make which would initialize
that cell at that point (inside @perform) that cell might have a rule
that would create a component with a @maintain or a @task -- that
means that while @perform may not change state itself it still often
results in state change.
> This actions cannot generate exceptions - perform is not undoable.
Not really true, but also note that w/ concurrency transactions might
need to be retried even if they had no errors, or wrong order of
execution, simply because their reads were invalidated by another txn.
This means that no non-undoable operations may happen inside txn.
> To update UI from mext.reaction I was need to make perform and put
> cell values to UI widgets.
> Of course this is trivial case not care about problems in action
> executing like socket errors discribed by you.
>
> maintain has readwrite txn and this is a bit surplus than required.
> I want to have as restricted execution context as need for executing
> rule.
> For me `disabled txn` is good solution for this case - just make pure
> non-optional listener and make synchronization inside it.
As I said, we simply cannot allow effects *inside listeners*, the
safest way to do it would be to apply all effects with txn completely
disabled, but instead we can offer a compromise where effects can
happen inside txn but only right at its beginning -- via @task or via
txn.effect, allowing effects after that point just would break any
chance of concurrency. Still, having "pure" effects running with
disabled txn would be nice, so maybe I should add that back too.
For UI updates, I would recommend to have @maintain rules in a
baseclass that would observe overridable cells telling them what
should be put into UI, something like this:
http://code.google.com/p/trellis-fork/source/browse/trunk/demos/wx_demo5-observe.py
I imagine we could have a listener that would only read the txn, have
no value, but could create effects, and name it something like
@maintain.effect. I guess I can add that, cause it does make sense.