-
Notifications
You must be signed in to change notification settings - Fork 27
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
Infinite loop when called on a Sequelize object #46
Comments
Note: the infinite loop we are experiencing does not result in a stack overflow or other sort of error. Rather, it seems the algorithm is just stuck, spinning forever (our server's CPU usage went from <1% to 100% basically immediately, though memory was seemingly not impacted). |
const fixture = {
x: {
name: 'a',
toJSON() {
return {name: 'b'}
}
}
}
console.log(fss(fixture)) will output: so it looks like toJSON is called in the main case, perhaps you've hit an edge case? Can you please provide a reproducible case? |
It's proving very difficult for me to replicate the error outside of our environment since we cannot easily port over the Sequelize instance object. I'll keep working on it and try to get you a working example soon. Thanks for the response. |
@MatthewHerbst could you inspect the output with It sounds like the |
I also met similar error. It's not infinite loop, but just very long computation (it could be minutes). We were using regular version of That long computation was caused by huge buffers, that could be inside Sequelize instance (could be several connections with read/write buffers). For instance, it was millions of |
ok that's a very interesting case. |
@davidmarkclements sure. Code to reproduce:
I also have added logging for traverse:
Sample log is attached.
|
Fwiw I just got hit by this using pino, which uses Normally everything is fine, but on sufficiently large/complex data structures (which I tried a few times to divine what "sufficiently large/complex" meant, but wasn't able to reproduce it outside of the production case (which does reproduce 100% of the time). |
This would be a large breaking change, but I generally wonder if an "actually safe" approach would be to stop at any value that was not: a) a primitive, b) an object literal (per Granted, the built-in |
Isn't that what we do already? The main meaning of "safe" is circular refs are handled |
I mean "safe" can mean whatever we/you want it to mean :-). I understand that currently "safe" means handles "circular refs", but I'm musing "safe" could also mean "do not Primarily/selfishly I'm proposing this because I think it would very likely avoid the degenerate " And granted, it's a large behavior change, mostly useful for this pino/logging case of "the application programmer put something in a log statement that they didn't mean to super-recursively put everything 'on the wire' to stdout", so probably needs to be addressed upstream in pino. |
I've tried to reproduce but you have data in a table that you haven't provided, and since the object being serialized is based on the data returned from sequelize, how can I reproduce without that data? I'm eager to solve this, if any one can provide a reproducible case I'll look into it. Preferably, the reproducible case should not involve databases, or third-party deps if possible. |
@davidmarkclements yeah understood on needing an easy repro. Fwiw I'm setting breakpoints locally and wondering if its not really a bug, but the algorithm being something like N^2 or something. Right now my I don't have a ton of stack frames on the stack, maybe few hundred: And it's not recursing truly infinitely b/c that would eventually blow the stack. It seems to be chugging away, just very inefficiently. Will keep poking at it. |
Okay, I need to move on for now, but here is where I got in debugging: I set a conditional breakpoint for when the stack hits 100 length. A few highlights from the image:
(Basically our domain model is a highly-inter-connected graph of a "project with a bunch of tasks, various levels/groupings of tasks (stage/unit/activity), and then dependencies (predecessors/successors) between the tasks. Obviously they become individuals rows in the database, but while in memory our ORM basically graph-izes all the references (intelligently/lazily/no+N+1-ily) so we can easily do things like critical path calculation, which is essentially walking a graph.) As I continue looking down the I.e. even though the 6th stack entry of So, could we somehow pre-tell ...i.e. here:
Put every element in Basically some differentiation of nodes between "has seen and not yet visited/processed" (not currently in the code AFAIU) and "has finished visiting/actually processed" (an entry in stack AFAIU). I am not an algorithms expert, but I vaguely recall seeing this distinction in graph traversal algorithms last time I was studying for interviews. :-P :-) Perhaps the code is already doing this and I'm just missing it. Not sure. Also fwiw I tried to re-create our graph with just a bunch of in-memory nodes with like 2-levels of 100 "parents" and 5 children-per-parent and failed to trigger this behavior. I think the input really does need to be pathologically circular, which is hard to create by hand (the graph I'm using is an actual one from production, hence the inherent size/complexity). I can try to synthesize also pathological input here & there/as I get time. (Also, just to be clear, we never wanted ...ah yeah, so this is really ugly, but just doing:
Stopped the pathological behavior. I have no idea is the resulting behavior is correct (i.e. I didn't inspect the output and it could be doing 'wrong things'), just that it stops the "not technically infinite but just a lot of it" recursion. Hopefully that is helpful... |
@mcollina Is this solved by the fixes in 2.1.0 and 2.1.1? |
I don't know and I don't have time to test. I think so. |
In ladjs/superagent@2e5d6fd the Superagent library changed from using
JSON.stringify
to usingfast-safe-stringify
for it's default object serializer when sending JSON data.We use Sequelize for our ORM. Sequelize uses their own custom object structure, which supports a
.toJSON
call.When Superagent was using
JSON.stringify
, that called the.toJSON
method on our Sequelize objects, properly converting them to JSON. However, with the change tofast-safe-stringify
,.toJSON
is no longer called, even if it exists.I'm not sure how to provide a copy of the object in question since getting its JSON representation is unlikely to be helpful, since all the Sequelize specifics are stripped from that.
Is there a reason this library does not call
.toJSON
if it exists?The text was updated successfully, but these errors were encountered: