stalk_climber 0.0.6 → 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,7 @@
1
+ --- !ruby/object:RDoc::Options
2
+ encoding: UTF-8
3
+ static_path: []
4
+ rdoc_include:
5
+ - .
6
+ charset: UTF-8
7
+ exclude: !ruby/regexp /(?:test)/
@@ -4,17 +4,15 @@ rvm:
4
4
  - 1.9.3
5
5
  - 2.0.0
6
6
  - jruby-19mode
7
- - rbx-2.0.0
7
+ - rbx
8
8
 
9
9
  before_install:
10
- - sudo apt-get update -qq
11
- - sudo apt-get install -qq beanstalkd
12
- - sudo sed -i 's/#START=yes/START=yes/' /etc/default/beanstalkd
13
- - sudo service beanstalkd start
14
- - cat /etc/init.d/beanstalkd | sed 's/NAME=beanstalkd/NAME=beanstalkd2/' | sudo tee /etc/init.d/beanstalkd2 2>&1>/dev/null
15
- - cat /etc/default/beanstalkd | sed 's/11300/11301/' | sudo tee /etc/default/beanstalkd2 2>&1>/dev/null
16
- - sudo chmod +x /etc/init.d/beanstalkd2
17
- - sudo service beanstalkd2 start
10
+ - curl -L https://github.com/kr/beanstalkd/archive/v1.9.tar.gz | tar xz -C /tmp
11
+ - cd /tmp/beanstalkd-1.9/
12
+ - make
13
+ - ./beanstalkd &
14
+ - ./beanstalkd -p 11301 &
15
+ - cd $TRAVIS_BUILD_DIR
18
16
 
19
17
  env:
20
18
  - BEANSTALK_ADDRESSES='beanstalk://localhost:11300,beanstalk://localhost:11301'
@@ -22,4 +20,4 @@ env:
22
20
  matrix:
23
21
  allow_failures:
24
22
  - rvm: jruby-19mode
25
- - rvm: rbx-2.0.0
23
+ - rvm: rbx
data/Gemfile CHANGED
@@ -7,4 +7,5 @@ gem 'debugger', :group => [:development, :test], :platforms => [:mri]
7
7
  group :test do
8
8
  gem 'coveralls', :require => false
9
9
  gem 'mocha', :require => false
10
+ gem 'minitest_should'
10
11
  end
@@ -1,9 +1,14 @@
1
1
  module StalkClimber; end
2
2
 
3
+ require 'forwardable'
3
4
  require 'beaneater'
4
5
  require 'stalk_climber/version'
5
6
  require 'stalk_climber/lazy_enumerable'
7
+ require 'stalk_climber/climber_enumerable'
8
+ require 'stalk_climber/climber_enumerables'
6
9
  require 'stalk_climber/connection'
7
10
  require 'stalk_climber/connection_pool'
8
11
  require 'stalk_climber/climber'
9
12
  require 'stalk_climber/job'
13
+ require 'stalk_climber/tubes'
14
+ require 'stalk_climber/tube'
@@ -1,11 +1,24 @@
1
1
  module StalkClimber
2
2
  class Climber
3
- include RUBY_VERSION >= '2.0.0' ? LazyEnumerable : Enumerable
4
3
 
5
- attr_accessor :beanstalk_addresses, :test_tube
6
- attr_reader :cache
4
+ extend Forwardable
7
5
 
8
- # Returns or creates a ConnectionPool from beanstalk_addresses
6
+ def_delegator :connection_pool, :tubes
7
+
8
+ # Collection of beanstalk_addresses the pool is connected to
9
+ attr_accessor :beanstalk_addresses
10
+
11
+ # Accessor to the climber's Jobs instance
12
+ attr_reader :jobs
13
+
14
+ # Tube used when injecting jobs to probe state of Beanstalkd
15
+ attr_accessor :test_tube
16
+
17
+ # :call-seq:
18
+ # connection_pool() => StalkClimber::ConnectionPool
19
+ #
20
+ # Returns or creates a ConnectionPool from beanstalk_addresses. Raises a
21
+ # RuntimeError if #beanstalk_addresses has not been set.
9
22
  def connection_pool
10
23
  return @connection_pool unless @connection_pool.nil?
11
24
  if self.beanstalk_addresses.nil?
@@ -15,50 +28,27 @@ module StalkClimber
15
28
  end
16
29
 
17
30
 
18
- # Climb over all jobs on all connections in the connection pool.
19
- # An instance of Job is yielded. For more information see Connection#climb
20
- def climb
21
- enum = to_enum
22
- return enum unless block_given?
23
- loop do
24
- begin
25
- yield enum.next
26
- rescue StopIteration => e
27
- return (e.nil? || !e.respond_to?(:result) || e.result.nil?) ? nil : e.result
28
- end
29
- end
30
- end
31
- alias_method :each, :climb
32
-
33
-
34
- # Perform a threaded climb across all connections in the connection pool.
35
- # This method cannot be used for enumerable enumeration because a break
36
- # called from one of the threads will cause a LocalJumpError. This could be
37
- # fixed, but expected behavior on break varies as to whether or not to wait
38
- # for all threads before returning a result. However, still useful for
39
- # operations that always visit all jobs.
40
- # An instance of Job is yielded to +block+
41
- def climb_threaded(&block)
42
- threads = []
43
- self.connection_pool.connections.each do |connection|
44
- threads << Thread.new { connection.each(&block) }
45
- end
46
- threads.each(&:join)
47
- return
48
- end
49
- alias_method :each_threaded, :climb_threaded
50
-
51
-
52
- # Creates a new Climber instance, optionally yielding the instance
53
- # if a block is given
54
- def initialize(beanstalk_addresses = nil, test_tube = nil)
31
+ # Creates a new Climber instance, optionally yielding the instance for
32
+ # configuration if a block is given
33
+ #
34
+ # Climber.new('beanstalk://localhost:11300', 'stalk_climber')
35
+ # #=> #<StalkClimber::Job beanstalk_addresses="beanstalk://localhost:11300" test_tube="stalk_climber">
36
+ def initialize(beanstalk_addresses = nil, test_tube = nil) # :yields: climber
55
37
  self.beanstalk_addresses = beanstalk_addresses
56
38
  self.test_tube = test_tube
39
+ @jobs = StalkClimber::Jobs.new(self)
57
40
  yield(self) if block_given?
58
41
  end
59
42
 
60
43
 
61
- # Returns a hash with connections as keys and max_job_ids as values
44
+ # :call-seq:
45
+ # max_job_ids() => Hash{Beaneater::Connection => Integer}
46
+ #
47
+ # Returns a Hash with connections as keys and max_job_ids as values
48
+ #
49
+ # climber = Climber.new('beanstalk://localhost:11300', 'stalk_climber')
50
+ # climber.max_job_ids
51
+ # #=> {#<Beaneater::Connection host="localhost" port=11300>=>1183}
62
52
  def max_job_ids
63
53
  connection_pairs = connection_pool.connections.map do |connection|
64
54
  [connection, connection.max_job_id]
@@ -67,19 +57,17 @@ module StalkClimber
67
57
  end
68
58
 
69
59
 
70
- # Returns an Enumerator for enumerating jobs on all connections.
71
- # Connections are enumerated in the order defined. See Connection#to_enum
72
- # for more information
73
- # A job is yielded with each iteration.
74
- def to_enum
75
- return Enumerator.new do |yielder|
76
- self.connection_pool.connections.each do |connection|
77
- connection.each do |job|
78
- yielder << job
79
- end
80
- end
81
- end
60
+ # :call-seq:
61
+ # to_s() => String
62
+ #
63
+ # Return string representation of climber
64
+ #
65
+ # Climber.new('beanstalk://localhost:11300', 'stalk_climber').to_s
66
+ # #=> #<StalkClimber::Job beanstalk_addresses="beanstalk://localhost:11300" test_tube="stalk_climber">
67
+ def to_s
68
+ return "#<StalkClimber::Job beanstalk_addresses=#{beanstalk_addresses.inspect} test_tube=#{test_tube.inspect}>"
82
69
  end
70
+ alias :inspect :to_s
83
71
 
84
72
  end
85
73
  end
@@ -0,0 +1,110 @@
1
+ module StalkClimber
2
+
3
+ module ClimberEnumerable
4
+
5
+ include RUBY_VERSION >= '2.0.0' ? LazyEnumerable : Enumerable
6
+ extend Forwardable
7
+
8
+ def_delegators :to_enum, :each
9
+
10
+ # A reference to the climber instance to which this enumerable belongs
11
+ attr_reader :climber
12
+
13
+
14
+ # :call-seq:
15
+ # new(enumerator_method) => New class including ClimberEnumerable
16
+ #
17
+ # Factory that simplifies the creation of ClimberEnumerable classes.
18
+ # Otherwise in simple cases a class would have to be defined only to
19
+ # include this module and set the desired :enumerator_method symbol.
20
+ # The :enumerator_method parameter is passed to each connection in
21
+ # the connection pool of the climber given at instantiation.
22
+ #
23
+ # jobs = ClimberEnumerable.new(:each_job)
24
+ # instance = jobs.new(climber)
25
+ # instance.each do |job|
26
+ # break job
27
+ # end
28
+ # #=> #<StalkClimber::Job id=1 body="Work to be done">
29
+ #
30
+ def self.new(enumerator_method)
31
+ return Class.new do
32
+ include StalkClimber::ClimberEnumerable
33
+ @enumerator_method = enumerator_method
34
+
35
+ # Create a new instance of a ClimberEnumerable when given +climber+ that
36
+ # references the StalkClimber that owns it
37
+ def initialize(climber)
38
+ @climber = climber
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+ # Add :enumerator_method class level accessor to inheriting class
45
+ def self.included(base) # :nodoc:
46
+ class << base
47
+ attr_reader :enumerator_method
48
+ end
49
+ end
50
+
51
+
52
+ # Perform a threaded iteration across all connections in the climber's
53
+ # connection pool. This method cannot be used for enumerable enumeration
54
+ # because a break called within one of the threads will cause a LocalJumpError.
55
+ # This could be fixed, but expected behavior on break varies as to whether
56
+ # or not to wait for all threads before returning a result. However, still
57
+ # useful for operations that always visit all elements.
58
+ # An instance of the element is yielded with each iteration.
59
+ #
60
+ # jobs = ClimberEnumerable.new(:each_job)
61
+ # instance = jobs.new(climber)
62
+ # instance.each_threaded do |job|
63
+ # ...
64
+ # end
65
+ def each_threaded(&block) # :yields: Object
66
+ threads = []
67
+ climber.connection_pool.connections.each do |connection|
68
+ threads << Thread.new { connection.send(self.class.enumerator_method, &block) }
69
+ end
70
+ threads.each(&:join)
71
+ return
72
+ end
73
+
74
+
75
+ # :call-seq:
76
+ # to_enum() => Enumerator
77
+ #
78
+ # Returns an Enumerator for enumerating elements on all connections.
79
+ # Connections are enumerated in the order defined. See Connection#to_enum
80
+ # for more information
81
+ # An instance of the element is yielded with each iteration.
82
+ def to_enum
83
+ return Enumerator.new do |yielder|
84
+ climber.connection_pool.connections.each do |connection|
85
+ connection.send(self.class.enumerator_method) do |element|
86
+ yielder << element
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+
93
+ # :call-seq:
94
+ # each {|obj| block} => Object
95
+ #
96
+ # Iterate over all elements on all connections in the climber's connection
97
+ # pool. If no block is given, returns the enumerator provided by #to_enum.
98
+ # An instance of the element is yielded to a given block. For more information
99
+ # see the method on Connection designated :enumerator_method by the implementing class
100
+ #
101
+ # jobs = ClimberEnumerable.new(:each_job)
102
+ # instance = jobs.new(climber)
103
+ # instance.each do |job|
104
+ # break job
105
+ # end
106
+ # #=> #<StalkClimber::Job id=1 body="Work to be done">
107
+
108
+ end
109
+
110
+ end
@@ -0,0 +1,7 @@
1
+ module StalkClimber
2
+
3
+ # ClimberEnumerable for iterating over connection jobs. See ClimberEnumerable
4
+ # for more information
5
+ Jobs = StalkClimber::ClimberEnumerable.new(:each_job)
6
+
7
+ end
@@ -1,48 +1,43 @@
1
1
  module StalkClimber
2
2
  class Connection < Beaneater::Connection
3
- include RUBY_VERSION >= '2.0.0' ? LazyEnumerable : Enumerable
4
3
 
4
+ extend Forwardable
5
+
6
+ def_delegator :job_enumerator, :each, :each_job
7
+
8
+ # Default tube used when no custom tube in use
5
9
  DEFAULT_TUBE = 'stalk_climber'
10
+
11
+ # Transmission used to probe state of Beanstalkd. Created with lowest
12
+ # possible priority and delay to reduce possibility of interference.
6
13
  PROBE_TRANSMISSION = "put 4294967295 0 300 2\r\n{}"
7
14
 
8
- attr_reader :max_climbed_job_id, :min_climbed_job_id, :test_tube
15
+ # Last known maximum job id on the Beanstalkd server
16
+ attr_reader :max_climbed_job_id
17
+ # Last known existing minimum job id on the Beanstalkd server
18
+ attr_reader :min_climbed_job_id
19
+ # Tube to use when probing the Beanstalkd server for information
20
+ attr_reader :test_tube
9
21
 
10
22
 
23
+ # :call-seq:
24
+ # cached_jobs() => Hash
25
+ #
11
26
  # Returns or creates a Hash used for caching jobs by ID
12
- def cache
13
- return @cache ||= {}
27
+ def cached_jobs
28
+ return @cached_jobs ||= {}
14
29
  end
15
30
 
16
31
 
17
32
  # Resets the job cache and reinitializes the min and max climbed job ids
18
- def clear_cache
19
- @cache = nil
33
+ def clear_job_cache
34
+ @cached_jobs = nil
20
35
  @min_climbed_job_id = Float::INFINITY
21
36
  @max_climbed_job_id = 0
37
+ return true
22
38
  end
23
39
 
24
40
 
25
- # Interface for job enumerator/enumeration in descending ID order. Returns an instance of
26
- # Job for each existing job on the beanstalk server. Jobs are enumerated in three phases. Jobs
27
- # between max_job_id and the max_climbed_job_id are pulled from beanstalk, cached, and yielded.
28
- # Jobs that have already been cached are yielded if they still exist, otherwise they are deleted
29
- # from the cache. Finally, jobs between min_climbed_job_id and 1 are pulled from beanstalk, cached,
30
- # and yielded.
31
- # Connection#each fulfills Enumberable contract, allowing connection to behave as an Enumerable.
32
- def climb
33
- enum = to_enum
34
- return enum unless block_given?
35
- loop do
36
- begin
37
- yield enum.next
38
- rescue StopIteration => e
39
- return (e.nil? || !e.respond_to?(:result) || e.result.nil?) ? nil : e.result
40
- end
41
- end
42
- end
43
- alias_method :each, :climb
44
-
45
-
46
41
  # Safe form of fetch_job!, returns a Job instance for the specified +job_id+.
47
42
  # If the job does not exist, the error is caught and nil is passed returned instead.
48
43
  def fetch_job(job_id)
@@ -84,7 +79,7 @@ module StalkClimber
84
79
  def initialize(address, test_tube = DEFAULT_TUBE)
85
80
  super(address)
86
81
  @test_tube = test_tube || DEFAULT_TUBE
87
- clear_cache
82
+ clear_job_cache
88
83
  yield(self) if block_given?
89
84
  use_test_tube
90
85
  end
@@ -92,7 +87,7 @@ module StalkClimber
92
87
 
93
88
  # Determintes the max job ID of the connection by inserting a job into the test tube
94
89
  # and immediately deleting it. Before returning the max ID, the max ID is used to
95
- # update the max_climbed_job_id (if sequentual) and possibly invalidate the cache.
90
+ # update the max_climbed_job_id (if sequentual) and possibly invalidate the job cache.
96
91
  # The cache will be invalidated if the max ID is less than any known IDs since
97
92
  # new job IDs should always increment unless there's been a change in server state.
98
93
  def max_job_id
@@ -111,12 +106,12 @@ module StalkClimber
111
106
 
112
107
 
113
108
  # Returns an Enumerator for crawling all existing jobs for a connection.
114
- # See Connection#each for more information.
115
- def to_enum
109
+ # See Connection#each_job for more information.
110
+ def job_enumerator
116
111
  return Enumerator.new do |yielder|
117
112
  max_id = max_job_id
118
113
 
119
- initial_cached_jobs = cache.values_at(*cache.keys.sort.reverse)
114
+ initial_cached_jobs = cached_jobs.values_at(*cached_jobs.keys.sort.reverse)
120
115
 
121
116
  max_id.downto(self.max_climbed_job_id + 1) do |job_id|
122
117
  job = fetch_and_cache_job(job_id)
@@ -127,7 +122,7 @@ module StalkClimber
127
122
  if job.exists?
128
123
  yielder << job
129
124
  else
130
- self.cache.delete(job.id)
125
+ self.cached_jobs.delete(job.id)
131
126
  end
132
127
  end
133
128
 
@@ -135,6 +130,7 @@ module StalkClimber
135
130
  job = fetch_and_cache_job(job_id)
136
131
  yielder << job unless job.nil?
137
132
  end
133
+ nil
138
134
  end
139
135
  end
140
136
 
@@ -147,7 +143,7 @@ module StalkClimber
147
143
  # and nil is returned
148
144
  def fetch_and_cache_job(job_id)
149
145
  job = fetch_job(job_id)
150
- self.cache[job_id] = job unless job.nil?
146
+ self.cached_jobs[job_id] = job unless job.nil?
151
147
  @min_climbed_job_id = job_id if job_id < @min_climbed_job_id
152
148
  @max_climbed_job_id = job_id if job_id > @max_climbed_job_id
153
149
  return job
@@ -155,13 +151,13 @@ module StalkClimber
155
151
 
156
152
 
157
153
  # Uses +new_max_id+ to update the max_climbed_job_id (if sequentual) and possibly invalidate
158
- # the cache. The cache will be invalidated if +new_max_id+ is less than any known IDs since
159
- # new job IDs should always increment unless there's been a change in server state.
154
+ # the job cache. The job cache will be invalidated if +new_max_id+ is less than any known
155
+ # IDs since new job IDs should always increment unless there's been a change in server state.
160
156
  def update_climbed_job_ids_from_max_id(new_max_id)
161
157
  if @max_climbed_job_id > 0 && @max_climbed_job_id == new_max_id - 1
162
158
  @max_climbed_job_id = new_max_id
163
159
  elsif new_max_id < @max_climbed_job_id
164
- clear_cache
160
+ clear_job_cache
165
161
  end
166
162
  end
167
163
 
@@ -178,5 +174,21 @@ module StalkClimber
178
174
  end
179
175
  end
180
176
 
177
+
178
+ # :call-seq:
179
+ # each_job() => Enumerator
180
+ # each_job {|job| block }
181
+ # Interface for job enumerator/enumeration in descending ID order. Returns an instance of
182
+ # Job for each existing job on the beanstalk server. Jobs are enumerated in three phases. Jobs
183
+ # between max_job_id and the max_climbed_job_id are pulled from beanstalk, cached, and yielded.
184
+ # Jobs that have already been cached are yielded if they still exist, otherwise they are deleted
185
+ # from the job cache. Finally, jobs between min_climbed_job_id and 1 are pulled from beanstalk,
186
+ # cached, and yielded.
187
+ #
188
+ # connection = Connection.new('localhost:11300')
189
+ # connection.each_job do |job|
190
+ # job.delete
191
+ # end
192
+
181
193
  end
182
194
  end