[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Please unblock minirok 0.9.2-1



Hello, I've uploaded minirok 0.9.2 to fix an important bug (random
crashes) that I finally figured out. Please find explanations below if
you need them to review the diff.

Thanks.

########################

The problem was that a thread object was trying to communicate via Qt
signals with other objects, which is not supported in Qt 3. The solution
is not to let the thread to emit signals, and to emit them via an
intermediate object. So we move from:

    Thread -> Signal -> Objects

To

    Thread -> Event -> Intermediate object -> Signal -> Objects

The first part of the diff is the intermediate object:

--- a/minirok/engine.py
+++ b/minirok/engine.py
@@ -26,7 +26,7 @@
 
 ##
 
-class GStreamerEngine(qt.QObject, threading.Thread):
+class GStreamerEngine(threading.Thread):
     SINK = 'alsasink'
 
     PLUGINS = {
@@ -36,10 +36,40 @@ class GStreamerEngine(qt.QObject, threading.Thread):
             'vorbis': [ '.ogg' ],
     }
 
+    class QObject(qt.QObject):
+        # The Engine class it's a thread; in Qt3, sending signals accross
+        # threads is not supported, so the Engine will send custom events to
+        # this class, and this class will emit the signals instead. Other parts
+        # of the code will transparently connect() to this interface instead of
+        # directly to the Engine object.
+
+        STATUS_CHANGED = qt.QEvent.User + 1
+        END_OF_STREAM  = qt.QEvent.User + 2
+        SEEK_FINISHED  = qt.QEvent.User + 3
+
+        def customEvent(self, event):
+            t = event.type()
+            data = event.data()
+
+            if t == self.STATUS_CHANGED:
+                self.emit(qt.PYSIGNAL('status_changed'), (data,))
+
+            elif t == self.END_OF_STREAM:
+                self.emit(qt.PYSIGNAL('end_of_stream'), (data,))
+
+            elif t == self.SEEK_FINISHED:
+                self.emit(qt.PYSIGNAL('seek_finished'), ())
+
+            else:
+                minirok.logger.error('unknown custom event: %d', t)
+
+    ##

     def __init__(self):
-        qt.QObject.__init__(self)
         threading.Thread.__init__(self)
 
+        self._qobject = self.QObject()
         self.exit_engine = threading.Event() # join() sets this

########################

The second part is making the thread create events instead of emitting
signals:
 
     def _set_status(self, value):
         if value != self._status:
             self._status = value
-            self.emit(qt.PYSIGNAL('status_changed'), (value,))
+            event = qt.QCustomEvent(self.QObject.STATUS_CHANGED)
+            event.setData(value)
+            qt.QApplication.postEvent(self._qobject, event)
 
     status = property(lambda self: self._status, _set_status)
 
@@ -141,7 +178,9 @@
     def _message_eos(self, bus, message):
         self.bin.set_state(gst.STATE_NULL)
         self.status = State.STOPPED
-        self.emit(qt.PYSIGNAL('end_of_stream'), (self.uri,))
+        event = qt.QCustomEvent(self.QObject.END_OF_STREAM)
+        event.setData(self.uri)
+        qt.QApplication.postEvent(self._qobject, event)
 
     def _message_error(self, bus, message):
         error, debug_info = message.parse_error()
@@ -151,7 +190,8 @@
     def _message_async_done(self, bus, message):
         if self.seek_pending:
             self.seek_pending = False
-            self.emit(qt.PYSIGNAL('seek_finished'), ())
+            qt.QApplication.postEvent(self._qobject,
+                    qt.QCustomEvent(self.QObject.SEEK_FINISHED))
 
########################

And, finally, we make the rest of objects talk to _qobject instead of
the thread:
 
+    def connect(self, signal, slot):
+        qt.QObject.connect(self._qobject, signal, slot)
+
 
--- a/minirok/lastfm_submit.py
+++ b/minirok/lastfm_submit.py
@@ -50,8 +50,8 @@
         func(minirok.Globals.playlist, qt.PYSIGNAL('new_track'),
                 self.slot_new_track)
 
-        func(minirok.Globals.engine, qt.PYSIGNAL('status_changed'),
-                self.slot_engine_status_changed)
+        minirok.Globals.engine.connect(
+                qt.PYSIGNAL('status_changed'), self.slot_engine_status_changed)
 
         func(self.timer, qt.SIGNAL('timeout()'), self.slot_submit)
 
--- a/minirok/main_window.py
+++ b/minirok/main_window.py
@@ -216,8 +216,8 @@
         self.connect(minirok.Globals.playlist, qt.PYSIGNAL('new_track'),
                 self.slot_set_tooltip)
 
-        self.connect(minirok.Globals.engine, qt.PYSIGNAL('status_changed'),
-                self.slot_engine_status_changed)
+        minirok.Globals.engine.connect(
+                qt.PYSIGNAL('status_changed'), self.slot_engine_status_changed)
 
     def slot_set_tooltip(self):
         tags = minirok.Globals.playlist.currently_playing

--- a/minirok/playlist.py
+++ b/minirok/playlist.py
@@ -69,11 +69,11 @@
 
         self.connect(self, qt.PYSIGNAL('list_changed'), self.slot_list_changed)
 
-        self.connect(minirok.Globals.engine, qt.PYSIGNAL('status_changed'),
-                self.slot_engine_status_changed)
+        minirok.Globals.engine.connect(
+                qt.PYSIGNAL('status_changed'), self.slot_engine_status_changed)
 
-        self.connect(minirok.Globals.engine, qt.PYSIGNAL('end_of_stream'),
-                self.slot_engine_end_of_stream)
+        minirok.Globals.engine.connect(
+                qt.PYSIGNAL('end_of_stream'), self.slot_engine_end_of_stream)
 
         self.init_actions()
         self.apply_preferences()

--- a/minirok/statusbar.py
+++ b/minirok/statusbar.py
@@ -64,11 +64,11 @@
         self.connect(minirok.Globals.playlist, qt.PYSIGNAL('new_track'),
                 self.slot_start)
 
-        self.connect(minirok.Globals.engine, qt.PYSIGNAL('status_changed'),
-                self.slot_engine_status_changed)
+        minirok.Globals.engine.connect(
+                qt.PYSIGNAL('status_changed'), self.slot_engine_status_changed)
 
-        self.connect(minirok.Globals.engine, qt.PYSIGNAL('seek_finished'),
-                self.slot_engine_seek_finished)
+        minirok.Globals.engine.connect(
+                qt.PYSIGNAL('seek_finished'), self.slot_engine_seek_finished)
 
         # Actions
         self.action_next_repeat_mode = kdeui.KAction('Change repeat mode',

########################

I also including an embarrassingly simple one-line fix for #485981:

+        self.setDaemon(True)

########################

P.S.: Ouch, the src -> minirok symlink in the tarball makes "d" display
the diff twice.

-- 
Adeodato Simó                                     dato at net.com.org.es
Debian Developer                                  adeodato at debian.org
 
— Oh, George, you didn't jump into the river. How sensible of you! 
                -- Mrs Banks in “Mary Poppins”


Reply to: