Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RtMidi cannot create ports past a certain limit #304

Open
sadguitarius opened this issue Nov 1, 2022 · 10 comments
Open

RtMidi cannot create ports past a certain limit #304

sadguitarius opened this issue Nov 1, 2022 · 10 comments

Comments

@sadguitarius
Copy link

sadguitarius commented Nov 1, 2022

I'm developing a Python script with the Mido library on a Raspberry Pi 4B and running into a problem where ALSA seq runs out of memory and is unable to continue creating ports past a certain number. In my case it's a maximum of 64 before ALSA gives up, but it's possible this number could be different on other systems.

So far I've traced the issue back to the RtMidi library, but it's entirely possible it goes deeper than that. I wrote a small C++ test program to illustrate:

#include <stdio.h>
#include <iostream>
#include "RtMidi.h"
#include <memory>
#include <string>

#define NUMOUTS 65

int main( int argc, char *argv[] ) {
    std::unique_ptr<RtMidiOut> midiouts[NUMOUTS];

    try {
        for (auto i = 0; i < NUMOUTS; ++i)
            midiouts[i] = std::make_unique<RtMidiOut>();
    }
    catch ( RtMidiError &error ) {
        error.printMessage();
    }

    for (auto i = 0; i < NUMOUTS; ++i) {
        std::cout << "Trying to open port " << i+1 << "\n";
        midiouts[i]->openVirtualPort("port" + std::to_string(i+1));
    }

    getchar();

    return 0;
}

With NUMOUTS set to 65, I get the error './test' terminated by signal SIGSEGV (Address boundary error). The error actually occurs when creating the RtMidiOut objects before the ports are opened.

The program will run successfully with 64 ports, but the strangest thing is that aconnect -l returns the error
ALSA lib seq_hw.c:466:(snd_seq_hw_open) open /dev/snd/seq failed: Cannot allocate memory, so it's like the entire ALSA midi system is choked by the memory error.

When I reduce NUMOUTS to 63, the program runs and aconnect -l successfully shows the newly created ports.

I do not currently have another working Linux platform to test this on. Are there any kernel settings or environment variables I should be aware of that might be causing this error? Would this an issue with the ALSA library itself?

@garyscavone
Copy link
Contributor

It seems odd that you would need to create so many ports. Rather, create one port and send messages to it. I don't think ALSA was designed with the idea that someone would open a new port every time a new message was ready to send.

@sadguitarius
Copy link
Author

I'm trying to create a MIDI patchbay using the Mido and rtpmidid libraries in order to route messages from multiple hardware sequencers. In order to do that, I need separate ports for each device (I have a large MIDI studio with >40 physical ports). Sorry if I'm misunderstanding you, but if I only create one port, wouldn't this just merge all incoming messages so there would be no way to distinguish what source they're coming from?

@garyscavone
Copy link
Contributor

Well, that seems like an unusual configuration (having so many physical MIDI ports). That said, I don't think there are any particular issues in the RtMidi code that would limit the number of instances you decide to create.

@sadguitarius
Copy link
Author

Agreed it is unusual, but I was hoping there would be a way to accomplish it with preexisting tools. It does seem like it might be doable if the port creation didn't happen on a separate client each time a new one was created. I'll keep trying...

@insolace
Copy link
Contributor

insolace commented Nov 2, 2022 via email

@sadguitarius
Copy link
Author

Ok, I think most of the issue is that while ALSA allows multiple ports under one sequencer client, RtMidi does not. This means that while one RtMidiIn or RtMidiOut object should be able to act as a container for multiple ports, it currently cannot. Is there a reason for this? It seems that instead of the connected_ flag attached to the client object, there could be a container variable to keep track of all currently open ports. I'm totally willing to try forking the project and mocking this up, but is there a reason why only a single connection to a client is allowed in the first place?

@insolace Since I'm getting the error before actually opening the ports, the issue is not with the ports themselves but rather that ALSA for some reason does not allow the creation of a large number of client objects. If RtMidi could avoid having to create so many clients and instead nest the ports under a single client, this would hopefully avoid that problem.

@garyscavone
Copy link
Contributor

If a single instance of RtMidi could be used to open multiple ports at the same time, then there would have to be a mechanism for specifying to which port a message is sent or from which a message is received. Given that it was unexpected, when designing the RtMidi API, that someone would want to have many ports open at the same time, I decided to make life simple and go with a single port per instance. So, if a single instance is to support many ports, there would have to be some significant API changes.

@sadguitarius
Copy link
Author

Got it. I was thinking about this, and a possible solution would be for there to be separate RtMidi objects per port, but if they were created with the same clientName instance variable, they would reference a single client internally. I don't think this would necessitate any API changes. Does this sound like it could be a workable solution? At the very least, the output of aconnect -l would be cleaned up considerably and note be full of identically named clients.

I'll have to look at the code a bit more to see how much this could potentially mess up. One thing I'm not sure of is how much is automatically managed by the system when ports are added and removed on the client and what happens to the port numbers. There may at least need to be some kind of function to refresh the client and look for changes, or maybe this could be rolled into the openPort function somehow.

@keinstein
Copy link

It seems to me that there is a limit of clients for ALSA. Since RtMidi opens a new client for each port this limit also hits RtMidi. ALSA allows several ports per client so this limit could be pushed somehow. However, this has some implications on the architecture of RtMidi. The main reason to use this design is thread safety, as ALSA is only thread safe if different threads use different clients. If RtMidi would use multiport clients, it would be necessary to use spinlocks in order to garantee thread safety.
This is in principle possible.

The problem of port association is not really a problem since ALSA provides the source and destination client and port of each ALSA message to the receiving ALSA client.

I played with both parts in https://github.com/keinstein/rtmidi/blob/code-deduplicate/src/ALSA.cpp .

@sadguitarius
Copy link
Author

Thanks, this is very helpful! I'll take a look at your code. Gonna take some time to wrap my head around this.

On a side note, I noticed that running the ALSA library function snd_seq_system_info_get_clients on a seq object gives the output 192. Unless I'm misunderstanding something, this should actually be the number of clients that is allowed to exist at once on any given system. Therefore, this really does seem like a bug in ALSA...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants