-
Notifications
You must be signed in to change notification settings - Fork 545
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 stackTrace property to fiber #868
base: main
Are you sure you want to change the base?
Conversation
If this is a bug, don't |
often you want resilience as well as information, which this PR aims for. |
Logging and so? |
This is not resilient:
|
I think there are valid use cases, where you want a detailed error report without killing the vm.
|
Anyway, remove what I said 😄 You're right. |
Incidentally, doest it work on new fiber and yield fiber? And does it out
something useful?
|
It would be good to try to compress the stack trace in case of infinite
direct recursion. Since the trace as limited output, it possible to not see
where the error started.
In addition if the stack trace get trunckated there should be an indication
of the situation, so the user does not get confused.
|
@mhermier It returns null for a new Fiber and works correctly on a yielded Fiber. The current implementation will stop walking the stack after the 16kb buffer was filled. |
(sorry for the noise)
Maybe a better solution would be to allow to iterate on stack frames
entries, and let wren make the string.
It is suboptimal, may have memory implications, but it should be easier to
let the user customize the stack trace output.
|
outputBuffer[0] = 0; | ||
int bytesWritten = 0; | ||
|
||
// Allocate 512byte for one line int the output |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"int" the output?? "in" the output... :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks ;)
ObjFiber* fiber = AS_FIBER(args[0]); | ||
|
||
// Allocate 16kb for our output string | ||
char outputBuffer[OUT_BUFSIZ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably better with allocating the string on the heap from the start. This will save us a copy and also avoid allocating much memory on the stack, which can cause a stack overflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see the benefit. wrenNewString will copy the memory in any case. As for the risk of a stack overflow: I'm not experienced enough in C to give an estimate of it's likelyness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The likelihood depends mostly on compile flags. I found https://softwareengineering.stackexchange.com/a/310659, which says that 16kb are probably fine.
As for the copy, I meant to allocate a ObjString
directly, then write to the space. I know this is a space for problems because we touch low level details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of the ObjString and will have a look into it. I probably have to think of hashing the string myself as well
They meant for debugging and not for text processing, IMO. You can process the stack trace and find the function names etc., that reminds me what angularjs used to do... :P |
I thought about that but felt that the additional complexity was not worth it |
Yes but that info can be used in plain text, viewed inside a debugger and
displayed in columns in a table, the user can make a stack trace formatter
more to it's liking, or whatever.
And more importantly, we have access to the whole trace, and not possibly a
truncated version of it.
|
I see the benefits too, I just don't see a simple way of doing it. This means we would need to make some kind of "StackIterator" available that let's you iterate "StackFrame" objects both exposed to wren. Right? |
A simple solution is to do like string does with
The implementation can be lazy or not. Going not lazy has the advantage of being able to compare stacks/make them easy to compare trivially, and could be helpfull in case of implementing a debugger that allows to trace instructions. But it is not an absolute requirement, since if we have access to the data we can make a copy of it. |
If we don't want to bloat core, another possibility is to move this to an extra module and move the foreign functions to |
Thats also what I was thinking of. I think C# does a similar thing. I have a few concerns about this. Bloating core and the overall code base is one of them, I also think we will need to exchange the iterator with a pre-filled list. Otherwise we would be able to run the fiber between iterations, which sounds like a recipe for disaster. I would definitely be willing to give such an implementation a try but I would like to hear some more opinions on that before putting any more work in it. |
One solution to mitigate the bload is to lazy import
|
Possible but using a module external of core for accessing Wren internals seems a little odd. It might be possible to expose stack-walking functionality to the public C Api and let people implement the functionality themselves - but this seems overly complex to me |
Just thought about a simpler implementation that don't need a separated module:
|
Most of them are internal changes, rejected extensions to the languages, previous attempts of some of the features that now have a more proper pull request. All in all, most of it is
Well for me the modules is an unsolved problem, I find the builtin handling to be too much simplistic, and we should improve |
I'm not 100% sure what you mean here, perhaps you'd want to join the discussion over on wren-lang/wren-cli#52 and flesh out those ideas a bit.
I've improved it slightly in Writing clean C code isn't my strong suit though so I'm hoping someone comes along interested in that piece of the puzzle. The branch I pointed you to earlier works 100%. You can compile |
I mean some infrastructure/tooling to normalize external plugin loading. I don't blame you in particular, but I think that the fork culture is plaguing wren. |
That's what wren-lang/wren-cli#52 is working towards. It of course ultimately needs more testing, polish, and merging into CLI proper.
Not sure I follow. This is what I'm trying to avoid - everyone having to maintain 100 patches just to use binary libraries. And then the library would be installed locally and ready to use for any script. |
@joshgoebel Well from my understanding it, essential is a deviation based on taste/jugement. Considering the amount of possible patch maintenance work, maintaining a distribution of |
A deviation from what? It's just a library. It would be Wren + C code that is 100% compatible with Wren Core (with zero patches). It seems very natural to me to group related functionality into modules/classes (as every other languages I know does). Some sort of "one function per branch" seems very strange (for libraries) - and also a huge amount of work vs just publishing a library - as is already common practice, even for Wren: https://github.com/wren-lang/wren/wiki/Modules. I totally see where that makes sense for patchsets, but not libraries.
I don't really have a great answer for "patches" to Wren Core... that's entirely outside the scope of what I'm working on and I think a different (but related) problem entirely... |
@mhermier Trying to get this FiberMirror stuff to work without success. Are there a bunch of other patches I'm not aware of? I get an error with the stack trace builder:
|
Related to a patch of mine that expose modules as objects. Will think of a way to deal with that, probably using mirrors. |
No, but it all seems to work other than one tiny typo and missing
That was my thought, a FunctionMirror.
Link to commit? I wasn't seeing the code. |
For now it will stay private. The modules are not exposed by design, and I don't want to raise more controversies for now. There is already some raging debates to fight that this one. I'll try to make it using mirrors, even if it leaks |
Hmmmm... Who is saying expose them as objects? But you have to expose at least "function name" and "module name" to have a workable stack trace - there is no way around that. So do some simply think that you shouldn't be able to retrieve a stack trace for a failing fiber? I was imagining that you'd add: class FunctionMirror {
foreign name { ... }
foreign moduleName { ... }
} Just the bare minimum needed for stack tracing. |
Things are taking shape :
Still have some issues:
|
Maybe we could coordinate a bit on Discord? I'm starting to think I may need this sooner rather than later and perhaps there is some way I could help. My context/use case is teaching others to program in Wren via programming exercises and I'm finding it really hard to work thru failing tests without a proper stack trace pointing to the line that has actually caused the error. So much so that it might actually be a blocker for my use case.
Obviously this is the smallest detail and easy to fix. I presume people needing a stacktrace directly will be doing custom processing of it in many cases so switching the order is trivial. |
I don't use discord, and will not subscribe. I have a personal rule of diminishing as much as possible my internet finger print, in terms of social media/chat group/... so I only maintain what I already have, unless absolutely required. Don't know if you can send me private message on github ? The prototype is working to some extent, thought I have a memory optimization to look before I'll push a new version for test. It seems that a function/method object is storing its name as a plain C string, and it would be more efficient to use an |
Not that I know of. :) Signal?
Very curious to see what you come up with and how low-level it is. I'm still hoping to migrate the Mirror stuff to a library so it can be loaded dynamically. I was 95% there before I ran into the missing access to module/function name. :) |
No Signal XD I pushed an update, I did not included the change for |
Are you sure it shouldn't be:
With 3 dots? it's a bit rough for anonymous functions but I guess it might be hard to find a variable name for them?
|
Haven't diagnosed it yet but it also seems to get "stuck" inside classes...
import "essentials:essentials" for Strings
import "mirror" for Mirror
System.print(Strings.upcase("bob"))
System.print(Strings.downcase("LUCY"))
var e = Fn.new { Fiber.abort("bad") }
var d = Fn.new { e.call(22) }
var c = Fn.new { Boo.smith() }
var b= Fn.new { c.call(96)}
var a = Fn.new { b.call(32) }
class Boo {
static smith() { d.call() }
}
var f = Fiber.new(Fn.new {
a.call()
})
var x = f.try()
System.print(Mirror.reflect(f).stackTrace)
System.print("STILL RUNNING") The stack trace just stops inside |
Can be I was not able to reproduce where you are stuck. |
My fault. I was only printing the trace, not the error. I needed
The
I'l call it from the "main" thread but wanting the trace of the fiber that crashed. It seems to work perfectly now that I fixed the error in the code. :) (with the |
I think it is a limitation of the compiler when it creates the function. For now I'm looking for functional bug, this one is more cosmetic. |
Porting into a library:
It's still a little messy since I did this by hand and I think you renamed a bunch of stuff in your last rebase and I only copied over the Fiber changes I needed. BUT... if you're using the latest So yes, it does require access to a few "wren internals" but that doesn't mean that access can't be provided at a later date via a binary extension library - it doesn't have to be compiled into the CLI from the very beginning. Just as long as you're using the same data structures, etc.... I think libraries that do this should be very careful to say so and make it clear they depend on Wren internals. Definitely not a thing every library needs to do. Again this is still early and more "proof of concept"... long-term we'd hopefully want to figure out how to make this whole process easier. From a maintainer perspective what would be desirable is if I could run a single command that (at least) mostly updates the library with the source from your Core patch - but bundles it up into module library form - all without manual intervention. I can see how someone using "Wren Core" in their own project might want a core patch for this (because what else is there to add binary functionality), but I can also see someone using Wren CLI expecting they could just install this functionality via a dynamic library, like any other binary library functionality. So it seems we need some way to keep these things in sync for libraries that would be useful in both contexts (Mirror just being one of them). This is all assuming Mirror remains outside Core - if it were merged then this problem is solved, at least for Mirror lib. What is nice to note here is the Wren code is of course already entirely portable. I simply copied it directly from your repo into mine and it works with 0 changes. All the complexity is at the C layer. |
Updated, I made |
Very difficult to keep up with your rebasing. :-) That's why we need something automated. :) |
Because it was not meant to be taken but reviewed. |
Thanks for all your hard work on this, really do appreciate it.
Even for just reviewing I find it easier to keep up with moving changes to a PR when I can see the individual commits as they layer on. Perhaps it's just me. |
It is not just you, but a way of mine to deal with patches and propositions. I have changes that impact so much the code, that it is almost impossible to do a merge each time an update it is performed. |
You can have your cake and eat it too if you interactively rebase. It's all about how you lay out the commits so when you rebase at the very end you can squash them down into the structure you desired all along... so while committing/developing, you have the FULL history on top (and for reviewers it's easy to see what you JUST changed), yet at the end you still have only 3 clean commits. (and they merge or patch as easily as they do now) Git is pretty amazing like that. |
Yes but usually the bus factor is so long that I have to reduce the patch set often so I don't loose track of what is going on, multiplied by the number of changes that I have in flight... |
I was writing a small test runner and noticed that after calling
fiber.try
, you can only obtain the error text but not the location where the error occured, which makes debugging very cumbersome.I added
fiber.stackTrace
in addition tofiber.error
. The stack trace is formated on the fly, which means there is no additional performance overhead if you don't use the property.This also works for fibers which are yielded. It will return null if the fiber hasn't run yet or returned already.
I added two additional tests to show how it works.
EDIT: Looks like some commits show up here that should be in main already. Basically, I only want
e3f1774 and ad05671. Anyone knows some git magic on how to fix that?