Non-Blocking Settimeout in JavaScript VS Sleep in Ruby

non-blocking setTimeout in javascript vs sleep in ruby

The setTimeout function is nothing at all like sleep since the former is asynchronous and the latter is synchronous.

The Ruby sleep method, like its POSIX counterpart, halts execution of the script. The setTimer function in JavaScript triggers a callback at a future time.

If you want to trigger an asynchronous callback, you might need something like EventMachine to run an event loop for you.

Non-blocking Timed event in Ruby like JavaScript setTimeout

Looks overkill for this task, but you can use delayed_job to run a task at a future time asynchronously.

  def deactivate
puts 'deactivate'
active = false
end

active = true
handle_asynchronously :deactivate, :run_at => Proc.new { 2.minutes.from_now }

JS-style async/non-blocking callback execution with Ruby, without heavy machinery like threads?

Okay, after some fiddling with threads and studying contributions by apeiros and asQuirreL, i came up with a solution that suits me.

I'll show sample usage first, source code in the end.

Example 1: simple non-blocking execution

First, a JS example that i'm trying to mimic:

setTimeout( function() {
console.log("world");
}, 0);

console.log("hello");

// 'Will print "hello" first, then "world"'.

Here's how i can do it with my tiny Ruby library:

# You wrap all your code into this...
Branch.new do

# ...and you gain access to the `branch` method that accepts a block.
# This block runs non-blockingly, just like in JS `setTimeout(callback, 0)`.
branch { puts "world!" }

print "Hello, "

end

# Will print "Hello, world!"

Note how you don't have to take care of creating threads, waiting for them to finish. The only scaffolding required is the Branch.new { ... } wrapper.

Example 2: synchronizing threads with a mutex

Now we'll assume that we're working with some input and output shared among threads.

JS code i'm trying to reproduce with Ruby:

var
results = [],
rounds = 5;

for (var i = 1; i <= rounds; i++) {

console.log("Starting thread #" + i + ".");

// "Creating local scope"
(function(local_i) {
setTimeout( function() {

// "Assuming there's a time-consuming operation here."

results.push(local_i);
console.log("Thread #" + local_i + " has finished.");

if (results.length === rounds)
console.log("All " + rounds + " threads have completed! Bye!");

}, 0);
})(i);
}

console.log("All threads started!");

This code produces the following output:

Starting thread #1.
Starting thread #2.
Starting thread #3.
Starting thread #4.
Starting thread #5.
All threads started!
Thread #5 has finished.
Thread #4 has finished.
Thread #3 has finished.
Thread #2 has finished.
Thread #1 has finished.
All 5 threads have completed! Bye!

Notice that the callbacks finish in reverse order.

We're also gonna assume that working the results array may produce a race condition. In JS this is never an issue, but in multithreaded Ruby this has to be addressed with a mutex.

Ruby equivalent of the above:

Branch.new 1 do

# Setting up an array to be filled with that many values.
results = []
rounds = 5

# Running `branch` N times:
1.upto(rounds) do |item|

puts "Starting thread ##{item}."

# The block passed to `branch` accepts a hash with mutexes
# that you can use to synchronize threads.
branch do |mutexes|

# This imitates that the callback may take time to complete.
# Threads will finish in reverse order.
sleep (6.0 - item) / 10

# When you need a mutex, you simply request one from the hash.
# For each unique key, a new mutex will be created lazily.
mutexes[:array_and_output].synchronize do
puts "Thread ##{item} has finished!"
results.push item

if results.size == rounds
puts "All #{rounds} threads have completed! Bye!"
end
end
end
end

puts "All threads started."
end

puts "All threads finished!"

Note how you don't have to take care of creating threads, waiting for them to finish, creating mutexes and passing them into the block.

Example 3: delaying execution of the block

If you need the delay feature of setTimeout, you can do it like this.

JS:

setTimeout(function(){ console.log('Foo'); }, 2000);

Ruby:

branch(2) { puts 'Foo' }

Example 4: waiting for all threads to finish

With JS, there's no simple way to have the script wait for all threads to finish. You'll need an await/defer library for that.

But in Ruby it's possible, and Branch makes it even simpler. If you write code after the Branch.new{} wrapper, it will be executed after all branches within the wrapper have been completed. You don't need to manually ensure that all threads have finished, Branch does that for you.

Branch.new do
branch { sleep 10 }
branch { sleep 5 }

# This will be printed immediately
puts "All threads started!"
end

# This will be printed after 10 seconds (the duration of the slowest branch).
puts "All threads finished!"

Sequential Branch.new{} wrappers will be executed sequentially.

Source

# (c) lolmaus (Andrey Mikhaylov), 2014
# MIT license http://choosealicense.com/licenses/mit/

class Branch
def initialize(mutexes = 0, &block)
@threads = []
@mutexes = Hash.new { |hash, key| hash[key] = Mutex.new }

# Executing the passed block within the context
# of this class' instance.
instance_eval &block

# Waiting for all threads to finish
@threads.each { |thr| thr.join }
end

# This method will be available within a block
# passed to `Branch.new`.
def branch(delay = false, &block)

# Starting a new thread
@threads << Thread.new do

# Implementing the timeout functionality
sleep delay if delay.is_a? Numeric

# Executing the block passed to `branch`,
# providing mutexes into the block.
block.call @mutexes
end
end
end

Asynchronous IO server : Thin(Ruby) and Node.js. Any difference?

Sinatra / Thin

Thin will be started in threaded mode,
if it is started by Sinatra (i.e. with ruby asynchtest.rb)

This means that your assumptions are correct; when reaching sleep 2 , the server is able to serve another request at the same time , but on another thread.

I would to show this behavior with a simple test:

#asynchtest.rb
require 'sinatra'
require 'thin'
set :server, %w[thin]

get '/test' do
puts "[#{Time.now.strftime("%H:%M:%S")}] logging /test starts on thread_id:#{Thread.current.object_id} \n"
sleep 10
"[#{Time.now.strftime("%H:%M:%S")}] success - id:#{Thread.current.object_id} \n"
end

let's test it by starting three concurrent http requests ( in here timestamp and thread-id are relevant parts to observe):

Sample Image
The test demonstrate that we got three different thread ( one for each cuncurrent request ), namely:

  • 70098572502680
  • 70098572602260
  • 70098572485180

each of them starts concurrently ( the starts is pretty immediate as we can see from the execution of the puts statement ) , then waits (sleeps) ten seconds and after that time flush the response to the client (to the curl process).

deeper understanding

Quoting wikipedia - Asynchronous_I/O:
In computer science, asynchronous I/O, or non-blocking I/O is a form of input/output processing that permits
other processing to continue before the transmission has finished .

The above test (Sinatra/thin) actually demonstrate that it's possible to start a first request from curl ( the client ) to thin ( the server)
and, before we get the response from the first (before the transmission has finished) it's possible to start a second and a third
request and these lasts requests aren't queued but starts concurrently the first one or in other words: permits other processing to continue*

Basically this is a confirmation of the @Holger just's comment: sleep blocks the current thread, but not the whole process. That said, in thin, most stuff is handled in the main reactor thread which thus works similar to the one thread available in node.js: if you block it, nothing else scheduled in this thread will run. In thin/eventmachine, you can however defer stuff to other threads.

This linked answers have more details: "is-sinatra-multi-threaded and Single thread still handles concurrency request?

Node.js

To compare the behavoir of the two platform let's run an equivalent asynchtest.js on node.js; as we do in asynchtest.rb to undertand what happen we add a log line when processing starts;
here the code of asynchtest.rb:

var express = require('express');
var app = express();

app.get('/test', function(req, res){
console.log("[" + getTime() + "] logging /test starts\n");
setTimeout(function(){
console.log("sleep doen't block, and now return");
res.send('[' + getTime() + '] success \n');
},10000);
});

var server = app.listen(3000,function(){
console.log("listening on port %d", server.address().port);
});

Let's starts three concurrent requests in nodejs and observe the same behavoir:

Sample Image

of course very similar to what we saw in the previous case.

This response doesn't claim to be exhaustive on the subject which is very complex and deserves further study and specific evidence before drawing conclusions for their own purposes.

Difference between wait() vs sleep() in Java

A wait can be "woken up" by another thread calling notify on the monitor which is being waited on whereas a sleep cannot. Also a wait (and notify) must happen in a block synchronized on the monitor object whereas sleep does not:

Object mon = ...;
synchronized (mon) {
mon.wait();
}

At this point the currently executing thread waits and releases the monitor. Another thread may do

synchronized (mon) { mon.notify(); }

(on the same mon object) and the first thread (assuming it is the only thread waiting on the monitor) will wake up.

You can also call notifyAll if more than one thread is waiting on the monitor – this will wake all of them up. However, only one of the threads will be able to grab the monitor (remember that the wait is in a synchronized block) and carry on – the others will then be blocked until they can acquire the monitor's lock.

Another point is that you call wait on Object itself (i.e. you wait on an object's monitor) whereas you call sleep on Thread.

Yet another point is that you can get spurious wakeups from wait (i.e. the thread which is waiting resumes for no apparent reason). You should always wait whilst spinning on some condition as follows:

synchronized {
while (!condition) { mon.wait(); }
}

Ruby Timeout Module - Timeout doesn't execute

The problem is the way MRI (Matz's Ruby Implementation) thread scheduling works. MRI uses a GIL (Global Interpreter Lock), which in practice means only one thread is truly running at a time.

There are some exception, but for the majority of the time there is only one thread executing Ruby code at any one time.

Normally you do not notice this, even during heavy computations that consume 100% CPU, because the MRI keeps time-slicing the threads at regular intervals so that each thread gets a turn to run.

However there's one exception where time-slicing isn't active and that's when a Ruby thread is executing native C-code instead of Ruby code.

Now it so happens that Array#combination is implemented in pure C:

[1] pry(main)> show-source Array#combination
From: array.c (C Method):

static VALUE
rb_ary_combination(VALUE ary, VALUE num)
{
...
}

When we combine this knowledge with how Timeout.timeout is implemented we can start to get a clue of what is happening:

[7] pry(main)> show-source Timeout#timeout
From: /opt/ruby21/lib/ruby/2.1.0/timeout.rb @ line 75:

75: def timeout(sec, klass = nil) #:yield: +sec+
76: return yield(sec) if sec == nil or sec.zero?
77: message = "execution expired"
78: e = Error
79: bl = proc do |exception|
80: begin
81: x = Thread.current
82: y = Thread.start {
83: begin
84: sleep sec
85: rescue => e
86: x.raise e
87: else
88: x.raise exception, message
89: end
90: }
91: return yield(sec)
92: ensure
93: if y
94: y.kill
95: y.join # make sure y is dead.
96: end
97: end
98: end
99: ...
1xx: end

Your code running Array.combination most likely actually starts executing even BEFORE the timeout thread runs sleep sec on line 84. Your code is launched on line 91 through yield(sec).

This means the order of execution actually becomes:

1: [thread 1] numbers.combination(5).count 
# ...some time passes while the combinations are calculated ...
2: [thread 2] sleep 5 # <- The timeout thread starts running sleep
3: [thread 1] y.kill # <- The timeout thread is instantly killed
# and never times out.

In order to make sure the timeout thread starts first you can try this, which will most likely trigger the timeout exception this time:

Timeout::timeout(5) { Thread.pass; numbers.combination(5).count }

This is because by running Thread.pass you allow the MRI scheduler to start and run the code on line 82 before the native combination C-code executes. However even in this case the exception won't be triggered until combination exits because of the GIL.

There is no way around this unfortunately. You would have to use something like JRuby instead, which has real concurrent threads. Or you could run the combination calculation in a Process instead of a thread.

Ruby timeout a block of code after n *milli* seconds

Just use a decimal value for the timeout. Example for n milliseconds:

Timeout::timeout(n / 1000.0) { sleep(100) }


Related Topics



Leave a reply



Submit