chris-zen / coremidi

CoreMIDI library for Rust
https://chris-zen.github.io/coremidi/coremidi/
MIT License
75 stars 20 forks source link

Client::new() returns error -50 after 6 calls #32

Open sourcebox opened 3 years ago

sourcebox commented 3 years ago

My application uses the midir crate that uses coremidi as a dependency. The original issue is tracked here Boddlnagg/midir#86

The reason for continously creating a client for scanning the ports because the cross-platform midir crate does not support change notifications yet.

While there is a workaround for the problem, it still would be nice to address the issue if possible. I would expect that the acquired client resource is closed at the end of each loop iteration as the variable goes out of scope.

Demo code:

use coremidi::Client;

fn main() {
    loop {
        let client = Client::new("test midi client");

        match client {
            Ok(client) => {
                println!("{:?}", &client);
            }
            Err(error) => {
                println!("{:?}", error);
            }
        }

        std::thread::sleep(std::time::Duration::from_millis(1000));
    }
}
chris-zen commented 3 years ago

Hi @sourcebox, thanks for the report.

I've been investigating it a bit. Apparently the Drop for Client is working fine and the client is disposed at CoreMIDI level.

With this code:

impl Drop for Client {
    fn drop(&mut self) {
        println!("Dropping client ...");
        let status = unsafe { MIDIClientDispose(self.object.0) };
        println!("{}", status);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let mut counter = 0;
        loop {
            let client = Client::new("test midi client");
            counter += 1;

            match client.as_ref() {
                Ok(client) => {
                    println!("{}, {:?}", counter, client);
                }
                Err(error) => {
                    println!("{:?}", error);
                    break;
                }
            }

            std::thread::sleep(std::time::Duration::from_millis(1000));
        }
    }
}

I see this:

1, Client { object: Object(bc29041), callback: BoxedCallback(0x0) }
Dropping client ...
0
2, Client { object: Object(bc29042), callback: BoxedCallback(0x0) }
Dropping client ...
0
3, Client { object: Object(bc29043), callback: BoxedCallback(0x0) }
Dropping client ...
0
4, Client { object: Object(bc29044), callback: BoxedCallback(0x0) }
Dropping client ...
0
5, Client { object: Object(bc29045), callback: BoxedCallback(0x0) }
Dropping client ...
0
6, Client { object: Object(bc29046), callback: BoxedCallback(0x0) }
Dropping client ...
0
7, Client { object: Object(bc29047), callback: BoxedCallback(0x0) }
Dropping client ...
0
8, Client { object: Object(bc29048), callback: BoxedCallback(0x0) }
Dropping client ...
0
-50

But reading the docs I just found a possible cause for the problem that it would be out of my control:

https://developer.apple.com/documentation/coremidi/1495335-midiclientdispose#discussion

Don’t explicitly dispose of your client; the system automatically disposes all clients when an app terminates.
However, if you call this method to dispose the last or only client owned by an app,
the MIDI server may exit if there are no other clients remaining in the system.
If this occurs, all subsequent calls by your app to MIDIClientCreate(_:_:_:_:) and MIDIClientCreateWithBlock(_:_:_:) fail.

So apparently, clients are not supposed to be created and destroyed that frequently, but instead live for the whole lifecycle of your app.