Dougui
Dougui

Reputation: 7230

How to made a http request in a thread an keep the call order?

I want to do a function which call a remote service each second. To do this, I have something like this :

stop = false
text = ""

while stop == false
  r = RestClient.post 'http://example.com'
  text += r.to_str
  sleep 1
  # after a treatment, the value of stop will set to true
end

The problem is than the program is blocked until the http request is done and I don't want it. I can put this code in a subprocess but I want to keep the result in call order. For example, I can have this request :

 time | anwser
--------------
  10  | Happy
  100 | New
  10  | Year

The second request is longer than the third so, with threads, I will have the third result before the second and the value of the variable text will be HappyYearNew and I want HappyNewYear.

I there a way to have multiple process and to keep the original order? It's a very little program, I don't want to have to install a server like redis if it's possible.

Upvotes: 1

Views: 865

Answers (2)

kik
kik

Reputation: 7937

Using hash

Since ruby-1.9, hash keys order is guaranteed. A simple solution here would be to take advantage of that, by putting your requests in a hash and storing their result accessing the hash element by its key :

requests = {
  foo: [ 'a', 1 ],
  bar: [ 'b', 5 ],
  foobar: [ 'c', 2 ]
}

requests.each do |name, config|
  Thread.new( name, config ) do |name, config|
    sleep config[1]
    requests[ name ] = config[0]
  end
end

sleep 6

requests.each do |name, result|
  puts "#{name} : #{result}"
end

Produces :

foo : a
bar : b
foobar : c

Thus, to match your provided code :

stop, i, text, requests = false, 0, '', {}

until stop
  i += 1
  requests[ i ] = nil

  Thread.new( i ) do |key|
    r = RestClient.post 'http://example.com'
    requests[ i ] = r.to_str
    sleep 1
    # after a treatment, the value of stop will set to true
  end
end

# you will have to join threads, here
text = requests.values.join

Using array

If the last example is good for you, you could even simplify that using an array. Array order is of course guaranteed too, and you can take advantage of ruby array dynamic size nature :

a = []
a[5] = 1
p a
=> [nil, nil, nil, nil, nil, 1]

So, previous example can be rewritten :

stop, i, text, requests = false, 0, '', []

until stop
  i += 1

  Thread.new( i ) do |key|
    r = RestClient.post 'http://example.com'
    requests[ i ] = r.to_str
    sleep 1
    # after a treatment, the value of stop will set to true
  end
end

# you will have to join threads, here
text = requests.join

Upvotes: 1

Linuxios
Linuxios

Reputation: 35788

Here's a pretty simple solution with Threads. I have results and rmutex as instance variables, you could make them global, class, or a lot of other things:

stop = false
Thread.current[:results] = {}
Thread.current[:rmutex] = Mutex.new
counter = 0

while(!stop)
  Thread.new(counter, Thread.current) do |idex, parent|
    r = RestClient.post 'http://example.com'
    parent[:rmutex].lock
    parent[:results][idex] = r.to_str
    parent[:rmutex].unlock
  end
  sleep 1
end

text = Thread.current[:results].to_a.sort_by {|o| o[0]}.map {|o| o[1]}.join

This works by storing the "index" into fetching that each threads operation is at, storing the result with its index, and putting it all together after sorting by index at the end.

Upvotes: 0

Related Questions