Beirdo
Beirdo

Reputation: 424

Stray threads in Python gstreamer use

I have noticed that after I use gstreamer via python (using python-gst-1.0 deb package version 1.2.0-1 from Ubuntu 14.04), I seem to have a stray thread for every encoder run. I have the gstreamer interface within a module I wrote, and it does the gobject.mainloop within the module, and does mainloop.quit(), so I wouldn't expect that it's the mainloop itself.

After a few runs, threading.enumerate() is showing:

[<_MainThread(MainThread, started 140079923849024)>,
 <_DummyThread(Dummy-1, started daemon 140079768815360)>,
 <_DummyThread(Dummy-3, started daemon 140079785338624)>,
 <_DummyThread(Dummy-4, started daemon 140079418832640)>,
 <_DummyThread(Dummy-2, started daemon 140079802386176)>]

Thankfully, they are started with daemon, so the program will exit, but I'm at a loss as to how to clean these up. They are affecting the possibility to use Ctrl-C to exit the script, as the KeyboardInterrupt is not always seeming to come to the MainThread. I end my run loop with:

try:
    time.sleep(899.0)
except KeyboardInterrupt:
    pass

time.sleep(1.0)

This should allow me to abort the loop timeout by hitting Ctrl-C twice rapidly as the first time catches the try/except, and the second one has no handler on the 1s sleep, and thus quits. However, with the stray threads, the second Ctrl-C is somehow never seen at this level, so I need to Ctrl-Z to get to the shell and forcefully kill the script. I don't like it.

Anyone have any idea what this stray thread would be, and how to have it cooperate and die for me? I'm about to break gdb out on a running process to determine what it might be.

The class code (stripped down to remove irrelevant parts):

class GstEncoder:
    def __init__(self, metadata, mediainfo):
        self.error = None

        # used for controlling logic which I removed for clarity
        self.metadata = metadata
        self.mediainfo = mediainfo

        # Create a pipeline in self.pipeline
        self.setupPipeline()

        # Put in the MainLoop
        self.mainloop = GObject.MainLoop()
        self.context = self.mainloop.get_context()
        self.abort = False

    def __del__(self):
        logger.info("Dying gasp!")
        if self.mainloop.is_running():
            self.mainloop.quit()
        self.pipeline.unref()

    def start(self):
        # Set in playing mode
        self.pipeline.set_state(Gst.State.PLAYING)

        # actually only used in some situations, removed the controlling logic for clarity
        GObject.timeout_add_seconds(900, self.timedOut)
        GObject.timeout_add_seconds(30, self.progressReport)

        try:
            self.abort = False
            self.mainloop.run()
        except KeyboardInterrupt:
            logger.warning("Aborted by Ctrl-C")
            self.abort = True
            self.mainloop.quit()
            raise KeyboardInterrupt

        # Stop the pipeline
        self.pipeline.set_state(Gst.State.NULL)
        return self.error

    def progressReport(self):
        position = self.pipeline.query_position(Gst.Format.TIME)[1]
        duration = self.pipeline.query_duration(Gst.Format.TIME)[1]
        if self.abort:
            return False
        percentage = 0.0 if duration == 0 \
                         else float(position) / float(duration) * 100.0
        logger.info("Progress: %s / %s  (%.2f%%)" % (Gst.TIME_ARGS(position),
                Gst.TIME_ARGS(duration), percentage))
        return True

    def timedOut(self):
        if self.abort:
            return False
        self.error = "Aborted by watchdog timer"
        logger.warning(self.error)
        self.abort = True
        self.mainloop.quit()
        return False

This is instantiated as:

err = None
# Filename, etc is in metadata
encoder = GstEncoder(metadata, mediainfo)
if encoder.error:
    err = encoder.error
if not err:
    err = encoder.start()

if err:
    logger.error(err)

encoder = None
print threading.enumerate()

An example pipeline can be seen at: https://s3.amazonaws.com/beirdo-share/before.png

Upvotes: 1

Views: 2399

Answers (1)

Matthew Marshall
Matthew Marshall

Reputation: 5883

I've run into an issue that sounds the same: Ctrl-C triggers a KeyboardInterrupt but the process doesn't exit due to non-daemon gstreamer threads. The fix was to call GObject.threads_init() once on application startup.

Upvotes: 1

Related Questions