-
-
Notifications
You must be signed in to change notification settings - Fork 320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add functions to the language #704
Conversation
Sweeet =D |
16885bc
to
db9aaac
Compare
FYI: I updated merged graph branch. You may wish to cherry-pick stuff into here. I can fix the merge conflict if necessary. https://github.com/purpleidea/mgmt/tree/feat/merged-graph Of note: I added caching of the function pointer returned from Func(). This way, if it's called more than once, the same func will be returned. Is this a problem for us? Of course if we Copy() the Func Expr, then the caching doesn't get copied... If this approach is satisfactory, then this allows us to look at the functions from the function graph, and determine which ones maps to which by calling Func again everywhere. This isn't as effective performance wise as adding a new map to our returned function signature, but at least for now, I think this is easier to plumb in, and once we're sure we don't need to change the signature any more, we can revisit in the future. |
856c0ba
to
a751e1d
Compare
The graph in which the two const nodes are not attached to anything looks
correct to me. What is supposed to happen is that the printf node sends a
FuncValue to the call node, which then creates a sub-graph which looks like
the V-shaped graph from master, plus a ChannelBasedSink which sends the
output of printf back to the call node via a channel.
Is it possible that the test harness stop with the error "stream errored:
func ***@***.***` stopped before it was loaded" before the call
node is given a chance to generate it's sub-graph?
Another thing I notice about this example is that there are two very
different printf nodes in this example, the one with in-degree zero which
produces a FuncValue, and the one with in-degree 2 in the V-shaped
sub-graph. So another possible explanation for the bug might be that we're
using the wrong one in one of the two graphs.
…On Fri, May 12, 2023, 18:25 James ***@***.***> wrote:
FYI: debugging test 42:
I set:
runGraphviz || true
to enable the function graph output.
Then I ran:
time go test github.com/purpleidea/mgmt/lang/ -run TestAstFunc2/test_#42
Which gets us the error:
interpret_test.go:1605: test #42: stream errored: func ***@***.***` stopped before it was loaded
Which also produced /tmp/graphviz.dot.png
[image: graphviz dot]
<https://user-images.githubusercontent.com/135091/238090580-ece5d436-53b8-4b70-b55d-41c92ec2b0de.png>
Not the two constants are not attached to the function. So it never gets
those inputs. Which is why we see the error.
The correct output looks roughly like this (from git master)
[image: graphviz dot]
<https://user-images.githubusercontent.com/135091/238090954-d45700fb-3838-4adf-a623-6864544392e5.png>
—
Reply to this email directly, view it on GitHub
<#704 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAL62D6LFKMFDZA2VTT2CLXF22GBANCNFSM6AAAAAAVUSX4KA>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Sorry, I forgot to include the mcl code:
Good point, I should have covered that sorry. Unfortunately, I don't think there are. The reason the nodes look slightly different is that in the earlier version, we're running a .String() to get the label on the Expr (which is what is in the function graph) where as in the newer version, we've had to simplify the label tag because we don't have as much context as the actual node is based on the function implementation (no more expr!). But the actual Stream() implementations in printf doing the work are identical.
I don't believe so. The reason we get this error is that the singular printf node which is running in the function engine receives no input (IOW, no incoming edges) so the engine closes the entire input stream. The printf function, seeing it is not getting any more inputs, then shuts down the output stream. Finally, the function engine notices (thanks engine!) that an output stream has shutdown without ever once producing a value, and assumes (rightly so) that there is a bug. All nodes are expected to either produce at least one value or error. The current printf implementation indeed is supposed to receive one or more values (format string and optional args) in the function engine. If this is now supposed to go through a channelsink or channelsource, then the fundamental API of every static arg list function (basically everything except map/reduce/etc) would need to change. I don't think that's what we were trying to accomplish. I do believe we probably just forgot two edges in this case. |
Then that's the bug. The printf node with in-degree zero (let's call it printf-zero) should not be doing the same work as the printf node which in-degree two (let's call it printf-two). printf-zero should produce a FuncValue which creates a sub-graph containing printf-two, while printf-two should produce a string based on its arguments.
That makes sense. The bug is that we give zero incoming edges to printf-two, which behaves like you explained when it has no incoming edges are missing. We should instead give zero incoming edges to printf-zero, which does produce a value when it has no incoming edges.
We do want to change the semantics of every single MCL function, that's the premise of this PR: previously, every single MCL function was converting values to values, that doesn't work, so we now want every single MCL function to be generating sub-graphs instead. Luckily, that doesn't mean that we have to change hundreds of function implementations throughout the codebase. We still need those Func implementations, we just need to make sure that the node which feeds the call node is not one of those Func implementations, and is instead a wrapper which returns a FuncValue which creates a sub-graph which contains one of those Func implementations. |
I have pushed an attempt at fixing the problem, but it doesn't work. The behaviour for our simplified test 42 is now non-deterministic :( Sometimes it fails with Sometimes it fails with that message and then hangs. Sometimes if fails with |
It's possible there is a race condition in either my engine or the subgraph channel stuff. Function graph generation should be deterministic though. Adding this line in TestAstFunc2:
Then running the test Will produce /tmp/graphviz.dot.png if graphviz is installed, and can at least confirm the correct function graph shapes. If we're satisfied with that shape, we can look into seeing where in the engine and/or call funcs the races are. |
Btw:
Indeed, since params come in as edges, there's no obvious ordering-- the only solution is to have them be named.
What do you mean by nearby? |
The T field of the enclosing FuncValue definition. |
Where? TestAstFunc2 is a folder, not a file. |
ah, it's also a function in |
(I see the same graph regardless of which of the non-deterministic failures occurs) That looks correct. The |
Oh! I bet the |
If you instead run:
Does it still display that non-determinism? |
I decided I wanted to double check I hadn't made a mistake somewhere in the deps for all of this (engine, txn, etc) so I wrote a test. Indeed I found a small bug (woops) and while I don't think it necessarily caused this issue, I figured I'd mention it in case. The bug and associated test is in the txn code. I rebased it and feat/merge-stuff contains the fix. |
I tried this branch of yours (with the txn fix, and just one arg, the format string, for the printf) and I get:
Meaning I think there might be a channel bug in the func code. I also notice that we hang at:
In the engine. This leads me to believe there is a channel bug in your function code. I can help debug that with you. BUT to be 100% sure it's not my fault (it might be!) I'm working on a real test for the engine stuff so I can be sure it's not the engine's fault. (I don't think so, but it's the most productive thing I can think to do on my own.) |
FAIL: TestAstFunc1/test_#16_(module_search1.txtar) FAIL: TestAstFunc1/test_#21_(recursive_module1.txtar)
As a first step, let's use panic(fmt.Sprintf("ExprRecur node %s still present after SetScope()", obj.Name)) instead of panic("ExprRecur node still present after SetScope()") so we can see which identifier is causing the problem. In
We can see that in |
Since Ah, I see the problem! This code was written to catch this case:
The idea is that when we encounter the In the problematic test, when we encounter The simple solution is simply to get rid of |
it isn't always correct, and recursion is already detected via other means anyway
cf4214d
to
25eb138
Compare
I have reset to your branch and dropped |
Not an issue, that's just the expected graph output format being different-- I fixed up all the others, but of course I couldn't fix this one until I knew what the output was supposed to be-- we're basically testing for stability at this point, instead of being precise and comparing to expected. Anyways, great news! These all pass too now, haha! It's sort of hilarious because weren't we just talking about ExprRecur being weird the other day? Amazing work! |
It seems a deadlock has crept in, or maybe it was just never seen previously :/ Hmmm. I think it happens with subtest 47 time go test github.com/purpleidea/mgmt/lang/ -run TestAstFunc2/test_#47
|
Seems it's actually a bunch that can fail. Time to bisect everything =D |
I'm proposing to merge all of this work in #719 |
Okay merged in #719 ! |
Congrats on the feature and the new release! |
This PR is not yet complete. It exists to make it easy to look at and comment on the code for the
mcl-functions
feature branch, which completely reworks the way in which functions work in MCL.The problem
Today in
master
, builtins and lambdas use very different compilation strategies. A builtin is compiled to a single Func which receives values from upstream nodes and emits values downstream. A lambda, by contrast, is compiled away, as the lambda's body is inlined at every call site. Once this inlining is performed, only builtins remain, so the resulting function graph is the graph of their Funcs.This does not work at all for MapFunc, a builtin which expects to receive a FuncValue as one of its inputs, because the above compilation strategy does not produce FuncValues, neither for builtins nor for lambdas. It would in fact be incorrect to construct a FuncValue from a builtin, because builtins can have an internal state and emit multiple values over time (e.g.
System("seq 3")
emits"1"
, then"2"
, then"3"
), but a FuncValue can only transform input values into a single output value, so it cannot represent that complexity. And of course, lambdas can contain builtins (e.g.func(x) { System(x) }
), so they cannot be represented by a FuncValue either.The solution
The function graph must be able to change over time, because if
func(x) { Map(System, xs) }
first receives a list of length 1 and then a list of length 2, then at first there must be one SystemFunc in the function graph, then later there must be 2 SystemFuncs.In order to do this, a FuncValue must be able to do more than transform input values into a single output value. Instead a FuncValue must describe in which way the function graph needs to change in order to accommodate a new instance of the function which the FuncValue represents. This branch's FuncValue does this by receiving input Funcs, creating and adding new nodes to the function graph, and then returning the one node among those new creations which shall be treated as the output Func, that is, the one whose output values are the result of applying the function represented by the FuncValue to the inputs received from the input nodes.
Another big change is that the function engine now works on a graph of Funcs instead of a graph of Exprs. This allows FuncValues to add Funcs to the function graph, without having to wrap them in Expr wrappers which don't correspond to any MCL code the user wrote.
Overview of the changes so far
xs
becomes a list of length 1 again, we must remove the now-disconnected SystemFunc node we created for the length 2 case.ExprVar
or anExprCall
.interpret_test.go:1202: test #62: funcs: process end err: context canceled...
messages. Those are red herrings since we're interrupting process and it's normal.interpret_test.go:1546
Remaining work
map
function changing its inputs over time, or other things as well.Given the number of changes, I would be surprised if all of that worked the first time, so in addition to the above, I expect a long debugging session to make all the tests pass.