-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#896 Fix hard crash when duplicating mappings that have Lua scripts
This crash was introduced by v2.16.0-pre.2 commit 073b9b2, which removed manual code previously responsible for deferring deallocation from real-time thread to main thread. However, since Lua script engine isn't aware of our special auto-deferring deallocator, deallocation was done in real-time thread. And since it was in some way a shared copy of the compiled script, there was a race condition and/or double free.
- Loading branch information
Showing
16 changed files
with
138 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/// A wrapper that implements Clone but not really by cloning the wrapped value but by | ||
/// creating the inner type's default value. | ||
/// | ||
/// This is useful if you have a large nested value of which almost anything inside must be cloned | ||
/// but you also have a few values in there that don't suit themselves to be cloned (e.g. compiled | ||
/// scripts). This must be well documented though, it's a very surprising behavior. | ||
/// | ||
/// Alternatives to be considered before reaching out to this: | ||
/// | ||
/// - Making the whole graph not cloneable | ||
/// - Using `Rc` or `Arc` (is cloneable but means that the inner value is not "standalone" anymore, | ||
/// can be accessed from multiple places or in case of `Arc` even threads ... and it means that | ||
/// you have less control over when and in which thread deallocation happens). | ||
/// - Writing a dedicated method (not `clone`) which makes it clear that this is not a standard | ||
/// clone operation. | ||
#[derive(Debug)] | ||
pub struct CloneAsDefault<T>(T); | ||
|
||
impl<T> CloneAsDefault<T> { | ||
pub fn new(value: T) -> Self { | ||
Self(value) | ||
} | ||
|
||
pub fn get(&self) -> &T { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<T: Default> Clone for CloneAsDefault<T> { | ||
fn clone(&self) -> Self { | ||
Self(T::default()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,6 @@ pub mod notification; | |
pub mod eel; | ||
|
||
pub mod bindings; | ||
|
||
mod clone_as_default; | ||
pub use clone_as_default::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,41 @@ | ||
use crate::base::CloneAsDefault; | ||
use crate::domain::FlexibleMidiSourceScript; | ||
use helgoboss_learn::{FeedbackValue, MidiSourceScript, MidiSourceScriptOutcome}; | ||
use std::borrow::Cow; | ||
|
||
pub type MidiSource = helgoboss_learn::MidiSource<FlexibleMidiSourceScript<'static>>; | ||
/// The helgoboss-learn MidiSource, integrated into ReaLearn. | ||
/// | ||
/// Now this needs some explanation: Why do we wrap the MIDI source script type with | ||
/// `CloneAsDefault<Option<...>>`!? Because the script is compiled and therefore doesn't suit itself | ||
/// to being cloned. But we need the MidiSource to be cloneable because we clone it whenever we | ||
/// sync the mapping(s) from the main processor to the real-time processor. Fortunately, the | ||
/// real-time processor doesn't use the compiled scripts anyway because those scripts are | ||
/// responsible for feedback only. | ||
/// | ||
/// Using `Arc` sounds like a good solution at first but it means that deallocation of the compiled | ||
/// script could be triggered *in the real-time thread*. Now, we have a custom global deallocator | ||
/// for automatically deferring deallocation if we are in a real-time thread. **But!** We use | ||
/// non-Rust script engines (EEL and Lua), so they are not aware of our global allocator ... and | ||
/// that means we would still get a real-time deallocation :/ Yes, we could handle this by manually | ||
/// sending the obsolete structs to a deallocation thread *before* the Rust wrappers around the | ||
/// script engines are even dropped (as we did before), but go there if the real-time processor | ||
/// doesn't even use the scripts. | ||
/// | ||
/// Introducing a custom method (not `clone`) would be quite much effort because we can't | ||
/// derive its usage. | ||
type ScriptType = CloneAsDefault<Option<FlexibleMidiSourceScript<'static>>>; | ||
|
||
pub type MidiSource = helgoboss_learn::MidiSource<ScriptType>; | ||
|
||
impl MidiSourceScript for ScriptType { | ||
fn execute( | ||
&self, | ||
input_value: FeedbackValue, | ||
) -> Result<MidiSourceScriptOutcome, Cow<'static, str>> { | ||
let script = self | ||
.get() | ||
.as_ref() | ||
.ok_or_else(|| Cow::Borrowed("script was removed on clone"))?; | ||
script.execute(input_value) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,32 @@ | ||
use crate::base::CloneAsDefault; | ||
use crate::domain::{ControlEventTimestamp, EelTransformation, LuaFeedbackScript}; | ||
use helgoboss_learn::{FeedbackScript, FeedbackScriptInput, FeedbackScriptOutput}; | ||
use std::borrow::Cow; | ||
use std::collections::HashSet; | ||
use std::error::Error; | ||
|
||
pub type Mode = | ||
helgoboss_learn::Mode<EelTransformation, LuaFeedbackScript<'static>, ControlEventTimestamp>; | ||
/// See [`crate::domain::MidiSource`] for an explanation of the feedback script wrapping. | ||
type FeedbackScriptType = CloneAsDefault<Option<LuaFeedbackScript<'static>>>; | ||
|
||
pub type Mode = helgoboss_learn::Mode<EelTransformation, FeedbackScriptType, ControlEventTimestamp>; | ||
|
||
impl FeedbackScriptType { | ||
fn get_script(&self) -> Result<&LuaFeedbackScript<'static>, Cow<'static, str>> { | ||
self.get() | ||
.as_ref() | ||
.ok_or_else(|| Cow::Borrowed("script was removed on clone")) | ||
} | ||
} | ||
|
||
impl FeedbackScript for FeedbackScriptType { | ||
fn feedback( | ||
&self, | ||
input: FeedbackScriptInput, | ||
) -> Result<FeedbackScriptOutput, Cow<'static, str>> { | ||
self.get_script()?.feedback(input) | ||
} | ||
|
||
fn used_props(&self) -> Result<HashSet<String>, Box<dyn Error>> { | ||
self.get_script()?.used_props() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters