Refresh Machine em Ruby

Posted by – 13/08/2008

Eventualmente todos enfrentaremos esta situação: você tem um objeto com “estados” que são lidos de uma fonte externa através de pooling. Usualmente você simplesmente adiciona uma thread no construtor do objeto e coloca dentro dela um loop infinito que verifica a cada intervalo de tempo se existe estado novo e, se for o caso, atualiza o estado do objeto.

Essa é uma construção muito simples e bastante utilizada, no entanto tem um problema de escalabilidade importante: para cada objeto você tem uma thread e um loop infinito. Se você tem poucos objetos, isso não chega a ser um problema… mas se tem muitos, aí é outra conversa.

Alguns vão argumentar que não é obrigatório usar essa construção, que dependendo da fonte dos dados, ela pode “atualizar” o objeto, em um padrão Observer, o que está mais do que correto… Mas nem sempre controlamos a fonte dos dados, e nem sempre ela é inteligente assim (na realizade, quando não a controlamos ela parece bantante burra!). Foi para isso que programei uma “Refresh Machine”: um container para objetos que precisam ser atualizados através de pooling. Ela presume duas coisas: que esse objeto tenha um método/atributo refresh que armazene o intervalo em segundos entre as atualizações, e um método do_refresh, que será executado entre os intervalos.

Vamos ao código:

require 'thread'
class RefreshMachine
 
  attr_accessor :wait
 
  def initialize(wait = false)
    @wait    = wait
    @killall = false
    @queue   = Queue.new
    @removed = Array.new
    @thread  = Thread.new do
      loop do
        ( @removed.clear; @queue.clear; @killall = false ) if @killall
        next if @queue.empty?
        object, last_refresh, thread = @queue.deq
 
        # Helps the garbage collection
        thread = nil if (! thread.nil?) and (! thread.alive?)
 
        # Three things can happen with a dequeued object
        if (Time.now < (last_refresh + object.refresh)) or
           (! thread.nil? and @wait)
          # First: It's too early to refresh it or we still have
          #        a refresh thread running and have to 'wait', so we
          #        just put it back in the queue
          @queue.enq [ object, last_refresh, thread ]
        else
          if @removed.include?(object)
            # Second: We have a "remove request" for it, so we
            #         delete the request and avoid queueing it again
            @removed.delete(object)
          else
            # Third: It's time to refresh it, so we
            #        call do_refresh and put it back in the queue
            add(object)
          end
        end
      end
    end
  end
 
  def add(object)
    @queue.enq [ object, Time.now, Thread.new { object.do_refresh } ]
  end
 
  def del(object)
    @removed << object unless @removed.include?(object)
  end
 
  def killall
    @killall = true
  end
 
end # of class RefreshMachine

O objetivo é usar o mínimo possível de threads com loops infinitos…. e ficou pequena o bastante para blogar a respeito 🙂

3 Comments on Refresh Machine em Ruby

  1. spectra says:

    hehehe. aqui estah o exemplo:

    class ForRefresh
      attr_accessor :refresh
      def initialize(name)
        @name = name
        @refresh = 5
      end
     
      def do_refresh
        puts "#{Time.now} - Refreshing #{@name}"
      end
    end
     
    r = RefreshMachine.new
     
    one = ForRefresh.new("one")
    two = ForRefresh.new("two")
     
    r.add(one)
    r.add(two)
     
    #=>Thu Aug 14 16:09:57 -0300 2008 - Refreshing one
    #=>Thu Aug 14 16:10:00 -0300 2008 - Refreshing two
    #=>Thu Aug 14 16:10:02 -0300 2008 - Refreshing one
    #=>Thu Aug 14 16:10:05 -0300 2008 - Refreshing two
    #=>Thu Aug 14 16:10:07 -0300 2008 - Refreshing one
    #=> ...
     
    r.del(two)
     
    #=>Thu Aug 14 16:11:17 -0300 2008 - Refreshing one
    #=>Thu Aug 14 16:11:22 -0300 2008 - Refreshing one
    #=>Thu Aug 14 16:11:27 -0300 2008 - Refreshing one
    #=> ...
     
    r.killall
  2. Braz Cubas Jr. says:

    E pode fazer em português… é que vi esse post na Ruby-Talk em inglês… nem vi que o texto está em “língua nativa”.

  3. Braz Cubas Jr. says:

    Great code. Can you please add an example of usage. I don’t think I got the mechanics of it (I am a Ruby newbie).

Leave a Reply

Your email address will not be published. Required fields are marked *