beaneater 0.1.0

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.
@@ -0,0 +1,39 @@
1
+ module Beaneater
2
+ # Represents a stats hash with proper underscored keys
3
+ class StatStruct < FasterOpenStruct
4
+ # Convert a stats hash into a struct.
5
+ #
6
+ # @param [Hash(String => String)] hash Hash Stats hash to convert to struct
7
+ # @return [Beaneater::StatStruct, nil] Stats struct from hash
8
+ # @example
9
+ # s = StatStruct.from_hash(:foo => "bar")
10
+ # s.foo # => 'bar'
11
+ #
12
+ def self.from_hash(hash)
13
+ return unless hash.is_a?(Hash)
14
+ underscore_hash = hash.inject({}) { |r, (k, v)| r[k.to_s.gsub(/-/, '_')] = v; r }
15
+ self.new(underscore_hash)
16
+ end
17
+
18
+ # Access value for stat with specified key.
19
+ #
20
+ # @param [String] key Key to fetch from stats.
21
+ # @return [String,Integer] Value for specified stat key.
22
+ # @example
23
+ # @stats['foo'] # => "bar"
24
+ #
25
+ def [](key)
26
+ self.send(key.to_s)
27
+ end
28
+
29
+ # Returns set of keys within this struct
30
+ #
31
+ # @return [Array<String>] Value for specified stat key.
32
+ # @example
33
+ # @stats.keys # => ['foo', 'bar', 'baz']
34
+ #
35
+ def keys
36
+ @hash.keys.map { |k| k.to_s }
37
+ end
38
+ end # StatStruct
39
+ end # Beaneater
@@ -0,0 +1,2 @@
1
+ require 'beaneater/tube/record'
2
+ require 'beaneater/tube/collection'
@@ -0,0 +1,134 @@
1
+ module Beaneater
2
+ # Represents collection of tube related commands.
3
+ class Tubes < PoolCommand
4
+
5
+ # Creates new tubes instance.
6
+ #
7
+ # @param [Beaneater::Pool] pool The beaneater pool for this tube.
8
+ # @example
9
+ # Beaneater::Tubes.new(@pool)
10
+ #
11
+ def initialize(pool)
12
+ @last_used = 'default'
13
+ super
14
+ end
15
+
16
+ # Finds the specified beanstalk tube.
17
+ #
18
+ # @param [String] tube_name Name of the beanstalkd tube
19
+ # @return [Beaneater::Tube] specified tube
20
+ # @example
21
+ # @pool.tubes.find('tube2')
22
+ # @pool.tubes['tube2']
23
+ # # => <Beaneater::Tube name="tube2">
24
+ #
25
+ def find(tube_name)
26
+ Tube.new(self.pool, tube_name)
27
+ end
28
+ alias_method :[], :find
29
+
30
+ # Reserves a ready job looking at all watched tubes.
31
+ #
32
+ # @param [Integer] timeout Number of seconds before timing out.
33
+ # @param [Proc] block Callback to perform on the reserved job.
34
+ # @yield [job] Reserved beaneater job.
35
+ # @return [Beaneater::Job] Reserved beaneater job.
36
+ # @example
37
+ # @conn.tubes.reserve { |job| process(job) }
38
+ # # => <Beaneater::Job id=5 body="foo">
39
+ #
40
+ def reserve(timeout=nil, &block)
41
+ res = transmit_to_rand(timeout ? "reserve-with-timeout #{timeout}" : 'reserve')
42
+ job = Job.new(res)
43
+ block.call(job) if block_given?
44
+ job
45
+ end
46
+
47
+ # Set specified tube as used.
48
+ #
49
+ # @param [String] tube Tube to be used.
50
+ # @example
51
+ # @conn.tubes.use("some-tube")
52
+ #
53
+ def use(tube)
54
+ return tube if @last_used == tube
55
+ res = transmit_to_all("use #{tube}")
56
+ @last_used = tube
57
+ rescue BadFormatError
58
+ raise InvalidTubeName, "Tube cannot be named '#{tube}'"
59
+ end
60
+
61
+ # List of all known beanstalk tubes.
62
+ #
63
+ # @return [Array<Beaneater::Tube>] List of all beanstalk tubes.
64
+ # @example
65
+ # @pool.tubes.all
66
+ # # => [<Beaneater::Tube name="tube2">, <Beaneater::Tube name="tube3">]
67
+ #
68
+ def all
69
+ transmit_to_rand('list-tubes')[:body].map { |tube_name| Tube.new(self.pool, tube_name) }
70
+ end
71
+
72
+ # List of watched beanstalk tubes.
73
+ #
74
+ # @return [Array<Beaneater::Tube>] List of watched beanstalk tubes.
75
+ # @example
76
+ # @pool.tubes.watched
77
+ # # => [<Beaneater::Tube name="tube2">, <Beaneater::Tube name="tube3">]
78
+ #
79
+ def watched
80
+ transmit_to_rand('list-tubes-watched')[:body].map { |tube_name| Tube.new(self.pool, tube_name) }
81
+ end
82
+
83
+ # Currently used beanstalk tube.
84
+ #
85
+ # @return [Beaneater::Tube] Currently used beanstalk tube.
86
+ # @example
87
+ # @pool.tubes.used
88
+ # # => <Beaneater::Tube name="tube2">
89
+ #
90
+ def used
91
+ Tube.new(self.pool, transmit_to_rand('list-tube-used')[:id])
92
+ end
93
+
94
+ # Add specified beanstalkd tubes as watched.
95
+ #
96
+ # @param [*String] names Name of tubes to watch
97
+ # @raise [Beaneater::InvalidTubeName] Tube to watch was invalid.
98
+ # @example
99
+ # @pool.tubes.watch('foo', 'bar')
100
+ #
101
+ def watch(*names)
102
+ names.each do |t|
103
+ transmit_to_all "watch #{t}"
104
+ end
105
+ rescue BadFormatError => ex
106
+ raise InvalidTubeName, "Tube in '#{ex.cmd}' is invalid!"
107
+ end
108
+
109
+ # Add specified beanstalkd tubes as watched and ignores all other tubes.
110
+ #
111
+ # @param [*String] names Name of tubes to watch
112
+ # @raise [Beaneater::InvalidTubeName] Tube to watch was invalid.
113
+ # @example
114
+ # @pool.tubes.watch!('foo', 'bar')
115
+ #
116
+ def watch!(*names)
117
+ old_tubes = watched.map(&:name) - names.map(&:to_s)
118
+ watch(*names)
119
+ ignore(*old_tubes)
120
+ end
121
+
122
+ # Ignores specified beanstalkd tubes.
123
+ #
124
+ # @param [*String] names Name of tubes to ignore
125
+ # @example
126
+ # @pool.tubes.ignore('foo', 'bar')
127
+ #
128
+ def ignore(*names)
129
+ names.each do |w|
130
+ transmit_to_all "ignore #{w}"
131
+ end
132
+ end
133
+ end # Tubes
134
+ end # Beaneater
@@ -0,0 +1,158 @@
1
+ module Beaneater
2
+ # Beanstalk tube which contains jobs which can be inserted, reserved, et al.
3
+ class Tube < PoolCommand
4
+ # The default delay for inserted jobs.
5
+ DEFAULT_DELAY = 0
6
+ # Default priority for inserted jobs, 0 is the highest.
7
+ DEFAULT_PRIORITY = 65536
8
+ # Default time to respond for inserted jobs.
9
+ DEFAULT_TTR = 120
10
+
11
+ # @!attribute name
12
+ # @return [String] name of the tube
13
+ attr_reader :name
14
+
15
+ # Fetches the specified tube.
16
+ #
17
+ # @param [Beaneater::Pool] pool The beaneater pool for this tube.
18
+ # @param [String] name The name for this tube.
19
+ # @example
20
+ # Beaneater::Tube.new(@pool, 'tube-name')
21
+ #
22
+ def initialize(pool, name)
23
+ @name = name.to_s
24
+ @mutex = Mutex.new
25
+ super(pool)
26
+ end
27
+
28
+ # Inserts job with specified body onto tube.
29
+ #
30
+ # @param [String] body The data to store with this job.
31
+ # @param [Hash] options The settings associated with this job.
32
+ # @option options [Integer] pri priority for this job
33
+ # @option options [Integer] ttr time to respond for this job
34
+ # @option options [Integer] delay delay for this job
35
+ # @return [Hash] beanstalkd command response
36
+ # @example
37
+ # @tube.put "data", :pri => 1000, :ttr => 10, :delay => 5
38
+ #
39
+ # @api public
40
+ def put(body, options={})
41
+ safe_use do
42
+ options = { :pri => DEFAULT_PRIORITY, :delay => DEFAULT_DELAY, :ttr => DEFAULT_TTR }.merge(options)
43
+ cmd_options = "#{options[:pri]} #{options[:delay]} #{options[:ttr]} #{body.bytesize}"
44
+ transmit_to_rand("put #{cmd_options}\n#{body}")
45
+ end
46
+ end
47
+
48
+ # Peek at next job within this tube in given `state`.
49
+ #
50
+ # @param [String] state The job state to peek at (`ready`, `buried`, `delayed`)
51
+ # @return [Beaneater::Job] The next job within this tube.
52
+ # @example
53
+ # @tube.peek(:ready) # => <Beaneater::Job id=5 body=foo>
54
+ #
55
+ # @api public
56
+ def peek(state)
57
+ safe_use do
58
+ res = transmit_until_res "peek-#{state}", :status => "FOUND"
59
+ Job.new(res)
60
+ end
61
+ rescue Beaneater::NotFoundError => ex
62
+ # Return nil if not found
63
+ end
64
+
65
+ # Reserves the next job from tube.
66
+ #
67
+ # @param [Integer] timeout Number of seconds before timing out
68
+ # @param [Proc] block Callback to perform on reserved job
69
+ # @yield [job] Job that was reserved.
70
+ # @return [Beaneater::Job] Job that was reserved.
71
+ # @example
72
+ # @tube.reserve # => <Beaneater::Job id=5 body=foo>
73
+ #
74
+ def reserve(timeout=nil, &block)
75
+ pool.tubes.watch!(self.name)
76
+ pool.tubes.reserve(timeout, &block)
77
+ end
78
+
79
+ # Kick specified number of jobs from buried to ready state.
80
+ #
81
+ # @param [Integer] bounds The number of jobs to kick.
82
+ # @return [Hash] Beanstalkd command response
83
+ # @example
84
+ # @tube.kick(5)
85
+ #
86
+ def kick(bounds=1)
87
+ safe_use { transmit_to_rand("kick #{bounds}") }
88
+ end
89
+
90
+ # Returns related stats for this tube.
91
+ #
92
+ # @return [Beaneater::StatStruct] Struct of tube related values
93
+ # @example
94
+ # @tube.stats.delayed # => 24
95
+ #
96
+ def stats
97
+ res = transmit_to_all("stats-tube #{name}", :merge => true)
98
+ StatStruct.from_hash(res[:body])
99
+ end
100
+
101
+ # Pause the execution of this tube for specified `delay`.
102
+ #
103
+ # @param [Integer] delay Number of seconds to delay tube execution
104
+ # @return [Hash] Beanstalkd command response
105
+ # @example
106
+ # @tube.pause(10)
107
+ #
108
+ def pause(delay)
109
+ transmit_to_all("pause-tube #{name} #{delay}")
110
+ end
111
+
112
+ # Clears all unreserved jobs in all states from the tube
113
+ #
114
+ # @example
115
+ # @tube.clear
116
+ #
117
+ def clear
118
+ pool.tubes.watch!(self.name)
119
+ %w(delayed buried ready).each do |state|
120
+ while job = self.peek(state.to_sym)
121
+ job.delete
122
+ end
123
+ end
124
+ pool.tubes.ignore(name)
125
+ rescue Beaneater::UnexpectedResponse
126
+ # swallow any issues
127
+ end
128
+
129
+ # String representation of tube.
130
+ #
131
+ # @return [String] Representation of tube including name.
132
+ # @example
133
+ # @tube.to_s # => "#<Beaneater::Tube name=foo>"
134
+ #
135
+ def to_s
136
+ "#<Beaneater::Tube name=#{name.inspect}>"
137
+ end
138
+ alias :inspect :to_s
139
+
140
+ protected
141
+
142
+ # Transmits a beanstalk command that requires this tube to be set as used.
143
+ #
144
+ # @param [Proc] block Beanstalk command to transmit.
145
+ # @return [Object] Result of block passed
146
+ # @example
147
+ # safe_use { transmit_to_rand("kick 1") }
148
+ # # => "Response to kick command"
149
+ #
150
+ def safe_use(&block)
151
+ @mutex.lock
152
+ tubes.use(self.name)
153
+ yield
154
+ ensure
155
+ @mutex.unlock
156
+ end
157
+ end # Tube
158
+ end # Beaneater
@@ -0,0 +1,4 @@
1
+ module Beaneater
2
+ # Current version of gem.
3
+ VERSION = "0.1.0"
4
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "beanstalk-client" do
4
+ before do
5
+ @beanstalk = Beaneater::Pool.new(['127.0.0.1:11300'])
6
+ @tubes = ['one', 'two', 'three']
7
+
8
+ # Put something on each tube so they exist
9
+ tube_one = @beanstalk.tubes.find('one')
10
+ tube_one.put('one')
11
+
12
+ tube_two = @beanstalk.tubes.find('two')
13
+ tube_two.put('two')
14
+ end
15
+
16
+ describe "test thread safe one" do
17
+ before do
18
+ # Create threads that will execute
19
+ # A: use one
20
+ # B: use one
21
+ # B: put two
22
+ # A: put one
23
+ a = Thread.new do
24
+ tube_one = @beanstalk.tubes.find('one')
25
+ sleep 4
26
+ tube_one.put('one')
27
+ end
28
+
29
+ b = Thread.new do
30
+ sleep 1
31
+ tube_two = @beanstalk.tubes.find('two')
32
+ tube_two.put('two')
33
+ end
34
+
35
+ a.join
36
+ b.join
37
+ end
38
+
39
+ it "should return correct current-jobs-ready for tube one" do
40
+ one = @beanstalk.tubes.find('one').stats
41
+ assert_equal 2, one.current_jobs_ready
42
+ end
43
+
44
+ it "should return correct current-jobs-ready for tube two" do
45
+ two = @beanstalk.tubes.find('two').stats
46
+ assert_equal 2, two.current_jobs_ready
47
+ end
48
+ end
49
+
50
+ describe "test thread safe two" do
51
+ before do
52
+ a = Thread.new do
53
+ tube_one = @beanstalk.tubes.find('one')
54
+ sleep 4
55
+ tube_one.put('one')
56
+ end
57
+
58
+ b = Thread.new do
59
+ tube_two = @beanstalk.tubes.find('two')
60
+ sleep 1
61
+ tube_two.put('two')
62
+ end
63
+
64
+ a.join
65
+ b.join
66
+ end
67
+
68
+ it "should return correct current-jobs-ready for tube one" do
69
+ one = @beanstalk.tubes.find('one').stats
70
+ assert_equal 2, one.current_jobs_ready
71
+ end
72
+
73
+ it "should return correct current-jobs-ready for tube two" do
74
+ two = @beanstalk.tubes.find('two').stats
75
+ assert_equal 2, two.current_jobs_ready
76
+ end
77
+ end
78
+
79
+ describe "test delete job in reserved state" do
80
+ before do
81
+ @tube_three = @beanstalk.tubes.find('three')
82
+ @tube_three.put('one')
83
+ @job = @tube_three.reserve
84
+ end
85
+
86
+ it "should be deleted properly" do
87
+ assert_equal 'one', @job.body
88
+ assert_equal 'one', @beanstalk.jobs.find(@job.id).body
89
+ @job.delete
90
+ assert_nil @beanstalk.jobs.find(@job.id)
91
+ end
92
+ end
93
+
94
+ describe "test delete job in buried state" do
95
+ before do
96
+ @tube_three = @beanstalk.tubes.find('three')
97
+ @tube_three.put('two')
98
+ @job = @tube_three.reserve
99
+ end
100
+
101
+ it "should delete job as expected in buried state" do
102
+ assert_equal 'two', @job.body
103
+ @job.bury
104
+ assert_equal 'two', @tube_three.peek(:buried).body
105
+
106
+ @job.delete
107
+ assert_nil @beanstalk.jobs.find(@job.id)
108
+ end
109
+ end
110
+
111
+ after do
112
+ cleanup_tubes!(@tubes, @beanstalk)
113
+ end
114
+
115
+ end