gemerald_beanstalk 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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - jruby-19mode
7
+ - rbx-19mode
8
+
9
+ before_script:
10
+ - bundle config
11
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || git clone https://github.com/beanstalkd/beaneater.git /tmp/beaneater'
12
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || BUNDLE_GEMFILE=/tmp/beaneater/Gemfile bundle install'
13
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || git clone https://github.com/nesquena/backburner.git /tmp/backburner'
14
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || BUNDLE_GEMFILE=/tmp/backburner/Gemfile bundle install'
15
+
16
+ script:
17
+ - rake test
18
+ - sudo sed -i 's/^\(127.0.0.1.*\)$/\1 coveralls.io/' /etc/hosts
19
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || rake start_gemerald_beanstalk_test_server &'
20
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || cd /tmp/beaneater'
21
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || BUNDLE_GEMFILE=/tmp/beaneater/Gemfile bundle exec rake test:full'
22
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || cd /tmp/backburner'
23
+ - '[ "$TRAVIS_RUBY_VERSION" = "jruby-19mode" ] && (exit 0) || BUNDLE_GEMFILE=/tmp/backburner/Gemfile bundle exec rake'
24
+
25
+ matrix:
26
+ allow_failures:
27
+ - rvm: jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'beanstalk_integration_tests', '~> 0.0.8'
7
+ gem 'coveralls', :require => false
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Freewrite.org
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # GemeraldBeanstalk
2
+ [![Build Status](https://travis-ci.org/gemeraldbeanstalk/gemerald_beanstalk.png?branch=master)](https://travis-ci.org/gemeraldbeanstalk/gemerald_beanstalk)
3
+ [![Coverage Status](https://coveralls.io/repos/gemeraldbeanstalk/gemerald_beanstalk/badge.png)](https://coveralls.io/r/gemeraldbeanstalk/gemerald_beanstalk)
4
+ [![Code Climate](https://codeclimate.com/github/gemeraldbeanstalk/gemerald_beanstalk.png)](https://codeclimate.com/github/gemeraldbeanstalk/gemerald_beanstalk)
5
+ [![Dependency Status](https://gemnasium.com/gemeraldbeanstalk/gemerald_beanstalk.png)](https://gemnasium.com/gemeraldbeanstalk/gemerald_beanstalk)
6
+
7
+
8
+ GemeraldBeanstalk offers a Ruby implementation of beanstalkd for testing and other uses.
9
+
10
+ ## Usage
11
+
12
+ GemeraldBeanstalk should work as a drop in replacement for beanstalkd. You can
13
+ start a server via GemeraldBeanstalk::Server.start:
14
+ ```ruby
15
+
16
+ # Start a GemeraldBeanstalk bound to 0.0.0.0:11300
17
+ GemeraldBeanstalk::Server.start
18
+
19
+ # Customize server binding
20
+ GemeraldBeanstalk::Server.start('192.168.1.10', 11301)
21
+ ```
22
+
23
+ GemeraldBeanstalk::Server.start returns an array containing the Thread the
24
+ server is running in and the server's GemeraldBeanstalk::Beanstalk instance.
25
+
26
+ The internals of GemeraldBeanstalk are undocumented at this point, with the
27
+ expectation being that it should be interacted with strictly via the [beanstalkd
28
+ protocol](https://github.com/kr/beanstalkd/blob/master/doc/protocol.md). This
29
+ will likely change in the future, allowing more programatic access directly to
30
+ the GemeraldBeanstalk::Beanstalk.
31
+
32
+ ## Installation
33
+
34
+ Add this line to your application's Gemfile:
35
+
36
+ gem 'gemerald_beanstalk'
37
+
38
+ And then execute:
39
+
40
+ $ bundle
41
+
42
+ Or install it yourself as:
43
+
44
+ $ gem install gemerald_beanstalk
45
+
46
+ ## Unbugs!
47
+ In the process of building GemeraldBeanstalk, a number of bugs and inconsistencies
48
+ with Beanstalkd protocol were discovered. Patches have been submitted to correct
49
+ the various bugs and inconsistencies, but they have not yet been merged into
50
+ beanstalkd.
51
+
52
+ It would be fairly tedious to reproduce the behavior of some of the bugs, and as
53
+ such, GemeraldBeanstalk doesn't suffer from them. This can be troubling when
54
+ you run tests that work against GemeraldBeanstalk, but then fail against an
55
+ actual beanstalkd server. Below are a list of those protocol issues that exist
56
+ with Beanstalk, but not with GemeraldBeanstalk.
57
+ * [Pause tube should check tube name valid](https://github.com/kr/beanstalkd/pull/217)
58
+ * [Can't ignore tube with name 200 chars long](https://github.com/kr/beanstalkd/issues/212)
59
+ * [Use of 200-char tube name causes INTERNAL_ERROR](https://github.com/kr/beanstalkd/issues/211)
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'gemerald_beanstalk'
4
+
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ end
10
+
11
+ task :start_gemerald_beanstalk_test_server do
12
+ Thread.abort_on_exception = true
13
+ server_thread, beanstalk = GemeraldBeanstalk::Server.start(ENV['BIND_ADDRESS'], ENV['PORT'])
14
+ trap("SIGINT") { server_thread.kill }
15
+ puts "GemeraldBeanstalk listening on #{beanstalk.address}"
16
+ server_thread.join
17
+ end
18
+
19
+ task :default => :test
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gemerald_beanstalk/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'gemerald_beanstalk'
8
+ spec.version = GemeraldBeanstalk::VERSION
9
+ spec.authors = ['Freewrite.org']
10
+ spec.email = ['dev@freewrite.org']
11
+ spec.description = %q{RubyGem implementation of beanstalkd}
12
+ spec.summary = %q{Gemerald Beanstalk offers a Ruby implementation of beanstalkd for testing and other uses.}
13
+ spec.homepage = 'https://github.com/gemeraldbeanstalk/gemerald_beanstalk'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^test/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'eventmachine'
22
+ spec.add_dependency 'thread_safe'
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'rake'
25
+ end
@@ -0,0 +1,10 @@
1
+ require 'set'
2
+ require 'gemerald_beanstalk/version'
3
+ require 'thread_safe'
4
+ require 'gemerald_beanstalk/job'
5
+ require 'gemerald_beanstalk/beanstalk'
6
+ require 'gemerald_beanstalk/connection'
7
+ require 'gemerald_beanstalk/command'
8
+ require 'gemerald_beanstalk/jobs'
9
+ require 'gemerald_beanstalk/tube'
10
+ require 'gemerald_beanstalk/server'
@@ -0,0 +1,289 @@
1
+ require 'securerandom'
2
+ require 'socket'
3
+ require 'gemerald_beanstalk/beanstalk_helper'
4
+
5
+ class GemeraldBeanstalk::Beanstalk
6
+
7
+ # contains functionality supporting core commands
8
+ include GemeraldBeanstalk::BeanstalkHelper
9
+
10
+ # Colon-separated host and port of the Server the Beanstalk instance is
11
+ # running on.
12
+ attr_reader :address
13
+
14
+ # The maximum size in bytes that a job can be. Jobs exceeding this threshold
15
+ # will result in JOB_TOO_BIG responses.
16
+ attr_reader :max_job_size
17
+
18
+
19
+ # Initializes a new Beanstalk instance with the given +address+ and
20
+ # +maximum_job_size+. +address+ is a string identifying the host and port
21
+ # that the Beanstalk instance is running on. +maximum_job_size+ is the
22
+ # maximum size in bytes that a job can be. Jobs exceeding this threshold will
23
+ # result in JOB_TOO_BIG responses.
24
+ #
25
+ # beanstalk = GemeraldBeanstalk::Beanstalk.new('localhost:11300')
26
+ def initialize(address, maximum_job_size = 2**16)
27
+ @max_job_size = maximum_job_size
28
+ @address = address
29
+ @connections = ThreadSafe::Array.new
30
+ @delayed = ThreadSafe::Array.new
31
+ @id = SecureRandom.base64(16)
32
+ @jobs = GemeraldBeanstalk::Jobs.new
33
+ @mutex = Mutex.new
34
+ @paused = ThreadSafe::Array.new
35
+ @reserved = ThreadSafe::Cache.new {|reserved, key| reserved[key] = [] }
36
+ @stats = ThreadSafe::Hash.new(0)
37
+ @tubes = ThreadSafe::Cache.new
38
+ @up_at = Time.now.to_f
39
+
40
+ tube('default', :create_if_missing)
41
+ end
42
+
43
+
44
+ protected
45
+
46
+ def bury(connection, job_id, priority, *args)
47
+ adjust_stats_key(:'cmd-bury')
48
+ job = find_job(job_id, :only => JOB_RESERVED_STATES)
49
+ return NOT_FOUND if job.nil? || !job.bury(connection, priority)
50
+
51
+ @reserved[connection].delete(job)
52
+ return BURIED
53
+ end
54
+
55
+
56
+ def delete(connection, job_id = nil, *args)
57
+ adjust_stats_key(:'cmd-delete')
58
+ job = find_job(job_id)
59
+ return NOT_FOUND if job.nil?
60
+
61
+ original_state = job.state
62
+ return NOT_FOUND unless job.delete(connection)
63
+
64
+ tube(job.tube_name).delete(job)
65
+ @jobs[job.id - 1] = nil
66
+ @reserved[connection].delete(job) if JOB_RESERVED_STATES.include?(original_state)
67
+
68
+ return DELETED
69
+ end
70
+
71
+
72
+ def ignore(connection, tube_name)
73
+ adjust_stats_key(:'cmd-ignore')
74
+ return NOT_IGNORED if (watched_count = connection.ignore(tube_name)).nil?
75
+ tube = tube(tube_name)
76
+ tube.ignore unless tube.nil?
77
+ return "WATCHING #{watched_count}\r\n"
78
+ end
79
+
80
+
81
+ def kick(connection, limit, *args)
82
+ adjust_stats_key(:'cmd-kick')
83
+ limit = limit.to_i
84
+ kicked = 0
85
+ JOB_INACTIVE_STATES.each do |job_state|
86
+ # GTE to handle negative limits
87
+ break if kicked >= limit
88
+ until (job = tube(connection.tube_used).next_job(job_state, :peek)).nil?
89
+ kicked += 1 if job.kick
90
+ break if kicked == limit
91
+ end
92
+ break if kicked > 0
93
+ end
94
+
95
+ return "KICKED #{kicked}\r\n"
96
+ end
97
+
98
+
99
+ def kick_job(connection, job_id = nil, *args)
100
+ job = find_job(job_id, :only => JOB_INACTIVE_STATES)
101
+ return (!job.nil? && job.kick) ? KICKED : NOT_FOUND
102
+ end
103
+
104
+
105
+ def list_tubes(connection)
106
+ adjust_stats_key(:'cmd-list-tubes')
107
+ return tube_list(active_tubes.keys)
108
+ end
109
+
110
+
111
+ def list_tube_used(connection)
112
+ adjust_stats_key(:'cmd-list-tube-used')
113
+ return "USING #{connection.tube_used}\r\n"
114
+ end
115
+
116
+
117
+ def list_tubes_watched(connection)
118
+ adjust_stats_key(:'cmd-list-tubes-watched')
119
+ return tube_list(connection.tubes_watched)
120
+ end
121
+
122
+
123
+ def pause_tube(connection, tube_name, delay)
124
+ adjust_stats_key(:'cmd-paue-tube')
125
+ return NOT_FOUND if (tube = tube(tube_name)).nil?
126
+ tube.pause(delay.to_i % 2**32)
127
+ @paused << tube
128
+ return PAUSED
129
+ end
130
+
131
+
132
+ def peek(connection, job_id = nil, *args)
133
+ adjust_stats_key(:'cmd-peek')
134
+ return peek_message(find_job(job_id))
135
+ end
136
+
137
+
138
+ def peek_buried(connection)
139
+ return peek_by_state(connection, :buried)
140
+ end
141
+
142
+
143
+ def peek_delayed(connection)
144
+ return peek_by_state(connection, :delayed)
145
+ end
146
+
147
+
148
+ def peek_ready(connection)
149
+ return peek_by_state(connection, :ready)
150
+ end
151
+
152
+
153
+ def put(connection, priority, delay, ttr, bytes, body)
154
+ adjust_stats_key(:'cmd-put')
155
+ bytes = bytes.to_i
156
+ return JOB_TOO_BIG if bytes > @max_job_size
157
+ return EXPECTED_CRLF if body.slice!(-2, 2) != CRLF || body.length != bytes
158
+
159
+ job = nil
160
+ # Ensure job insertion order and ID
161
+ @mutex.synchronize do
162
+ job = GemeraldBeanstalk::Job.new(self, @jobs.next_id, connection.tube_used, priority, delay, ttr, bytes, body)
163
+ @jobs.enqueue(job)
164
+ tube(connection.tube_used).put(job)
165
+ end
166
+
167
+ # Send async so client doesn't wait while we check if job can be immediately dispatched
168
+ connection.transmit("INSERTED #{job.id}\r\n")
169
+
170
+ connection.producer = true
171
+
172
+ case job.state
173
+ when :ready
174
+ honor_reservations(job)
175
+ when :delayed
176
+ @delayed << job
177
+ end
178
+ return nil
179
+ end
180
+
181
+
182
+ def quit(connection)
183
+ disconnect(connection)
184
+ return nil
185
+ end
186
+
187
+
188
+ def release(connection, job_id, priority, delay)
189
+ adjust_stats_key(:'cmd-release')
190
+ job = find_job(job_id, :only => JOB_RESERVED_STATES)
191
+ return NOT_FOUND if job.nil? || !job.release(connection, priority, delay)
192
+
193
+ @reserved[connection].delete(job)
194
+ @delayed << job if job.delayed?
195
+ return RELEASED
196
+ end
197
+
198
+
199
+ def reserve(connection, *args)
200
+ adjust_stats_key(:'cmd-reserve')
201
+ return BAD_FORMAT unless args.empty?
202
+ reserve_job(connection)
203
+ return nil
204
+ end
205
+
206
+
207
+ def reserve_with_timeout(connection, timeout = 0, *args)
208
+ adjust_stats_key(:'cmd-reserve-with-timeout')
209
+ timeout = timeout.to_i
210
+ return nil if reserve_job(connection, timeout) || timeout != 0
211
+ connection.wait_timed_out
212
+ return TIMED_OUT
213
+ end
214
+
215
+
216
+ def stats(connection)
217
+ adjust_stats_key(:'cmd-stats')
218
+ stats = @jobs.counts_by_state.merge(stats_commands).merge({
219
+ 'job-timeouts' => @stats[:'job-timeouts'],
220
+ 'total-jobs' => @jobs.total_jobs,
221
+ 'max-job-size' => @max_job_size,
222
+ 'current-tubes' => active_tubes.length,
223
+ }).merge(stats_connections).merge({
224
+ 'pid' => Process.pid,
225
+ 'version' => GemeraldBeanstalk::VERSION,
226
+ 'rusage-utime' => 0,
227
+ 'rusage-stime' => 0,
228
+ 'uptime' => uptime,
229
+ 'binlog-oldest-index' => 0,
230
+ 'binlog-current-index' => 0,
231
+ 'binlog-records-migrated' => 0,
232
+ 'binlog-records-written' => 0,
233
+ 'binlog-max-size' => 10485760,
234
+ 'id' => @id,
235
+ 'hostname' => Socket.gethostname,
236
+ })
237
+ return yaml_response(stats.map{|stat, value| "#{stat}: #{value}" })
238
+ end
239
+
240
+
241
+ def stats_job(connection, job_id = nil, *args)
242
+ adjust_stats_key(:'cmd-stats-job')
243
+ job = find_job(job_id)
244
+ return NOT_FOUND if job.nil?
245
+
246
+ return yaml_response(job.stats.map{ |stat, value| "#{stat}: #{value}" })
247
+ end
248
+
249
+
250
+ def stats_tube(connection, tube_name)
251
+ adjust_stats_key(:'cmd-stats-tube')
252
+ return NOT_FOUND if (tube = tube(tube_name)).nil?
253
+
254
+ return yaml_response(tube.stats.map{ |stat, value| "#{stat}: #{value}" })
255
+ end
256
+
257
+
258
+ def touch(connection, job_id = nil, *args)
259
+ adjust_stats_key(:'cmd-touch')
260
+ job = find_job(job_id, :only => JOB_RESERVED_STATES)
261
+ return NOT_FOUND if job.nil? || !job.touch(connection)
262
+
263
+ return TOUCHED
264
+ end
265
+
266
+
267
+ def use(connection, tube_name)
268
+ adjust_stats_key(:'cmd-use')
269
+ tube(connection.tube_used).stop_use
270
+ tube(tube_name, :create_if_missing).use
271
+ connection.use(tube_name)
272
+
273
+ return "USING #{tube_name}\r\n"
274
+ end
275
+
276
+
277
+ def watch(connection, tube_name)
278
+ adjust_stats_key(:'cmd-watch')
279
+ if connection.tubes_watched.include?(tube_name)
280
+ watched_count = connection.tubes_watched.length
281
+ else
282
+ tube(tube_name, :create_if_missing).watch
283
+ watched_count = connection.watch(tube_name)
284
+ end
285
+
286
+ return "WATCHING #{watched_count}\r\n"
287
+ end
288
+
289
+ end