2.2.0
Older versions have this issue too.
https://jsbin.com/sujuwabufi/3/edit?html,js,output
The steps must be in order. If any step is skipped or the order is changed the bug does not appears.
App does not freeze.
The app freezes for several seconds, depend on how big the data set is. The reason behind this is change detection triggered for every object in the data set but it should only for the watcher one time and for the computed property one time.
One thing to note is this only happen one time after the first mutation. After that everything work as intended.
Here is a profiling
Sidenote: deep watching a huge object is absolutely not recommended.
@yyx990803 Did not have any issues with it before. The objects are frozen so basically its not so deep watcher. As far as I can tell performance and memory consummation is not much effected if You are watching like 50k properties. Are there any reasons behind it?
@kailniris it depends on your use case, but most of the time I find a deep watcher can be replaced by some better alternatives.
Interestingly the freezing behavior only occurs in Chrome - works fine in Safari and FF. Looks like it's some weird V8-specific de-optimization. I don't think this is a bug on Vue's part - but we will see if we can work around this in Chrome...
Here is a more reduced example. Without dom/compiler.
Vue.component('comp', {
data() {
return {
dataProp: ''
};
},
watch: {
dataProp() { }
},
render: h => h('')
});
new Vue({
render: h => h('comp')
}).$mount();
var items = {};
for (var i = 1; i < 50000; i++) {
Vue.set(items, i, Object.freeze({ _id: i, }));
}
var vm = new Vue({
data: {
items: items
},
methods: {
test: function test() {
var item = {_id: 1};
Vue.set(items, item._id, item);
}
},
computed: {
computed() {
return 'asd';
},
item() {
return this.items[1].name;
}
},
watch: {
items: {
handler() { console.log('changed!')},
deep: true
}
},
render: h => h('comp')
}).$mount();
setTimeout(vm.test, 1000)
Strangely, nothing can be removed anymore. Even seemingly unrelated computed propertycomputed
cannot be removed in order to reproduce.
Even if You switch the instance registration sequence the problem will not occurs.
Looking into this briefly, it seems that reactiveGetter
runs into some deoptimization issue in V8. Running with --trace-deopt
shows
[deoptimizing (DEOPT eager): begin 0x249c405da2d9 <JS Function reactiveGetter (SharedFunctionInfo 0x35761089bf1)> (opt #14) @5, FP to SP delta: 64, caller sp: 0x7fff51fe6060]
;;; deoptimize at <https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js:635:15> inlined at <https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.js:847:13>, wrong map
reading input frame reactiveGetter => bytecode_offset=0, args=1, height=6; inputs:
0: 0x249c405da2d9 ; [fp - 16] 0x249c405da2d9 <JS Function reactiveGetter (SharedFunctionInfo 0x35761089bf1)>
1: 0x249c405d5d81 ; [fp + 16] 0x249c405d5d81 <an Object with map 0x29c6e4760191>
2: 0x2e7797b3a811 ; [fp - 8] 0x2e7797b3a811 <FixedArray[11]>
3: 0x1f0127a04aa9 ; (literal 5) 0x1f0127a04aa9 <Odd Oddball: optimized_out>
4: 0x1f0127a04aa9 ; (literal 5) 0x1f0127a04aa9 <Odd Oddball: optimized_out>
5: 0x1f0127a04aa9 ; (literal 5) 0x1f0127a04aa9 <Odd Oddball: optimized_out>
6: 0x1f0127a04aa9 ; (literal 5) 0x1f0127a04aa9 <Odd Oddball: optimized_out>
7: 0x1f0127a04aa9 ; (literal 5) 0x1f0127a04aa9 <Odd Oddball: optimized_out>
8: 0x1f0127a04aa9 ; (literal 5) 0x1f0127a04aa9 <Odd Oddball: optimized_out>
translating interpreted frame reactiveGetter => bytecode_offset=0, height=48
0x7fff51fe6058: [top + 104] <- 0x249c405d5d81 ; 0x249c405d5d81 <an Object with map 0x29c6e4760191> (input #1)
-------------------------
0x7fff51fe6050: [top + 96] <- 0x104f81039c99 ; caller's pc
0x7fff51fe6048: [top + 88] <- 0x7fff51fe6078 ; caller's fp
0x7fff51fe6040: [top + 80] <- 0x2e7797b3a811 ; context 0x2e7797b3a811 <FixedArray[11]> (input #2)
0x7fff51fe6038: [top + 72] <- 0x249c405da2d9 ; function 0x249c405da2d9 <JS Function reactiveGetter (SharedFunctionInfo 0x35761089bf1)> (input #0)
0x7fff51fe6030: [top + 64] <- 0x1f0127a02311 ; new_target 0x1f0127a02311 <undefined> (input #0)
0x7fff51fe6028: [top + 56] <- 0x3576108b679 ; bytecode array 0x3576108b679 <BytecodeArray[126]> (input #0)
0x7fff51fe6020: [top + 48] <- 0x3500000000 ; bytecode offset 53 (input #0)
-------------------------
0x7fff51fe6018: [top + 40] <- 0x1f0127a04aa9 ; 0x1f0127a04aa9 <Odd Oddball: optimized_out> (input #3)
0x7fff51fe6010: [top + 32] <- 0x1f0127a04aa9 ; 0x1f0127a04aa9 <Odd Oddball: optimized_out> (input #4)
0x7fff51fe6008: [top + 24] <- 0x1f0127a04aa9 ; 0x1f0127a04aa9 <Odd Oddball: optimized_out> (input #5)
0x7fff51fe6000: [top + 16] <- 0x1f0127a04aa9 ; 0x1f0127a04aa9 <Odd Oddball: optimized_out> (input #6)
0x7fff51fe5ff8: [top + 8] <- 0x1f0127a04aa9 ; 0x1f0127a04aa9 <Odd Oddball: optimized_out> (input #7)
0x7fff51fe5ff0: [top + 0] <- 0x1f0127a04aa9 ; accumulator 0x1f0127a04aa9 <Odd Oddball: optimized_out> (input #8)
[deoptimizing (eager): end 0x249c405da2d9 <JS Function reactiveGetter (SharedFunctionInfo 0x35761089bf1)> @5 => node=0, pc=0x104f8103b660, caller sp=0x7fff51fe6060, state=TOS_REGISTER, took 0.195 ms]
[removing optimized code for: reactiveGetter]
around 14000 times, completely dominating. Running with Canary and --future in this case, but it's a similar picture for the M58 with Crankshaft/Full-Codegen. I'm not exactly sure what's going on, but we'll have a look.
Thanks for chiming in @bmeurer !
@jaro-sevcik is going to take a look now.
@jaro-sevcik @bmeurer woot! Thanks for the quick fix :)
Closing this for now since there doesn't seem to be anything actionable on Vue's part besides waiting for the v8 patch to go live.
Most helpful comment
Fixed in v8: https://chromium.googlesource.com/v8/v8/+/fe704477d6b515f2ab2b13cb26eb05556a2cf134