Live/Max hang (Live at 100% CPU) when saving patch


    Mar 06 2021 | 1:57 pm
    Hi all,
    I've spent hours trying to get to the bottom of this, but so far to no avail. The patch is set up as follows:
    • I have one .maxpat which is monitoring the track the device sits on, and outputs a 0 or 1 when that track is selected or deselected. The patch uses two live.path objects and a live.observer object, as well as a JavaScript object that implements some simple logic; it uses deferlow to make sure that whatever actions are done in response to notifications are done asynchronously.
    • Then the main .amxd uses bpatcher to include the above .maxpat (using bpatcher because it provides a UI element showing whether or not the track is selected), and grabs control over the Push's button matrix when the track is selected and relinquishes control again when the track is deselected. This too is implemented in a JavaScript object.
    All this works beautifully, but when I go to save the (amxd) patch, both Max and Live hang, with Live using 100% CPU (Max using only a few percent). I then have no choice but to Force Quit the process; when I restart Live, I get the crash report, but I've looked at the logs, and there's not much of interest there -- not totally unexpected, since in a way of course it didn't actually crash (rather it was hanging).
    The above is already the result of shrinking this to the minimal thing that still hangs. Some things I've thought about:
    • For a while I thought maybe it had something to do with doing too much in response to notifications, hence the emphasis on deferlow above.
    • I also make sure I don't actually use the LiveAPI until the device is fully initialised (I wait for the bang from live.thisdevice).
    • I removed everything that would introduce any kind of parameters (I thought perhaps I was doing something bold with parameters that was messing up the Live parameter table).
    • I'm pretty sure the JavaScript code isn't looping anywhere (and anyway the patch works; it just crashes when I save it).
    I am a bit at a loss here how to continue debugging this; any advice would be very much appreciated.
    -Edsko
    PS. In case it matters: 2017 Macbook Pro; Live 11, Max 8.1.10.

    • Mar 07 2021 | 10:24 am
      This morning I woke up with a start, "ah, there is one more potential loop in my code" (anyone else have that kind of wake up sometimes? :) .
      Let me start with the conclusion: resolving paths with "new LiveAPI(null, path)" was returning non-sensical results. Details:
      • I was running some code in response to the "preview state" (third output) of a "live.thisdevice" going back from 0 to 1 (re-initialising the patch after it was saved).
      • This code was using the LiveAPI, but as I mentioned, it turned out that resolving paths (using "new LiveAPI(null, path)") was returning non-sense results; new LiveAPI(null, "this_device") was returning an object of type "Push2" instead of the expected "MaxDevice".
      • When I inserted a "deferlow" in between the output of the live.thisdevice and the JS code, I was suddenly getting errors "jsliveapi: Live API is not initialized, use live.thisdevice to determine when initialization is complete".
      • From the above my tentative conclusion is that the Live API is temporarily unavailable after a patch is saved, and that getting a "1" from from the "preview state" output of "live.thisdevice" does not mean it is available again.
      • Normally, we wait for "live.thisdevice" to output a bang on its first output in order to know that we can use the Live API. Unfortunately, the "live.thisdevice" does not output a bang on that output after a patch is saved.
      • I'm therefore not aware of any event I can use to wait for that tells me the LiveAPI is available again after the patch is saved. Right now I'm using a delay 1000 (followed by a deferlow), which seems to work, but it feels a bit brittle.
      As for the code that was looping: I hadn't thought about it before because I expect this loop to iterate a few steps at most: it's a loop that looks for the parent of this_device:
      exports.deviceTrack = function(path) {
        return function() {
          var parentObj  = null;
          var parentPath = path;
      
          var i = 0;
      
          do {
            parentPath = parentPath + " canonical_parent";
            parentObj  = new LiveAPI(null, parentPath);
          } while(parentObj.type !== 'Track' && parentObj.id != 0);
      
          return parentObj;
        }
      }
      Here, "path" is usually "this_device"; so this follows the advice from https://docs.cycling74.com/max8/vignettes/live_api_overview#Canonical_Parent and uses "this_device canonical_parent" to find the track that the Max device sits on; except that if that Max device is grouped, the canonical parent is actually the chain of the group, rather than the track, and so we need to continue going higher up to find the track itself. However, as mentioned above, that "new LiveAPI" was returning non-sensical results; rather than returning the track or a chain, it was running an object of type "Push2", and looking for its canonical parent just returned that very same Push2 object again, ad infinitum.
    • Mar 07 2021 | 2:55 pm
      Hello. (anyone else have that kind of wake up sometimes? :) I exactly know what you are meaning :D Very interesting to read what you are working about. I'm doing similar things but without java objects coded by myself. Unfortunatly i'm not able to write java codes. Maybe sometime i will get into it. What exactly does your java objects do?
      "I'm therefore not aware of any event I can use to wait for that tells me the LiveAPI is available again after the patch is saved. Right now I'm using a delay 1000 (followed by a deferlow), which seems to work, but it feels a bit brittle."
      ... dont know if it works for you, but 'savebang' object outputs a bang when the patcher is saved.
      Thanks for sharing this matthias
    • Mar 07 2021 | 4:39 pm
      What exactly does your java objects do?
      (JavaScript, not Java.) I find it easier to implement most of the core logic in JavaScript. The general approach I take is described in http://edsko.net/2020/12/26/trichords-part1/ (and part 2 at http://edsko.net/2020/12/27/trichords-part2/ ) , but I've changed the setup a bit since I wrote that blog post: I now implement part of the logic in JavaScript (anything that has non-trivial control flow basically), but I take advantage of the visual clarity of Max for wiring things up. I find this works quite well.
      In particular, for the "is the track this device lives on currently selected" sub patch, the layout is as follows:
      ... dont know if it works for you, but 'savebang' object outputs a bang when the patcher is saved.
      Ah, interesting idea! Hadn't considered that. Tried it just now, but unfortunately the order of events doesn't seem quite right. We first get the bang from the 'savebang' object, and only then the notifications from the "preview enabled" outlet (#3) of the "live.thisdevice". Pity! Thanks for the suggestion anyway :-)
      -Edsko
    • Mar 07 2021 | 9:37 pm
      Can we start a place called 100% CPU and have a live Max hang? Because that's how I read it and its a good idea... at least in my head.
    • Mar 07 2021 | 9:44 pm
      Can we start a place called 100% CPU and have a live Max hang? Because that's how I read it and its a good idea..
      ...but...but... then how did you read the "when saving patch" part?... no wait, i got it now: there will be a "Live Max Hang!" tonight at "100% CPU" in downtown Motherboard tonight! it's a fundraiser to help save our friend, "Patch" from unfair quantization conditions! πŸ™Œ yes, i wholeheartedly support this idea πŸ‘