[Kde-bindings] Using threads with QtRuby

Richard Dale rdale at foton.es
Fri Feb 5 20:22:24 UTC 2010


On Friday 05 February 2010 07:37:16 pm Uriel wrote:
> > But you only get a log of vitual method callbacks if you want them.
> > The are
> > various levels and types of debug output and it is quite easy to
> > customize.
> > Maybe it is a bit over complicated but you will only get virtual method
> > callbacks logged if one of the things you pass to
> > Qt::Internal.setDebug() is
> > QTDB_VIRTUAL.
> 
> Yes, I know about different debug levels, thank you. But sometimes I
> become paranoid and explicitly set level to QTDB_ALL complicating all
> the thing around myself. :) So, may be it's not a really big deal.
OK - I wasn't sure over verbose debugging had to do with Ruby threads, but it 
doesn't do any harm to explain QtRuby's (over?) complicated debugging options 
to people who haven't worked it all out yet.

> Here is the small patch for qtruby4.rb in trunk with comments. I've
> tested it on different amount of threads and everything seems to be ok.
> 
> --- qtruby4.rb    2010-02-06 00:20:35.056527428 +0500
> +++ qtruby4.rb    2010-02-06 00:31:59.293534007 +0500
> @@ -43,6 +43,17 @@
>           @@debug_level
>       end
> 
> +    # We're causing main thread to sleep for some really small amount
> of time time to force Ruby VM do the scheduling
> +    @@ruby_thread_sleep_time = 1e-9
> +
> +    def Qt.ruby_thread_sleep_time
> +        @@ruby_thread_sleep_time
> +    end
> +
> +    def Qt.ruby_thread_sleep_time=(time)
> +        @@ruby_thread_sleep_time = time
> +    end
> +
>       module Internal
>           #
>           # From the enum MethodFlags in
>  qt-copy/src/tools/moc/generator.cpp @@ -454,6 +465,26 @@
>       end
> 
>       class Application < Qt::Base
> +        private
> +        def enable_ruby_threads
> +          # Start timer if only it hasn't been started yet
> +          return unless @ruby_threads_timer_id == -1
> +
> +          @ruby_threads_timer_id = startTimer 0
> +        end
> +
> +        def disable_ruby_threads
> +          # Stop timer only if we have main thread and just one custom
> which will be stopped right after this call
> +          return unless Thread.list.count == 2
> +
> +          killTimer @ruby_threads_timer_id
> +          @ruby_threads_timer_id = -1
> +        end
> +        protected
> +        def timerEvent(ev)
> +          sleep Qt::ruby_thread_sleep_time
> +        end
> +        public
>           def initialize(*args)
>               if args.length == 1 && args[0].kind_of?(Array)
>                   super(args.length + 1, [$0] + args[0])
> @@ -466,6 +497,8 @@
>           # Otherwise, rb_gc_call_finalizer_at_exit() can delete
>           # stuff that Qt::Application still needs for its cleanup.
>           def exec
> +            @ruby_threads_timer_id = -1
> +
>               method_missing(:exec)
>               self.dispose
>               Qt::Internal.application_terminated = true
> @@ -3149,4 +3182,32 @@
>       end
>   end
> 
> +# We're doing some trick here by redefining all the methods for new
> thread creation.
> +# Then we adding custom function call to the user's block passed to one
> of these functions.
> +class Thread
> +    class << self
> +        alias :__oldNew :new
> +        alias :__oldStart :start
> +        alias :__oldFork :fork
> +    end
> +
> +    def self.decorate_thread(old_method, *args, &block)
> +        Qt::Application.instance.send :enable_ruby_threads
> +        send old_method, *args, &proc{ block.call;
> Qt::Application.instance.send :disable_ruby_threads }
> +    end
> +    private_class_method(:decorate_thread)
> +
> +    def self.new(*args, &block)
> +        decorate_thread :__oldNew, *args, &block
> +    end
> +
> +    def self.start(*args, &block)
> +        decorate_thread :__oldStart, *args, &block
> +    end
> +
> +    def self.fork(*args, &block)
> +        decorate_thread :__oldFork, *args, &block
> +    end
> +end
> +
>   # kate: space-indent false;
Yes, this is really good, I think, but it is probably going to be Ruby 1.8.x 
specific. I have no personal fear of monkey patching something as major as the 
Ruby thread api, but it is easy to see why some people could get nervous.. 

For Ruby 1.9.x instead we will probably need to change the QtRuby code to 
acquire some sort of 'Big Lock' before each method invocation or callback into 
the Ruby runtime like a slot invocation or virtual method callback. So we will 
probably need to make the code above conditional on a Ruby 1.8.x runtime being 
found.

I am not sure it is a good idea to unconditionally override 
Qt::Application.exec() as it involves a performance penalty, which is why I 
suggested having a different Qt::Application.exec_with_threads() (or similarly 
named) method. A large percentage of QtRuby support problems have been to do 
with combining Ruby threads with the Qt event loop, and so it looks to me as 
though your solution would be pretty useful to a lot of people. We just need 
to think through all the issues a bit carefully. As KDE 4.4 is frozen and KDE 
4.5 isn't for six months I think there is time to sort it out.

-- Richard



More information about the Kde-bindings mailing list