Leaking GstCaps?

11 views
Skip to first unread message

JAmes Atwill

unread,
Jun 13, 2024, 3:48:55 AMJun 13
to gstreamer-java
Hello!

I'm on GStreamer 1.24.4, JNA 5.14.0, Kotlin 2.0, Corretto 20, macOS Sonoma 14.5.

I've fiddled with GStreamer in the past, but now I'm basically using it for the first time by writing a simple "Hello World" using playbin3 and running it with:

  GST_DEBUG=GST_TRACER:7
  GST_TRACERS=leaks

It looks effectively like:

fun helloWorld(file: String) {
  val playbin = Gst.parseLaunch("playbin3 uri=\"file://$file\"")

  val lambda = MESSAGE { _, msg -> msg.dispose() }
  playbin.bus.connect(lambda)
  Gst::invokeLater {
   playbin.play()
 }
  Thread.sleep(400)
  Gst::invokeLater {
   playbin.stop()
  }
  Thread.sleep(400)
  Gst::invokeLater {
   playbin.bus.disconnect(lambda)
   playbin.bus.dispose()
  }
  Gst::invokeLater {
   playbin.dispose()
  }
  Thread.sleep(2000)
  System.gc()
  Thread.sleep(2000)
}


After calling helloWorld(), I call System.gc() a few more times, then Thread.sleep for 10 seconds before manually calling Gst.deinit (just to get the leaks dump)

This seems to leak two objects:
GST_TRACER :0:: object-alive, type-name=(string)GstCaps, address=(gpointer)0x600000f81b80, description=(string)audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int)44100, channels=(int)2, channel-mask=(bitmask)0x0000000000000003, ref-count=(uint)11, trace=(string);

GST_TRACER :0:: object-alive, type-name=(string)GstCaps, address=(gpointer)0x600000f81e50, description=(string)audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int)44100, channels=(int)2, channel-mask=(bitmask)0x0000000000000003, ref-count=(uint)1, trace=(string);

If I run this in a loop, it leaks two GstCaps per run. The same things happens if I don't use invokeLater anywhere. If I process 10,000 files, I have 20,000 leaked GstCaps.

I'm not sure if it matters, but the files themselves are valid FLAC files.

What am I missing?

  JAmes


Neil C Smith

unread,
Jun 13, 2024, 4:28:56 AMJun 13
to gstream...@googlegroups.com
Hi,

On Thu, 13 Jun 2024 at 08:48, JAmes Atwill <jat...@linuxstuff.org> wrote:
> After calling helloWorld(), I call System.gc() a few more times, then Thread.sleep for 10 seconds before manually calling Gst.deinit (just to get the leaks dump)
...
> If I run this in a loop, it leaks two GstCaps per run. The same things happens if I don't use invokeLater anywhere. If I process 10,000 files, I have 20,000 leaked GstCaps.

Thanks for reminding me how much I dislike Kotlin! :-)

I'm sure I understand the code you've shared, but what is the context
in which it's called? When you mention a loop, where are you calling
Gst.deinit? That cannot be called more than once per process.

If you're doing manual GC calls in the test anyway, please try
removing all of the calls to dispose() and seeing if the leaks
continue. The usual reason that people see leaks like this is
incorrect eager disposal. Some objects are also handled together on
the Java side - eg. pipeline manages its own bus. I'm not sure what
happens on the GStreamer side if you dispose of that separately in
advance.

The Gst::executor is a scheduled executor, so you can get rid of all
of those sleeps. Also make sure that state transitions and associated
messages are complete.

Best wishes,

Neil

--
Neil C Smith
Codelerity Ltd.
www.codelerity.com

Codelerity Ltd. is a company registered in England and Wales
Registered company number : 12063669
Registered office address : 3rd Floor Suite, 207 Regent Street,
London, W1B 3HH, England

JAmes Atwill

unread,
Jun 14, 2024, 6:15:25 PMJun 14
to gstreamer-java
On Thursday, June 13, 2024 at 10:28:56 AM UTC+2 neil wrote:

Thanks for reminding me how much I dislike Kotlin! :-)

Is it too much "fun" for you? :^) I appreciate your quick response!

I've simplified the code, removed dispose() calls, call GStreamer code in a nested function and now handle the state changed transitions in a handler but still have the same GstCaps leaks.

fun helloWorld(filename: String) {

    fun playThenStop() {
        val finished = CountDownLatch(1)

        val playbin = Gst.parseLaunch("playbin3 name=\"tester\" uri=\"file://$filename\"")


        val lambda = MESSAGE { _, msg ->
            if (msg.type == STATE_CHANGED) {
                val stateChangedMsg = msg as StateChangedMessage
                if (stateChangedMsg.source.name == "tester") {

                    // on playing, stop.
                    // on stop (I think), unblock the finished mutex

                    if (stateChangedMsg.newState == PLAYING) {
                        Gst::invokeLater { playbin.stop() }
                    } else if (stateChangedMsg.oldState == PAUSED && stateChangedMsg.newState == READY) {
                        Gst::invokeLater { finished.countDown() }
                    }
                }
            }
        }

        playbin.bus.connect(lambda)
        println("Pressing Play")
        playbin.play()
        finished.await()
        playbin.bus.disconnect(lambda)
        println("Done")
    }

    playThenStop()
    for (i in 0L..10L) {
        print(".")
        System.gc()
        Thread.sleep(100 + (100 * i))
    }
    println("")
    Gst.deinit() // to make the leaks tracer print
    // System.exit(0)
}


The Gst::executor is a scheduled executor, so you can get rid of all of those sleeps.

I understand what this means, but I'm not savvy enough to know why it's important?
 
Also make sure that state transitions and associated messages are complete.

I think this new version handles that (I'm not sure what message I should be waiting for), but as-is, I still get leaked GstCaps:

 GST_TRACER :0:: object-alive, type-name=(string)GstCaps, address=(gpointer)0x600003f89770, description=(string)audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int)44100, channels=(int)2, channel-mask=(bitmask)0x0000000000000003, ref-count=(uint)1, trace=(string);
GST_TRACER :0:: object-alive, type-name=(string)GstCaps, address=(gpointer)0x600003f22990, description=(string)audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int)44100, channels=(int)2, channel-mask=(bitmask)0x0000000000000003, ref-count=(uint)11, trace=(string);

For each additional call to playThenStop(), I leak two more GstCaps. 

I'm not sure what the etiquette is here for long stack traces, so I ran with GST_LEAKS_TRACER_STACK_TRACE and put them in a gist here: https://gist.github.com/idcmp/ad77b4585685b763820b5e3d9a6b295b

I feel like I'm missing something horribly obvious - any help is much appreciated!

  JAmes

JAmes Atwill

unread,
Jun 16, 2024, 6:10:56 PM (13 days ago) Jun 16
to gstreamer-java
I've been poking at this to no avail - I also leak the same GstCaps if I re-use the pipeline, but just change the uri each time.

  JAmes

Neil C Smith

unread,
Jun 17, 2024, 12:35:15 PM (12 days ago) Jun 17
to gstream...@googlegroups.com
Hi,

On Sun, 16 Jun 2024 at 23:11, JAmes Atwill <jat...@linuxstuff.org> wrote:
> I've been poking at this to no avail - I also leak the same GstCaps if I re-use the pipeline, but just change the uri each time.

Sounding more and more like an upstream memory leak.

If you remove the bus listener do you still see it?

JAmes Atwill

unread,
Jun 19, 2024, 5:12:38 PM (10 days ago) Jun 19
to gstreamer-java
Okay, I've replaced my pipeline with:

val pipeline = Gst.parseLaunch("filesrc location=\"$filename\" ! decodebin ! fakesink sync=false name=sink")

and my leak goes away. I'm pretty new to all this, but I think it would fit your guess that there's a leak in playbin3?

  JAmes
Reply all
Reply to author
Forward
0 new messages