Open Bug 1925128 Opened 27 days ago Updated 14 days ago

School Edsby website hangs

Categories

(Core :: JavaScript Engine, defect, P2)

defect

Tracking

()

Tracking Status
firefox-esr128 --- disabled
firefox131 --- disabled
firefox132 --- disabled
firefox133 --- disabled
firefox134 --- affected

People

(Reporter: neha, Assigned: bthrall)

References

(Blocks 1 open bug, Regression)

Details

(Keywords: regression)

​​My son's school uses an edsby website for parent-teacher comms and it used to work smoothly before and recently (maybe since last 2 months) it hangs. I captured a profile with it: https://share.firefox.dev/3A1CihU . Hopefully this helps.
The website https://rotherglen.edsby.com requires login so I'm happy to collect more info if you can direct me to collect whatever you might need to fix it.

Steps to Reproduce:

  1. Login to https://rotherglen.edsby.com
  2. On the left menu, click on the child name to get info for the child's class.

Expected Results:
Class teacher posts including pictures are shown in chronological order.

Actual Results:
None of the class posts load, the content area is empty.
The content finally loads after 2 minutes.

Profile URL:
https://share.firefox.dev/3A1CihU

Doing the same in Chrome, it isn't immediate but the content on the page loads in 1-2 seconds.

The time is spent doing nursery collections while parsing JSON. https://share.firefox.dev/3zP5VDe

It would be interesting to find out which JSON is being parsed, so that we can turn it into an anonymized testcase.

I believe the JSON parsing is coming from here:

        _clone_internal: function(e) {
            if (e instanceof A) {
                let t = "c";
                return b.deserializeClass(e.serialize(t), t);
            }
            try {
                let t = Math.random(), a = JSON.stringify(e, function(e, a) {
                    return "object" == typeof this[e] && this[e] instanceof A ? this[e].serialize(t) : a;
                });
                return JSON.parse(a, function(e, a) {
                    return "object" == typeof a ? b.deserializeClass(a, t) : a;
                });
            } catch (e) {}
        }
See Also: → 1848090

Neha, can you check if it's a regression using mozregression?

Flags: needinfo?(nkochar)

This is likely a regression from the json-parse-with-source feature we implemented (bug 1913085 or an earlier bug).

Because it's calling JSON.parse with a reviver function, we maintain "parse record" objects that basically mirror the parsed JSON objects but store source information for it. We're fully tracing these maps on each nursery GC it looks like.

For the JS shell micro-benchmark below:

--setpref=experimental.json_parse_with_source=false: 450 ms
--setpref=experimental.json_parse_with_source=true : 13442 ms

Bryan, as a first step I think we should flip the pref, definitely on beta, because it will give us more time to fix this regression on Nightly.

function f() {
    var o = [];
    for (var i = 0; i < 100000; i++) {
        o.push({x: "foo".repeat(10) + i, y: "bar".repeat(10) + i});
    }
    var json = JSON.stringify(o);
    var res;
    var t = new Date;
    for (var i = 0; i < 5; i++) {
        res = JSON.parse(json, (key, value) => value);
    }
    print(new Date - t);
    return res;
}
f();
Component: Performance → JavaScript Engine
Flags: needinfo?(bthrall)

(unsetting needinfo for Neha, I don't think we need the regression range anymore)

Flags: needinfo?(nkochar)
Assignee: nobody → bthrall
Blocks: sm-runtime
Severity: -- → S3
Flags: needinfo?(bthrall)
Priority: -- → P2
Depends on: 1925334

We expected the json-parse-with-source feature to make calling JSON.parse with a reviver function more expensive, but this is worse than I expected.

Most of the cost appears to be from allocating JSStrings for the "source", but about a third of it comes from just allocating the context parameter to the reviver.

I'll look into reducing the cost from those two allocations.

I think the bigger problem is the GC fully tracing the parse record object roots. You can see this in the flame graph tab of the profiles in comment 0 and comment 2, and in a profile for the micro-benchmark in comment 7.

The tracing gets slow for large object graphs, because there's more and more stuff to trace through.

Keywords: regression
Regressed by: 1913085

Set release status flags based on info from the regressing bug 1913085

I think the bigger problem is the GC fully tracing the parse record object roots.

Yes, the problem is that there's a large data structure on the stack which is getting traced every time we do a minor GC. One fix for this would be to use JSObjects to hold the ParseRecordObject tree. These would become tenured and since nursery tracing stops when it reaches a tenured object this would greatly reduce the amount of stuff traced.

Another improvement we could make is to pass gcHeap as the third parameter to NewStringCopy in JSONReviveHandler<CharT>::finishPrimitiveParseRecord. Half the minor GCs in the profile come from that site. This would stop allocating source strings in the nursery when the output tree becomes too large to fit in the nursery.

(In reply to Jon Coppeard (:jonco) from comment #12)
I also wondered about the possibility of calling the reviver functions as we parse the JSON tree so avoiding the need to create this data structure in the first place. I didn't check the spec too closely so maybe that doesn't work.

Set release status flags based on info from the regressing bug 1913085

You need to log in before you can comment on or make changes to this bug.