Skip to content

Instantly share code, notes, and snippets.

@tsnow
Last active August 29, 2015 13:56
Show Gist options
  • Save tsnow/9275532 to your computer and use it in GitHub Desktop.
Save tsnow/9275532 to your computer and use it in GitHub Desktop.
PeriodicUpdater Ruby

PeriodicUpdater

Usage

$vehicle_providers = PeriodicUpdater.new(:period => 120){ VehicleWizardClient.get_providers }
$vehicle_providers.start

sleep(3) # make sure the first call completes before use
$vehicle_providers.get #=> [{:provider => ...}]

# change the provider list in VehicleWizard
sleep(120 + 3) # a little time for the call to complete, after the wait period
$vehicle_providers.get #=> [{:provider => ...}, {:provider => ...}]
# Adapted from Crafting Rails Applications, by Jose Valim, SQL Metrics chapter, final example. http://pragprog.com/book/jvrails/crafting-rails-applications
# http://pragprog.com/titles/jvrails/source_code
# ./code/sql_metrics/5_final/lib/sql_metrics.rb
# And
# Go Programming Phrasebook http://www.informit.com/store/go-programming-language-phrasebook-9780321817143
# http://www.informit.com/content/images/9780321817143/downloads/9780321817143_codeexamples.zip
# ./examples/concurrentMap.go
require 'thread'
class PeriodicUpdater
def initialize(args={:period => 60}, &action)
@period = args.fetch(:period) # in seconds
@action = action # what you want to happen
@data = nil # where that action's output will go
@queue = Queue.new # Pipe in requests to update the data and retrieve the data
@run_loop = nil # thread
@timer = nil # thread
@started = false # state var
end
def get
return @data unless @started
q = Queue.new
@queue.push(lambda{ q.push @data })
q.pop
end
def run_loop
while !next_lambda.nil?
work
end
end
def next_lambda
@action_lambda = @queue.pop
end
def work
@action_lambda.call
end
def queue_update
@queue.push(lambda{ @data = @action.call })
end
def periodic_updater
while true
sleep @period
queue_update
end
end
def stop
return self unless @started
@queue << nil # @run_loop will terminate after finishing pending requests
@run_loop.join
@run_loop = nil
@started = false
@timer.kill
@timer.join
@timer = nil
self
end
def start
@started = true
@run_loop = Thread.new{ run_loop }
queue_update
@timer = Thread.new{ periodic_updater }
self
end
end
require 'test/unit'
require './periodic_updater'
class PeriodicUpdaterTests < Test::Unit::TestCase
def test_roundtrip
a = 0
updater = PeriodicUpdater.new(:period => 2){ a += 1 }
assert_equal nil, updater.get
updater.start
assert_equal 1, updater.get
sleep 1
assert_equal 1, updater.get
sleep 1
assert_equal 2, updater.get
updater.stop
assert_equal 2, updater.get
sleep 3
assert_equal 2, updater.get
end
end
@wbrady
Copy link

wbrady commented Feb 28, 2014

Why does the get method push another lambda onto the queue rather than just accessing @data?

This is a really interesting idea but rather than poll for changes, I feel like it would be better to have Vehicle Wizard (in this case) push changes to RC when it knows its own data changed.

@tsnow
Copy link
Author

tsnow commented Feb 28, 2014

I was trying to make it threadsafe, so that it doesn't access @DaTa while @DaTa is being written by another thread.

@tsnow
Copy link
Author

tsnow commented Feb 28, 2014

@wbrady That's reasonable as well. And it might commute well with jcalvert's idea of storing the data in memcached. Something like:

class VehicleWizardFleets
      def self.all
            Rails.cache.read(:vehicle_wizard_fleets)
      end
      def self.store(data)
            Rails.cache.write(:vehicle_wizard_fleets, data)
      end
end
class VehicleWizardFleetsController < ApplicationController
  def create
       VehicleWizardFleets.store(params)
       render :text => 'stored'
  rescue => e
       render :text => e.message, :status => 500
  end
end

@wbrady
Copy link

wbrady commented Feb 28, 2014

Ahh yes now I see why you queue it again in get

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment