Observable crash in 0.22 - Max call stack size exceeded

I just got the following error in 0.22 (OSX) - was working ok in 0.21:

  // ignore case so just convert both to lowercase before comparison
  function stringContainsString(main, filter){
      if (filter.length <= 3)
          return false;

      return main.toLowerCase().indexOf(filter.toLowerCase()) != -1;
  }

  var filteredItems = allItems.where(function(e){
      return Observable(function() {
          return stringContainsString(e.suburb, searchString.value);
      });
  });


LOG: InternalError: UnhandledException: Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 178
    Source line:     var i = current._dependencies.indexOf(this);
    JS stack trace: RangeError: Maximum call stack size exceeded
        at Observable.depend (FuseJS/Observable.js:178:32)
        at Object.defineProperty.get (FuseJS/Observable.js:513:8)
        at null._func (MainView.js:100:61)
        at evaluate (FuseJS/Observable.js:127:22)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
     in Outracks.Simulator.GeneratedApplication</usr/local/share/uno/Packages/FuseCore/0.32.11/$.uno:307>
ERROR: 
    Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 178
    Source line:     var i = current._dependencies.indexOf(this);
    JS stack trace: RangeError: Maximum call stack size exceeded
        at Observable.depend (FuseJS/Observable.js:178:32)
        at Object.defineProperty.get (FuseJS/Observable.js:513:8)
        at null._func (MainView.js:100:61)
        at evaluate (FuseJS/Observable.js:127:22)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)

Hey!

Do you have any code that reproduces this issue?

Just updated post, but essentially it’s this code:

  // ignore case so just convert both to lowercase before comparison
  function stringContainsString(main, filter){
      if (filter.length <= 3)
          return false;

      return main.toLowerCase().indexOf(filter.toLowerCase()) != -1;
  }

  var filteredItems = allItems.where(function(e){
      return Observable(function() {
          return stringContainsString(e.suburb, searchString.value);
      });
  });

Oh and really big allItems array (~15000 items).

Still crashing in 0.23, full log:

LOG: InternalError: Script error: Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 134
    Source line:         oldDependencies.forEach(function(x) {
    JS stack trace: RangeError: Maximum call stack size exceeded
        at FuseJS/Observable.js:134:35
        at Array.forEach (native)
        at evaluate (FuseJS/Observable.js:134:19)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
        at FuseJS/Observable.js:571:6
    </usr/local/share/uno/Packages/Fuse.Scripting.V8/0.32.14/$.uno:135>
LOG: InternalError: UnhandledException: Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 134
    Source line:         oldDependencies.forEach(function(x) {
    JS stack trace: RangeError: Maximum call stack size exceeded
        at FuseJS/Observable.js:134:35
        at Array.forEach (native)
        at evaluate (FuseJS/Observable.js:134:19)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
        at FuseJS/Observable.js:571:6
     in Outracks.Simulator.GeneratedApplication</usr/local/share/uno/Packages/FuseCore/0.32.14/$.uno:307>
LOG: InternalError: Script error: Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 511
    Source line:     get : function()
    JS stack trace: RangeError: Maximum call stack size exceeded
        at Object.defineProperty.get (FuseJS/Observable.js:511:16)
        at null._func (MainView.js:99:34)
        at evaluate (FuseJS/Observable.js:127:22)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
        at FuseJS/Observable.js:571:6
    </usr/local/share/uno/Packages/Fuse.Scripting.V8/0.32.14/$.uno:135>
LOG: InternalError: UnhandledException: Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 511
    Source line:     get : function()
    JS stack trace: RangeError: Maximum call stack size exceeded
        at Object.defineProperty.get (FuseJS/Observable.js:511:16)
        at null._func (MainView.js:99:34)
        at evaluate (FuseJS/Observable.js:127:22)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
        at FuseJS/Observable.js:571:6
     in Outracks.Simulator.GeneratedApplication</usr/local/share/uno/Packages/FuseCore/0.32.14/$.uno:307>
LOG: Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 511
    Source line:     get : function()
    JS stack trace: RangeError: Maximum call stack size exceeded
        at Object.defineProperty.get (FuseJS/Observable.js:511:16)
        at null._func (MainView.js:99:34)
        at evaluate (FuseJS/Observable.js:127:22)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
        at FuseJS/Observable.js:571:6

LOG: Failure state within a failed state. Simply ignoring that.
ERROR: 
    Name: RangeError: Maximum call stack size exceeded
    Error message: Uncaught RangeError: Maximum call stack size exceeded
    File name: FuseJS/Observable.js
    Line number: 134
    Source line:         oldDependencies.forEach(function(x) {
    JS stack trace: RangeError: Maximum call stack size exceeded
        at FuseJS/Observable.js:134:35
        at Array.forEach (native)
        at evaluate (FuseJS/Observable.js:134:19)
        at depChanged (FuseJS/Observable.js:113:16)
        at FuseJS/Observable.js:571:6
        at PumpMessages (FuseJS/Observable.js:562:3)
        at Observable._queueMessage (FuseJS/Observable.js:584:2)
        at Object.defineProperty.set (FuseJS/Observable.js:520:8)
        at depChanged (FuseJS/Observable.js:113:14)
        at FuseJS/Observable.js:571:6

Hey there SL,

Thanks for such a thorough report. I’ll raise and issue for this now and one of the us will get back to you as soon as we have some news.

Cheers

Hi again, it may help us to see a full example if you have time.

I tried to make a minimal test example by doing this

<App>
    <JavaScript>
        var Observable = require('FuseJS/Observable');

        // ignore case so just convert both to lowercase before comparison
        function stringContainsString(main, filter){
            if (filter.length <= 3)
                return false;

            return main.toLowerCase().indexOf(filter.toLowerCase()) != -1;
        }

        var testIt = function() {
            console.log("--- 0 ---");
            var allItems = new Observable({});
            searchString = new Observable("hi_there");

            console.log("--- 1 ---");
            for (var i=0; i < 16000; i++) {
                allItems.add("num"+i);
            }
            console.log("len of allItems = " + allItems.length);

            console.log("--- 2 ---");     // p.s swapping this and step 1 doesnt trigger the issue either
            var filteredItems = allItems.where(function(e) {
                return Observable(function() {
                    return stringContainsString(e.suburb, searchString.value);
                });
            });

            console.log("--- 3 ---");
        }


        module.exports = {
            testIt: testIt,
        }
    </JavaScript>

    <DockPanel>
        <TopFrameBackground DockPanel.Dock="Top" />
        <StackPanel>
            <Button Text="Play" Clicked="{testIt}" Height="100" />
        </StackPanel>
        <BottomBarBackground DockPanel.Dock="Bottom" />
    </DockPanel>
</App>

But this did not trigger any issue on fuse 0.23. This naturally suggests that the issue arrises when this observable is used.

I’ve got it down to this minimal test case:

<App>
    <JavaScript>
        var Observable = require("FuseJS/Observable");

        var allItems = Observable();
        for (var i=0; i < 16000; i++) {
            allItems.add({data:"num"+i})
        }

        var searchString = Observable("a");

        // ignore case so just convert both to lowercase before comparison
        function stringContainsString(main, filter) {
            return main.toLowerCase().indexOf(filter.toLowerCase()) != -1;
        }

        var filteredItems = allItems.where(function(e) {
            return Observable(function() {
                return stringContainsString(e.data, searchString.value);
            });
        });

        module.exports = {
            filteredItems:filteredItems,
            searchString:searchString
        }
   </JavaScript>

   <DockPanel>
      <TextInput Value="{searchString}" />
   </DockPanel>
</App>

Thanks for the test case. Investigating…

Hi,

When you have very large observables like here, .where() with an observable function as the condition becomes a very expensive operation, as it creates a lot of new observables per item in your array, each of which needs a subscription.

Instead of where() with an observable condition, use map(condition) to a where(), like this:

var filteredItems = searchString.map(function(x) {
        return allItems.where(function(y) { return stringContainsString(y.data, x); })
    }).inner();

This makes it aroundt 10000x faster, and avoids the stack overflow :slight_smile:

I’ve also made an experimental fix for the stack overflow as that shouldn’t happen in any case. If it passes testing, it might roll out.

Nice!

Thanks.

Hey Anders this is really interesting could you elaborate on the two differences between the two pieces of code. What’s happening in each, and why one is slower, I’m still a bit confused.