sendq 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+