Tools - Creating documents from a coroutine - illegal instruction on exit

Description:

If you create a DocumentNode from within a coroutine, when exiting Renoise a dialog appears saying “Renoise quit unexpectedly”. Clicking on report shows an illegal access.

Reproduction steps:

  1. Create a new Renoise tool. Put the following in main.lua:
renoise.tool().tool_finished_loading_observable:add_notifier(function ()
  coroutine.wrap(function ()
    print("making prefs from a coroutine")
    local prefs = renoise.Document.create("CoroBugPreferences") {
      configured = false,
    }
    prefs:load_from("config.xml")
  end)()
end)
  1. Zip the tool and install it.

  2. Verify the tool ran by checking the tool terminal. Then exit Renoise.

Expected results:

I don’t get an error when shutting down Renoise and it can shut down cleanly.

Actual results:

The OS shows an error.

Environment:

  • MacOS 14.4.1 with System Integrity Protection enabled
  • Renoise ARM64 V3.4.4 (built May 7 2024)

Error Report

I won’t post all of it because I don’t know what is safe to share. But hopefully this helps:

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGILL)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000000000000010
Exception Codes:       0x0000000000000001, 0x0000000000000010

Termination Reason:    Namespace SIGNAL, Code 4 Illegal instruction: 4
Terminating Process:   Renoise [48857]

VM Region Info: 0x10 is not in any region.  Bytes before following region: 4301471728
      REGION TYPE                    START - END         [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      UNUSED SPACE AT START
--->  
      __TEXT                      100634000-101638000    [ 16.0M] r-x/r-x SM=COW  /Applications/Renoise.app/Contents/MacOS/Renoise

Towards the end I see “(Data Abort) byte read Translation fault”.

Details

The bug still occurs without the outer tool_finished_loading_observable callback. I think that should be there before I try to access tool preferences, so I left it.

I ran into this while working on a much more complex tool. This is the trimmed down version. I am using coroutines extensively to make the observable callbacks easier to deal with. As a workaround I can only load preferences outside of the coroutine. That prevents the error.

Thanks for the detailed report!

Creating the document inside the coroutine seems to be the problem here. I’m working on it, but it will take a while to fix.

As already mentioned, you can work around this for now by moving the document creation out of the coroutine. But you can keep the loading/saving there:

local prefs = renoise.Document.create("CoroBugPreferences") {
 configured = false,
}

renoise.tool().tool_finished_loading_observable:add_notifier(function ()
 local f = coroutine.wrap(function ()
   print("loading prefs from a coroutine")
   prefs:load_from("config.xml")
 end)
 f()
end)

Just out of interest, how do you use coroutines in notifiers as patterns? What problem does this solve?

Thanks for checking so quickly. That’s great I can still load and save in the coroutine.

I made a little async executor with futures/promises. You can spawn multiple tasks and if the task is waiting on a callback from
Renoise it suspends.

Similar to using promises in JavaScript, it lets you avoid nesting a bunch of callbacks.

So for example, the tool finished loading callback can be written like this:

Future.from_observable(renoise.tool().tool_finished_loading_observable):await()

— my plugin code here…

Where it is really helpful though is using combinators. One use case I have is waiting for a reply from a midi device or timing out if it isn’t received. I can write it like this:

let reply = Future.race(
  delay(1000).and_then(function () error(“timeout”) end),
  midi_device.wait_for_sysex(is_identify_reply)
):await()

I am planning on writing a forum post. It’s not a lot of code and it’s not tied to my project or anything.

2 Likes

Very cool. With midi, which is async by design, this actually makes a lot of sense. Would be great if you could share this with us.

I’ve now fixed this by not allowing new document models to be created in coroutines. Difficult to explain why, but allowing this would require quite a bit of work here which probably isn’t worth it.

If you try to create a document model in a coroutine, it will spit out an error, telling you that you should create the model on the main thread and then use or instantiate it in the coroutine.

Lua document models can not be created in coroutines. 
Create the model in the main thread via `create("MyDocument"){ some_init_content... }`, 
then instantiate it in the coroutine via `instantiate("MyDocument")` if necessary.

So in your case, the workaround could be either:

---create a new document model blueprint
renoise.Document.create("CoroBugPreferences") {
 configured = false,
}

---instantiate document model in the coroutine
renoise.tool().tool_finished_loading_observable:add_notifier(function ()
 coroutine.wrap(function ()
   print("loading prefs from a coroutine")
   prefs = renoise.Document.instantiate("CoroBugPreferences")
   prefs:load_from("config.xml")
 end)()
end)

or:

---create a new document model instance
prefs = renoise.Document.create("CoroBugPreferences") {
 configured = false,
}

---use document model in the coroutine
renoise.tool().tool_finished_loading_observable:add_notifier(function ()
 coroutine.wrap(function ()
   print("loading prefs from a coroutine")
   prefs:load_from("config.xml")
 end)()
end)

That works for me! As long as I can load and save from a coroutine it’s not a blocker at all.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.