Skip to content

Commit

Permalink
Fixed concurrency problems in window counter.
Browse files Browse the repository at this point in the history
Rewrote WindowCounter to be much more efficient.

[#11: resolved]
  • Loading branch information
dbalatero committed Jun 18, 2009
1 parent 4f2063d commit 38df104
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 37 deletions.
54 changes: 18 additions & 36 deletions lib/queue_stick/window_counter.rb
@@ -1,3 +1,5 @@
require 'thread'

module QueueStick
class WindowCounter
attr_reader :name
Expand All @@ -8,60 +10,40 @@ def initialize(name, window)
'counter requires a name' if name.nil?
raise ArgumentError,
'counter requires a time window' if window.nil?

@name = name
@window = window

# Converted to seconds.
@window = window * 60
@count = 0

@counts = []
@timings = []
@counts = Hash.new { |h, k| h[k] = 0 }
@mutex = Mutex.new
end

# TODO(dbalatero): break this up?
def count
to_delete = []
current_minute = self.class.current_minute
range = (current_minute - @window)..current_minute
sum = 0
threshold = threshold_time

@counts.each_index do |index|
if @timings[index] < threshold
to_delete << index
else
sum += @counts[index]
@mutex.synchronize do
range.each do |key|
sum += @counts[key]
end
end

to_delete.each do |index|
@counts.delete_at(index)
@timings.delete_at(index)
end

sum
end

def increment!(by = 1)
current = Time.now
current -= current.sec
timing = @timings.last

if timing and
self.class.times_have_same_minute?(current, timing)
@counts[@counts.size - 1] += by
else
@counts << by
@timings << current
current_minute = self.class.current_minute
@mutex.synchronize do
@counts[current_minute] += by
end
end

private
def self.times_have_same_minute?(time1, time2)
time1.to_i / 60 == time2.to_i / 60
def self.current_minute
minute_for(Time.now)
end

def threshold_time
current = Time.now
current - (current.sec + @window) + 60
def self.minute_for(time)
(time.to_f / 60).round
end
end
end
22 changes: 21 additions & 1 deletion spec/queue_stick/window_counter_spec.rb
Expand Up @@ -33,6 +33,26 @@
counter = QueueStick::WindowCounter.new(:test, 5)
counter.count.should == 0
end

# see lighthouse ticket #11
it "should not flip the count back to 0 randomly with multiple threads contending" do
counter = QueueStick::WindowCounter.new(:test, 1)
threads = []

# 16 increments, 4 threads
4.times do
threads << Thread.new(counter) do |c|
4.times do
c.count
c.increment!
c.count
end
end
end
threads.each { |thread| thread.join }

counter.count.should == 16
end
end

describe "increment!" do
Expand Down Expand Up @@ -73,7 +93,7 @@
@counter.count.should == 33

Time.stub!(:now).
and_return(Time.mktime(2009, 5, 18, 22, 36, 0, 0))
and_return(Time.mktime(2009, 5, 18, 22, 37, 31, 0))
@counter.count.should == 0
end
end
Expand Down

0 comments on commit 38df104

Please sign in to comment.