southbridgeio / tdlib-ruby

Ruby bindings and client for TDLib
MIT License
94 stars 45 forks source link

Handling updates in background #19

Open yi-jiayu opened 5 years ago

yi-jiayu commented 5 years ago

Is there a way to register handlers and then loop forever while processing them?

I tried this naive way of doing so:

  while client.alive?
  end

However this pegs the CPU at 100%.

Here's something I've tried instead:

diff --git a/lib/tdlib/client.rb b/lib/tdlib/client.rb
index 54356a9..3d8279f 100644
--- a/lib/tdlib/client.rb
+++ b/lib/tdlib/client.rb
@@ -49,10 +49,14 @@ class TD::Client
       end
     end

-    @update_manager.run
+    @thread = @update_manager.run
     ready
   end

+  def wait
+    @thread.join if @thread
+  end
+
   # Sends asynchronous request to the TDLib client and returns Promise object
   # @see TD::ClientMethods List of available queries as methods
   # @see https://github.com/ruby-concurrency/concurrent-ruby/blob/master/docs-source/promises.in.md

This assigns the thread returned by UpdateManager#run to an instance variable so that it can be joined in the Client#wait method.

Then I create some signal handlers to call Client#dispose on interrupt (which causes the thread started by UpdateManager to return), and wait on the client:

diff --git a/README.md b/README.md
index 5e1ba88..b3a162c 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,7 @@ begin
               nil
             end
   end
-  
+
   client.connect

   loop do
@@ -99,6 +99,25 @@ begin
     sleep 0.1
   end

+  client.on(TD::Types::Update) do |update|
+      puts 'Got update:'
+      puts update
+    end
+
+    Signal.trap("INT") {
+      Thread.start do
+        client.dispose
+      end
+    }
+
+    Signal.trap("TERM") {
+      Thread.start do
+        client.dispose
+      end
+    }
+
+    client.wait
+
 ensure
   client.dispose
 end

This processes updates continuously without spinning.

vladislav-yashin commented 5 years ago

Would this work for you?

while client.alive?
  sleep 1
end

sleep will put the current running thread to sleep and schedule the execution of another thread.

But I agree that we should think about implementing the built-in way of doing this. Joining UpdateManager thread is a good idea, but I will think a bit more about the public API.

yi-jiayu commented 5 years ago

Cool, that works.

Did a simple comparison between the three methods:

Modified Client:

$ time timeout 5 bundle exec ruby example.rb 

real    0m5.020s
user    0m0.496s
sys 0m0.211s

While loop without sleep:

$ time timeout 5 bundle exec ruby example.rb 

real    0m5.017s
user    0m4.720s
sys 0m0.182s

While loop with sleep 1:

$ time timeout 5 bundle exec ruby example.rb 

real    0m5.025s
user    0m0.454s
sys 0m0.175s

The while loop without sleep spends almost all of its time in user mode, while both joining the UpdateManager thread and a while loop with sleep 1 perform similarly.