[e-lang] Review requested: Fixing the ordering problem in Data-E
Kevin Reid
kpreid at switchb.org
Mon May 31 09:23:33 PDT 2010
I wrote this three years ago and finally got around to dusting it off
and integrating it... This is a rough draft patch; I would like
feedback on whether this is going about things the right way.
Previous thread: http://www.eros-os.org/pipermail/e-lang/2007-April/012007.html
Particular things I'd like input on:
* What should the FQNs of Really and makeAssembler be?
* Where should the assembler be plugged into the Data-E system, and
should it
be optional?
For easy browsing, a copy of the updoc explanation, also included in
the patch:
------------------------------------------------------------------------
# Copyright 2010 Kevin Reid, under the terms of the MIT X license
# found at http://www.opensource.org/licenses/mit-
license.html ................
XXX TODO: Dig up the mailing list discussion for this topic.
XXX linewraps
The problem is that, depending on the order of serialization, Data-E
unserialization may proceed in an order which fails to unserialize,
because some reference must be near; in particular, when the recipient
of a given call is an object whose creation was deferred.
For example, suppose A and B refer to each other, and A is encountered
first:
/-------\
V |
+---+ +---+
-->| A |-->| B |
+---+ +---+
Furthermore, object C is a facet of A, and B refers to A:
/-------\
V |
+---+ +---+
-->| A |-->| B |
+---+ +---+
*^* |
+---+ |
| C |<----/
+---/
Then the deSubgraphKit algorithm:
* meets A, descends into its components
* meets B, descends into its components
* meets A again, produces a promise for A
* meets C, descends into its components
* meets A again as the receiver for C's uncall
This last step is the problem. We find we have the Data-E code:
def t_A := makeA(makeB(t_A, t_A.makeC()))
which will fail with not-synchronously-callable at makeC. (Note that
this problem can only occur if the *recipient* of an object's uncall
is another object in the subgraph, which participates in cycles,
rather than being an exit or non-cyclic subgraph.)
Another case is when C is not a facet of A, but is the product of
makeC(_ :near); this will also fail since t_A is not near when
makeC(t_A) is done. Note that unlike the previous case, this cannot be
fixed 'statically' (based only on the Data-E graph structure) since it
depends on the requirements for makeC's parameters.
The general solution to both of these problems is to recognize that
the 'order of execution' in unserialization is not part of the
meaningful content of a Data-E graph; therefore, we use the *caller*
hook of deSubgraphKit.makeBuilder to include a module which defers
calls until they may succeed.
The algorithm used is an incremental topological sort. Before each
call is performed, it is examined for dependencies (such as the
recipient being near). If it has any, it is deferred until those
dependencies have also executed.
The component which replaces E.call in unserialization is called an
'assembler', because its job is to perform the steps of assembling the
subgraph from the disorganized parts (calls) in the proper order until
it is complete.
Testing the assembler by itself
-------------------------------
XXX review: The assembler has more general use, as speculated in the
April 18 2007 mail; what should the FQN really be?
? def Really := <elib:slot.Really>
? def makeAssembler :=
<import:org.erights.e.elib.serial.makeAssembler>
# value: <makeAssembler>
? def setDependencyBuilder(portrayal, addDependency) {
> if (portrayal =~ [list :Really[List], =="asSet", []]) {
> for x in list { addDependency(x) }
> }
> }
The parameters to makeAssembler are a callback which may inspect calls
and add additional dependencies on specific references (which is not
invoked until the call constructing the recipient has been performed),
and the caller which should be used for the underlying real calls
(that this is wrapping).
? def a := makeAssembler(setDependencyBuilder, E)
# value: <assembler>
? def [y, s, l, x] := [a(1).next(),
> a(l).asSet(),
> a(__makeList).run(x, y),
> a(0).next()]
> a.finish()
> [y,s,l,x]
# value: [2, [1, 2].asSet(), [1, 2], 1]
XXX test the call() based interface.
? def l := a(["hello world", a(l).readOnly()]).diverge()
> a.finish()
> l
# value: ["hello world", <***CYCLE***>.readOnly()].diverge()
Data-E ordering
---------------
To test Data-E, we use the problem case described at the beginning of
this document.
? def b
> def a := ["this is A", b]
> bind b := ["this is B", a, a.diverge()]
> a
# value: ["this is A", ["this is B", <***CYCLE***>, ["this is A",
<***CYCLE***>].diverge()]]
? def surgeon := <elib:serial.makeSurgeon>.withSrcKit(""); null
? print(def ser := surgeon.serialize(a))
# stdout: def t__0 := ["this is A", ["this is B", t__0,
t__0.diverge()]]
? surgeon.unserialize(ser)
# value: ["this is A", ["this is B", <***CYCLE***>, ["this is A",
<***CYCLE***>].diverge()]]
XXX possibly add the default feature of using rangeSubsetOf to check
if the guards of the parameters in the the recipient's alleged type
are known to reject promises.
------------------------------------------------------------------------
-------------- next part --------------
A non-text attachment was scrubbed...
Name: assembler.patch
Type: application/octet-stream
Size: 13549 bytes
Desc: not available
Url : http://www.eros-os.org/pipermail/e-lang/attachments/20100531/797ed268/attachment.obj
-------------- next part --------------
--
Kevin Reid <http://switchb.org/kpreid/>
More information about the e-lang
mailing list