musikr: fix inputstream memory leak

Apparently allocating the bytes on the JVM side of the taglib parser
will wind up leaking memory due to a bugged cache in ByteBuffer.

Instead, allocate the bytes in native, wrap it into a ByteBuffer, and
then pass it upwards into NativeInputStream. This seems to fix the
leak.
This commit is contained in:
Alexander Capehart 2025-01-30 09:29:26 -07:00
parent 7c8863bd3a
commit 7880c777ba
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 17 additions and 13 deletions

View file

@ -34,7 +34,7 @@ JInputStream::JInputStream(JNIEnv *env, jobject jInputStream) : env(env), jInput
jInputStreamNameMethod = jInputStreamClass.method("name", jInputStreamNameMethod = jInputStreamClass.method("name",
"()Ljava/lang/String;"); "()Ljava/lang/String;");
jInputStreamReadBlockMethod = jInputStreamClass.method("readBlock", jInputStreamReadBlockMethod = jInputStreamClass.method("readBlock",
"(J)[B"); "(Ljava/nio/ByteBuffer;)Z");
jInputStreamIsOpenMethod = jInputStreamClass.method("isOpen", "()Z"); jInputStreamIsOpenMethod = jInputStreamClass.method("isOpen", "()Z");
jInputStreamSeekFromBeginningMethod = jInputStreamClass.method( jInputStreamSeekFromBeginningMethod = jInputStreamClass.method(
"seekFromBeginning", "(J)Z"); "seekFromBeginning", "(J)Z");
@ -59,15 +59,20 @@ TagLib::FileName JInputStream::name() const {
} }
TagLib::ByteVector JInputStream::readBlock(size_t length) { TagLib::ByteVector JInputStream::readBlock(size_t length) {
// Do manual memory management here since we don't to avoid the added abstraction // We have to invert the buffer allocation here siits not a perfect system (vykeen instead of korvax0 but i warped all over the hub and i dont think its possible to find a "perfect" purple system like you would withnce the JVM ByteBuffer allocation system
// overhead of a smart JByteArrayRef. // uses a bugged caching mechanism that leaks memory if used in multithreaded contexts.
auto data = env->CallObjectMethod(jInputStream, jInputStreamReadBlockMethod, TagLib::ByteVector buf { static_cast<unsigned int>(length), 0 };
static_cast<jlong>(length)); jobject wrappedByteBuffer = env->NewDirectByteBuffer(buf.data(), buf.size());
if (data == nullptr) { if (wrappedByteBuffer == nullptr) {
throw std::runtime_error("Failed to wrap ByteBuffer");
}
JObjectRef byteBuffer = { env, wrappedByteBuffer };
jboolean result = env->CallBooleanMethod(jInputStream,
jInputStreamReadBlockMethod, *byteBuffer);
if (!result) {
throw std::runtime_error("Failed to read block, see logs"); throw std::runtime_error("Failed to read block, see logs");
} }
JByteArrayRef jByteArray = { env, reinterpret_cast<jbyteArray>(data) }; return buf;
return jByteArray.copy();
} }
void JInputStream::writeBlock(const TagLib::ByteVector &data) { void JInputStream::writeBlock(const TagLib::ByteVector &data) {

View file

@ -28,14 +28,13 @@ internal class NativeInputStream(private val deviceFile: DeviceFile, fis: FileIn
fun name() = requireNotNull(deviceFile.path.name) fun name() = requireNotNull(deviceFile.path.name)
fun readBlock(length: Long): ByteArray? { fun readBlock(buf: ByteBuffer): Boolean {
try { try {
val buffer = ByteBuffer.allocate(length.toInt()) channel.read(buf)
channel.read(buffer) return true
return buffer.array()
} catch (e: Exception) { } catch (e: Exception) {
Log.d("NativeInputStream", "Error reading block", e) Log.d("NativeInputStream", "Error reading block", e)
return null return false
} }
} }