beaneater 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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