Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ For details and minor changes, please see the [version control log
messages](https://github.com/SpotlightKid/python-rtmidi/commits/master).


## Unreleased

Features:

- Support free-threaded Python builds (3.13t and later): importing `rtmidi`
no longer re-enables the GIL. Calls on one `MidiIn` / `MidiOut` instance
are serialized, on every build; see "Threads" in the usage docs.
- `delete()` may be called while another thread is in a call on the same
instance; the C++ instance is destroyed when that call returns. Calling
it from the instance's own input callback raises `InvalidUseError`.

Changes:

- While `MidiIn.close_port()` waits for the input thread, `get_message()`
from another thread returns `None` and other calls on the instance raise
`InvalidUseError`.

Fixes:

- Deleting the last reference to a `MidiIn` / `MidiOut` instance never
freed the C++ instance (since 1.4.1), so its MIDI client and ports stayed
open, and a `MidiIn` input thread kept running and could call a freed
callback when a message arrived. If the last reference goes away in the
instance's own input callback, the C++ instance is destroyed on another
thread.
- `MidiIn.close_port()` (and deleting a `MidiIn`) could deadlock when a
message arrived for the input callback at the same moment.
- `MidiIn` / `MidiOut` instances no longer form a reference cycle with
their error callback, so `del` frees them without the garbage collector.
- Methods called after `delete()` raise `InvalidUseError` instead of
crashing; `close_port()` and `delete()` do nothing then. Calls that were
in progress in other threads no longer use the freed C++ instance.
- Replacing a callback with `set_callback()` while messages arrive could
call a freed callback.

Project infrastructure:

- Building from the Cython source requires Cython >= 3.1.


## 1.6.0 (2025-04-18)

Project infrastructure:
Expand Down
2 changes: 1 addition & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ build tools are installed.

If you are installing from a Git repository checkout, since this does not
include the C++ module source code pre-compiled from the Cython source, you'll
also need to install Cython >= 0.29, either via pip or from its Git repository.
also need to install Cython >= 3.1, either via pip or from its Git repository.
Using virtualenv / virtualenvwrapper is strongly recommended in this scenario:

Make a virtual environment:
Expand Down
34 changes: 34 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,40 @@ available MIDI output port and send a middle C note on MIDI channel 1:
last message is sent and before the output port is closed, otherwise
the message may be lost.

Threads
=======

Calls on one ``MidiIn`` or ``MidiOut`` instance are serialized, so several
threads can share an instance, for example to send messages through one
``MidiOut``. On a free-threaded Python build (3.13t and later),
**python-rtmidi** does not re-enable the GIL, and calls on different instances
run in parallel.

The callback set with ``MidiIn.set_callback`` runs on a thread started by the
MIDI backend, not on one of your threads:

* A new callback set with ``set_callback`` gets the next message. But a
message that is being delivered while ``cancel_callback`` or ``close_port``
is called may still reach the old callback after that call returns.
* ``MidiIn.close_port`` and ``MidiIn.delete`` wait for that thread to stop.
``close_port`` may be called from inside the callback; ``delete`` may not
(``InvalidUseError``). While ``close_port`` waits, ``get_message`` returns
``None`` and most other calls on the same instance from other threads raise
``InvalidUseError``.

``delete`` may be called while another thread is in a call on the same
instance: the C++ instance is then destroyed when that call returns. After
``delete``, methods raise ``InvalidUseError``, except ``get_current_api`` and
``is_port_open``, which still work, and ``close_port`` and ``delete``, which
do nothing.

Reference cycles through an instance's callbacks (for example a callback whose
``data`` is the ``MidiIn`` itself) are collected by the garbage collector like
any other; a message being delivered at that moment is dropped. If the last
reference to a ``MidiIn`` goes away in its own input callback, the C++
instance is destroyed on another thread, which waits for the callback to
return.

More usage examples can be found in the examples_ and tests_ directories
of the source repository.

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
[build-system]
build-backend = "mesonpy"
requires = [
"cython",
# 3.1: freethreading_compatible, critical_section and pymutex
"cython>=3.1",
"wheel",
"meson-python",
"ninja"
Expand Down Expand Up @@ -33,6 +34,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Free Threading :: 2 - Beta",
"Topic :: Multimedia :: Sound/Audio :: MIDI",
"Topic :: Software Development :: Libraries :: Python Modules",
]
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.in
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
coverage
Cython
Cython>=3.1
flake8
myst-parser
pip-tools
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ colorama==0.4.6
# via tox
coverage==7.6.11
# via -r requirements-dev.in
cython==3.0.11
cython==3.1.0
# via -r requirements-dev.in
distlib==0.3.9
# via virtualenv
Expand Down
Loading