sendq 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/sendq.rb +124 -0
  2. data/test/test_sendq.rb +74 -0
  3. metadata +54 -0
data/lib/sendq.rb ADDED
@@ -0,0 +1,124 @@
1
+ require 'time'
2
+
3
+ #
4
+ # SendQ - Easy access to time-constrained send queues.
5
+ #
6
+ # Example:
7
+ #
8
+ # output = []
9
+ # # process a maximum of 5 within 3 seconds
10
+ # s = SendQ.new(5, 3) { |x| output.push(x) }
11
+ # (0..9).each { |x| s.add(x) }
12
+ # s.run
13
+ # output # => [0,1,2,3,4]
14
+ # s.queue # => [5,6,7,8,9]
15
+ # s.run # nothing changes, we're still waiting to run
16
+ # sleep 3 # wait for the constraint to expire
17
+ # s.run
18
+ # output # => [0,1,2,3,4,5,6,7,8,9]
19
+ # s.queue # => []
20
+ #
21
+ # Note: The calculation is very simple and can probably be gamed. It simply
22
+ # tests if the last run, subtracted from the current time, is greater than the
23
+ # interval.
24
+ #
25
+ # The clock is by default the unix epoch and therefore is restricted to a
26
+ # minimum granularity of 1 second. If you wish to change this or otherwise
27
+ # manipulate the constraint, see SendQ.clock.
28
+ #
29
+ # Simple accessors (all can be modified):
30
+ # * queue: this is the internal queue that will be exhausted on runs. See SendQ#add.
31
+ # * filled: this is the number filled in the current interval. guaranteed to never be greater than max_per_interval
32
+ # * action: this is the block you supplied to the constructor.
33
+ # * max_per_interval: the maximum number of queue elements to exhaust in the timeframe of the interval.
34
+ #
35
+ #--
36
+ #
37
+ # Please see the COPYING file in the source distribution for copyright information.
38
+ #
39
+ #++
40
+
41
+ class SendQ
42
+
43
+ VERSION = "0.0.1"
44
+
45
+ attr_accessor :queue
46
+ attr_accessor :filled
47
+ attr_accessor :action
48
+ attr_accessor :max_per_interval
49
+
50
+ #
51
+ # Constructor.
52
+ #
53
+ # * max_per_interval (required): the maximum amount of queue elements to process in a given interval.
54
+ # * interval (optional, default 1 second): if this much time has passed, reset how many can be filled in the next run to max_per_interval. See SendQ.clock for more information on how to manipulate the clock.
55
+ # * action (required): A block that describes what you want to do with each element in the queue. This block must take one argument - the element of the queue to process.
56
+ #
57
+ def initialize(max_per_interval, interval = 1, &action)
58
+ raise ArgumentError, "Requires an action block to construct" unless action
59
+
60
+ @interval = interval
61
+ @last_run = 0
62
+ @max_per_interval = max_per_interval
63
+ @queue = []
64
+ @filled = 0
65
+ @action = action
66
+ end
67
+
68
+ #
69
+ # Add an item to the queue.
70
+ #
71
+ # Like any normal queue, items are processed in FIFO order.
72
+ #
73
+ def add(item)
74
+ @queue.push(item)
75
+ end
76
+
77
+ #
78
+ # Evaluate how many items to process in the queue and then process them.
79
+ # Update the clock with its current value.
80
+ #
81
+ def run
82
+ fill = [how_many, @queue.size].min
83
+ @filled += fill
84
+ fill.times do @action.call(@queue.shift) end
85
+ @last_run = clock
86
+ end
87
+
88
+ protected
89
+
90
+ #
91
+ # Get this SendQs clock.
92
+ #
93
+ # This defaults to Time.now.to_i. You can of course redefine it. Example:
94
+ #
95
+ # sendq = SendQ.new { ...}
96
+ # def sendq.clock Time.now.to_f
97
+ #
98
+ # For fractional seconds.
99
+ #
100
+ # Note:: All of the internal clock management is done via simple
101
+ # mathematical operators and comparison operators. Therefore, your clock can
102
+ # realistically be anything that implemements these operators.
103
+ #
104
+ def clock
105
+ Time.now.to_i
106
+ end
107
+
108
+ #
109
+ # Return how many items in the queue to process this run.
110
+ #
111
+ # Side Effect: will reset the filled count if we have determined the
112
+ # interval for the existing filled count has expired.
113
+ #
114
+ def how_many
115
+ if (clock - @last_run) >= @interval then
116
+ @filled = 0
117
+ @max_per_interval
118
+ elsif @filled < @max_per_interval
119
+ @max_per_interval - @filled
120
+ else
121
+ 0
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,74 @@
1
+ #
2
+ # Please see the COPYING file in the source distribution for copyright information.
3
+ #
4
+ require 'test/unit'
5
+ require 'sendq'
6
+
7
+ class TestSendQ < Test::Unit::TestCase
8
+ def setup
9
+ @output = []
10
+ end
11
+
12
+ def test_construct
13
+ assert_raise(ArgumentError) { SendQ.new }
14
+ assert_raise(ArgumentError) { SendQ.new(10) }
15
+ assert_raise(ArgumentError) { SendQ.new(10, 1, 12) }
16
+ assert_nothing_raised { SendQ.new(10) { puts "Foo" } }
17
+ end
18
+
19
+ def test_fill_queue
20
+ s = SendQ.new(1) { }
21
+ (0..10).each { |x| s.add(x) }
22
+ assert_equal(s.queue, (0..10).to_a)
23
+ end
24
+
25
+ def test_run_basic
26
+ s = SendQ.new(5) { |x| @output.push(x) }
27
+ (0..10).each { |x| s.add(x) }
28
+ s.run
29
+ assert_equal(@output, (0..4).to_a)
30
+ assert_equal(s.queue, (5..10).to_a)
31
+ end
32
+
33
+ def test_run_multiple
34
+ # XXX obviously, this test will have some failures if it takes more
35
+ # than 3 seconds to get to the sleep statement
36
+ s = SendQ.new(5, 3) { |x| @output.push(x) }
37
+ (0..10).each { |x| s.add(x) }
38
+ s.run
39
+ assert_equal(@output, (0..4).to_a)
40
+ assert_equal(s.queue, (5..10).to_a)
41
+
42
+ assert_equal(s.max_per_interval, s.filled)
43
+
44
+ s.run
45
+
46
+ # should be exactly the same
47
+ assert_equal(@output, (0..4).to_a)
48
+ assert_equal(s.queue, (5..10).to_a)
49
+
50
+ sleep 3
51
+
52
+ s.run
53
+
54
+ assert_equal(@output, (0..9).to_a)
55
+ assert_equal(s.queue, [10])
56
+
57
+ sleep 3
58
+
59
+ s.run
60
+
61
+ assert_equal(@output, (0..10).to_a)
62
+ assert_equal(s.queue, [])
63
+
64
+ assert_equal(s.filled, 1)
65
+
66
+ (11..14).each { |x| s.add(x) }
67
+
68
+ s.run
69
+
70
+ assert_equal(@output, (0..14).to_a)
71
+ assert_equal(s.queue, [])
72
+ assert_equal(s.max_per_interval, s.filled)
73
+ end
74
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sendq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Erik Hollensbe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: erik@hollensbe.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/sendq.rb
26
+ - test/test_sendq.rb
27
+ has_rdoc: true
28
+ homepage:
29
+ post_install_message:
30
+ rdoc_options: []
31
+
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: "0"
39
+ version:
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ requirements: []
47
+
48
+ rubyforge_project: sendq
49
+ rubygems_version: 1.1.1
50
+ signing_key:
51
+ specification_version: 2
52
+ summary: A library for queues that can only release so much information over a given timespan
53
+ test_files: []
54
+