beehive 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/.gemtest ADDED
File without changes
data/MANIFEST ADDED
@@ -0,0 +1,21 @@
1
+ .gemtest
2
+ MANIFEST
3
+ README.md
4
+ Rakefile
5
+ example/client.rb
6
+ example/multiple-workers.rb
7
+ example/worker.rb
8
+ lib/.gitkeep
9
+ lib/beehive/.gitkeep
10
+ lib/beehive/client.rb
11
+ lib/beehive/version.rb
12
+ lib/beehive/worker.rb
13
+ lib/beehive.rb
14
+ license.txt
15
+ spec/.gitkeep
16
+ spec/.rspec
17
+ spec/beehive/client.rb
18
+ spec/beehive/worker.rb
19
+ spec/helper.rb
20
+ spec/tmp/.gitkeep
21
+ task/build.rake
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Beehive
2
+
3
+ Beehive is a super lightweight queue system that uses Redis as it's storage engine.
4
+ Beehive was created because I got fed up with Resque's large memory footprint and not
5
+ being able to find a decent alternative that wasn't broken.
6
+
7
+ ## Requirements
8
+
9
+ * Redis and the redis Gem - install with `gem install redis`
10
+ * The JSON gem - install with `gem install json`
11
+ * Ruby >= 1.9.2
12
+
13
+ ## Installation & Usage
14
+
15
+ Installing Beehive is done as following:
16
+
17
+ $ gem install beehive
18
+
19
+ Once it's installed you can use it as following:
20
+
21
+ require 'beehive'
22
+
23
+ client = Beehive::Client.new
24
+ client.queue('email.send', :to => 'info@yorickpeterse.com', :subject => 'Hello, world!')
25
+
26
+ Your worker would look like the following:
27
+
28
+ require 'beehive'
29
+
30
+ Beehive.job('email.send') do |params|
31
+ # Do something with Net::IMAP, Net::POP, etc
32
+ end
33
+
34
+ worker = Beehive::Worker.new({}, {:jobs => ['email.send']})
35
+ worker.work
36
+
37
+ For more examples see the "example" directory.
38
+
39
+ ## License
40
+
41
+ Beehive is licensed under the MIT license, a copy of this license can be found in the file
42
+ "license.txt".
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require File.expand_path('../lib/beehive', __FILE__)
2
+ require 'rake/testtask'
3
+
4
+ # Load all the tasks
5
+ Dir.glob(File.expand_path("../task/*.rake", __FILE__)).each do |f|
6
+ import(f)
7
+ end
8
+
9
+ # Set the tests so they can be run with $ rake test
10
+ Rake::TestTask.new do |t|
11
+ t.test_files = Dir.glob(File.expand_path('../spec/beehive/**/*.rb', __FILE__))
12
+ t.verbose = true
13
+ end
data/example/client.rb ADDED
@@ -0,0 +1,7 @@
1
+ require File.expand_path('../../lib/beehive', __FILE__)
2
+
3
+ # Initialize the client
4
+ client = Beehive::Client.new
5
+
6
+ # Send the job to the queue
7
+ client.queue('example', :amount => 2)
@@ -0,0 +1,30 @@
1
+ require File.expand_path('../../lib/beehive', __FILE__)
2
+
3
+ # A more "complex" job
4
+ Beehive.job('example') do |params|
5
+ if params.key?('amount')
6
+ amount = params['amount'].to_i
7
+ else
8
+ amount = 2
9
+ end
10
+
11
+ amount.times do |num|
12
+ puts "Iteration: #{num}"
13
+ end
14
+ end
15
+
16
+ # The amount of workers to start
17
+ if ARGV.empty?
18
+ worker_amount = 5
19
+ else
20
+ worker_amount = ARGV[0].to_i
21
+ end
22
+
23
+ # Start 5 workers
24
+ worker_amount.times do
25
+ Process.fork do
26
+ # Start the worker and wait for a job to do
27
+ worker = Beehive::Worker.new({}, {:jobs => ['example'], :log_level => Logger::INFO, :daemonize => true})
28
+ worker.work
29
+ end
30
+ end
data/example/worker.rb ADDED
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../../lib/beehive', __FILE__)
2
+
3
+ # Define the job. Due to the forking behavior the output won't be actually visible.
4
+ Beehive.job('example') do |params|
5
+ puts "Hello, world!"
6
+ end
7
+
8
+ # Create a new instance of a worker and customize the settings a bit.
9
+ worker = Beehive::Worker.new({}, {:jobs => ['example'], :log_level => Logger::INFO})
10
+
11
+ # And start the worker
12
+ worker.work
data/lib/.gitkeep ADDED
File without changes
File without changes
@@ -0,0 +1,70 @@
1
+ module Beehive
2
+ ##
3
+ # The client class that can be used to add and manage jobs in the queue.
4
+ #
5
+ # @author Yorick Peterse
6
+ # @since 0.1
7
+ #
8
+ class Client
9
+ # Instance of the class "Redis"
10
+ attr_reader :connection
11
+
12
+ ##
13
+ # Creates a new instance of the client and sends the specified options to Redis.
14
+ #
15
+ # @author Yorick Peterse
16
+ # @since 0.1
17
+ # @param [Hash] options A hash containing options to use for Redis. See
18
+ # Redis#initialize for more information.
19
+ #
20
+ def initialize(options = {})
21
+ @connection = ::Redis.new(options)
22
+ end
23
+
24
+ ##
25
+ # Queues a given job in the storage.
26
+ #
27
+ # @example
28
+ # client = Beehive::Client.new
29
+ # client.queue('video', :title => 'Hello world')
30
+ #
31
+ # @author Yorick Peterse
32
+ # @since 0.1
33
+ # @param [String] job The name of the job to queue.
34
+ # @param [Hash] params The parameters to send to the job.
35
+ #
36
+ def queue(job, params = {})
37
+ job = "beehive.jobs.#{job}"
38
+ params = JSON.dump(params)
39
+
40
+ @connection.rpush(job, params)
41
+ end
42
+
43
+ ##
44
+ # Retrieves the last job and removes it from the storage.
45
+ #
46
+ # @author Yorick Peterse
47
+ # @since 0.1
48
+ # @param [String] job The name of the type of job to retrieve.
49
+ # @return [Array]
50
+ #
51
+ def get(job)
52
+ job = "beehive.jobs.#{job}"
53
+ job = @connection.rpop(job)
54
+
55
+ if !job.nil?
56
+ return JSON.load(job)
57
+ end
58
+ end
59
+
60
+ ##
61
+ # Closes the Redis connection.
62
+ #
63
+ # @author Yorick Peterse
64
+ # @since 0.1
65
+ #
66
+ def disconnect
67
+ @connection.quit
68
+ end
69
+ end # Client
70
+ end # Beehive
@@ -0,0 +1,9 @@
1
+ module Beehive
2
+ ##
3
+ # The current version of Beehive.
4
+ #
5
+ # @author Yorick Peterse
6
+ # @since 0.1
7
+ #
8
+ Version = '0.1'
9
+ end
@@ -0,0 +1,159 @@
1
+ module Beehive
2
+ ##
3
+ # The Worker class is used to retrieve and process jobs (in the background). Whenever a
4
+ # job is received the worker will fork itself and execute the job in that process. This
5
+ # is useful because any errors won't crash the worker itself plus it will reduce the
6
+ # memory usage as once Ruby allocates memory to a process it's never released unless
7
+ # that process exits.
8
+ #
9
+ # @author Yorick Peterse
10
+ # @since 0.1
11
+ #
12
+ class Worker
13
+ ##
14
+ # Hash containing the default worker options.
15
+ #
16
+ # @author Yorick Peterse
17
+ # @since 0.1
18
+ #
19
+ Options = {
20
+ :logger => ::Logger.new(STDOUT),
21
+ :daemonize => false,
22
+ :jobs => [],
23
+ :wait => 5,
24
+ :log_level => Logger::WARN
25
+ }
26
+
27
+ # Instance of Beehive::Client, used for communicating with the Redis server
28
+ attr_reader :connection
29
+
30
+ # Hash containing all the custom configuration options
31
+ attr_accessor :options
32
+
33
+ ##
34
+ # Creates a new instance of the class, sets all the options and connects to the Redis
35
+ # database.
36
+ #
37
+ # @example
38
+ # worker = Beehive::Worker.new({}, {:jobs => ['video'], :wait => 2})
39
+ #
40
+ # @author Yorick Peterse
41
+ # @since 0.1
42
+ # @param [Hash] redis_options Hash containing all the options to use for Redis. See
43
+ # Redis#new for more information.
44
+ # @param [Hash] worker_options Hash containing worker specific options.
45
+ # @option worker_options :logger The logger that should be used, has to be compatible
46
+ # with Logger of the standard library.
47
+ # @option worker_options :daemonize Tells the worker to run in the background.
48
+ # @option worker_options :jobs An array of jobs the current worker has to process.
49
+ # @option worker_options :wait The amount of seconds to wait after each iteration,
50
+ # reduces CPU and network usage.
51
+ # @option worker_options :log_level The log even to use for the :logger option, set to
52
+ # Logger::WARN by default.
53
+ #
54
+ def initialize(redis_options = {}, worker_options = {})
55
+ @connection = ::Beehive::Client.new(redis_options)
56
+ @options = Options.merge(worker_options)
57
+ @options[:logger].level = @options[:log_level]
58
+ @shutdown = false
59
+
60
+ # Check if the given jobs are valid
61
+ @options[:jobs].each do |job|
62
+ if !::Beehive::Jobs.key?(job)
63
+ raise("The job \"#{job}\" is invalid as it could not be found in Beehive::Jobs")
64
+ end
65
+ end
66
+
67
+ trap_signals
68
+ end
69
+
70
+ ##
71
+ # Waits for available jobs and execute a job whenever one is available.
72
+ #
73
+ # @example
74
+ # worker = Beehive::Worker.new
75
+ # worker.work
76
+ #
77
+ # @author Yorick Peterse
78
+ # @since 0.1
79
+ #
80
+ def work
81
+ # Daemonize the process?
82
+ if @options[:daemonize] === true
83
+ Process.daemon(true)
84
+ end
85
+
86
+ @options[:logger].info("Starting main worker, PID: #{Process.pid}")
87
+
88
+ loop do
89
+ if @shutdown === true
90
+ @options[:logger].info('The party has ended, time to shut down')
91
+ @connection.disconnect
92
+ exit
93
+ end
94
+
95
+ # Reset the child PID
96
+ @child_pid = nil
97
+
98
+ # Check if there are any jobs available
99
+ @options[:jobs].each do |job|
100
+ params = @connection.get(job)
101
+
102
+ if params
103
+ @options[:logger].info(
104
+ "Received the job \"#{job}\" with the following data: #{params.inspect}"
105
+ )
106
+
107
+ # Fork the process and run the job
108
+ @child_pid = Process.fork do
109
+ @options[:logger].info('Process forked, executing job')
110
+
111
+ begin
112
+ ::Beehive::Jobs[job].call(params)
113
+
114
+ @options[:logger].info('Job successfully processed')
115
+ exit
116
+ rescue => e
117
+ @options[:logger].error("Failed to execute the job: #{e.inspect}")
118
+ end
119
+ end
120
+
121
+ # Wait for the job to finish. This prevents this worker from spawning a worker
122
+ # for each job it has to process (which could result in hundreds of processes
123
+ # being spawned.
124
+ Process.waitpid(@child_pid)
125
+ end
126
+ end
127
+
128
+ # Reduces CPU load and network traffic
129
+ sleep(@options[:wait])
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ ##
136
+ # Registers all the signals that trigger a shutdown.
137
+ #
138
+ # @author Yorick Peterse
139
+ # @since 0.1
140
+ #
141
+ def trap_signals
142
+ # Shut down gracefully
143
+ ['TERM', 'INT'].each do |signal|
144
+ Signal.trap(signal) { @shutdown = true }
145
+ end
146
+
147
+ # QUIT will trigger the setup to quit immediately
148
+ Signal.trap('QUIT') do
149
+ @shutdown = true
150
+
151
+ # No point in killing a child process if it isn't there in the first place
152
+ if !@child_pid.nil?
153
+ @options[:logger].info("Shutting down the worker with PID #{@child_pid}")
154
+ Process.kill('INT', @child_pid)
155
+ end
156
+ end
157
+ end
158
+ end # Worker
159
+ end # Beehive
data/lib/beehive.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'redis'
2
+ require 'json'
3
+ require 'logger'
4
+
5
+ require_relative('beehive/version')
6
+ require_relative('beehive/client')
7
+ require_relative('beehive/worker')
8
+
9
+ ##
10
+ # Beehive is a lightweight queue system that uses Redis to store all jobs. It's inspired
11
+ # by existing systems such as Minion and Stalker. Beehive does not offer a plugin system
12
+ # or any other fancy features, it merely allows you to queue a job and have it processed
13
+ # in the background.
14
+ #
15
+ # ## Features
16
+ #
17
+ # * Uses Redis as it's storage mechanism.
18
+ # * Lightweight: workers don't waste resources on loading large amounts of libraries.
19
+ # * Easy to use
20
+ #
21
+ # Fore more information see the README file.
22
+ #
23
+ # @author Yorick Peterse
24
+ # @since 0.1
25
+ #
26
+ module Beehive
27
+ ##
28
+ # Hash containing all the defined jobs.
29
+ #
30
+ # @author Yorick Peterse
31
+ # @since 0.1
32
+ #
33
+ Jobs = {}
34
+
35
+ ##
36
+ # Adds a new job to the list.
37
+ #
38
+ # @example
39
+ # Beehive.job('video') do |params|
40
+ # puts "Hello!"
41
+ # end
42
+ #
43
+ # @author Yorick Peterse
44
+ # @since 0.1
45
+ # @param [String] job The name of the job.
46
+ # @param [Proc] block The block to call whenever the job has to be processed.
47
+ #
48
+ def self.job(job, &block)
49
+ Jobs[job.to_s] = block
50
+ end
51
+ end
data/license.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011, Yorick Peterse
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/spec/.gitkeep ADDED
File without changes
data/spec/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --colour
@@ -0,0 +1,17 @@
1
+ require File.expand_path('../../helper', __FILE__)
2
+
3
+ describe('Beehive::Client') do
4
+
5
+ it('Queue a new job') do
6
+ client = Beehive::Client.new
7
+ client.queue('video', :title => 'hello')
8
+ end
9
+
10
+ it('Get the latest job') do
11
+ client = Beehive::Client.new
12
+ job = client.get('video')
13
+
14
+ job['title'].should === 'hello'
15
+ end
16
+
17
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path('../../helper', __FILE__)
2
+
3
+ describe('Beehive::Worker') do
4
+ it('Create a new instance of Beehive::Worker') do
5
+ worker = Beehive::Worker.new
6
+
7
+ worker.options[:logger].level.should === Logger::WARN
8
+ worker.options[:jobs].size.should === 0
9
+ end
10
+
11
+ it('Start a worker and queue a job') do
12
+ path = File.expand_path('../../tmp/output', __FILE__)
13
+
14
+ if File.exist?(path)
15
+ File.unlink(path)
16
+ end
17
+
18
+ Beehive.job('spec') do |params|
19
+ File.open(path, 'w') do |handle|
20
+ handle.write('Hello, world!')
21
+ handle.close
22
+ end
23
+ end
24
+
25
+ # Queue the job
26
+ client = Beehive::Client.new
27
+ client.queue('spec')
28
+
29
+ pid = Process.fork do
30
+ worker = Beehive::Worker.new({}, {:jobs => ['spec']})
31
+ worker.work
32
+ end
33
+
34
+ # Wait for the process to end
35
+ Process.kill('INT', pid)
36
+ Process.waitpid(pid)
37
+
38
+ path = File.read(path, File.size(path)).strip
39
+
40
+ path.should === 'Hello, world!'
41
+ end
42
+
43
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,3 @@
1
+ require File.expand_path('../../lib/beehive', __FILE__)
2
+ require 'rspec'
3
+ require 'rspec/autorun'
data/spec/tmp/.gitkeep ADDED
File without changes
data/task/build.rake ADDED
@@ -0,0 +1,55 @@
1
+ require 'find'
2
+
3
+ namespace :build do
4
+
5
+ desc 'Builds the documentation using YARD'
6
+ task :doc do
7
+ gem_path = File.expand_path('../../', __FILE__)
8
+ command = "yard doc #{gem_path}/lib -m markdown -M rdiscount -o #{gem_path}/doc "
9
+ command += "-r #{gem_path}/README.md --private --protected "
10
+ command += "--files #{gem_path}/license.txt"
11
+
12
+ sh(command)
13
+ end
14
+
15
+ desc 'Builds a new Gem'
16
+ task :gem do
17
+ gem_path = File.expand_path('../../', __FILE__)
18
+
19
+ # Build and install the gem
20
+ sh("gem build #{gem_path}/beehive.gemspec")
21
+ sh("mv #{gem_path}/beehive-#{Beehive::Version}.gem #{gem_path}/pkg")
22
+ sh("gem install #{gem_path}/pkg/beehive-#{Beehive::Version}.gem")
23
+ end
24
+
25
+ desc 'Builds the MANIFEST file'
26
+ task :manifest do
27
+ gem_path = File.expand_path('../../', __FILE__)
28
+ ignore_exts = ['.gem', '.gemspec', '.swp']
29
+ ignore_files = ['.DS_Store', '.gitignore', 'output']
30
+ ignore_dirs = ['.git', '.yardoc', 'pkg', 'doc']
31
+ files = ''
32
+
33
+ Find.find(gem_path) do |f|
34
+ f[gem_path] = ''
35
+ f.gsub!(/^\//, '')
36
+
37
+ # Ignore directories
38
+ if !File.directory?(f) and !ignore_exts.include?(File.extname(f)) and !ignore_files.include?(File.basename(f))
39
+ files += "#{f}\n"
40
+ else
41
+ Find.prune if ignore_dirs.include?(f)
42
+ end
43
+ end
44
+
45
+ # Time to write the MANIFEST file
46
+ begin
47
+ handle = File.open 'MANIFEST', 'w'
48
+ handle.write files.strip
49
+ puts "The MANIFEST file has been updated."
50
+ rescue
51
+ abort "The MANIFEST file could not be written."
52
+ end
53
+ end
54
+
55
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: beehive
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "0.1"
6
+ platform: ruby
7
+ authors:
8
+ - Yorick Peterse
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-20 00:00:00 +02:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: redis
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 2.2.0
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.5.1
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rake
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.8.7
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 2.6.0
58
+ type: :development
59
+ version_requirements: *id004
60
+ description: Beehive is a lightweight queue system that uses Redis.
61
+ email: info@yorickpeterse.com
62
+ executables: []
63
+
64
+ extensions: []
65
+
66
+ extra_rdoc_files: []
67
+
68
+ files:
69
+ - .gemtest
70
+ - MANIFEST
71
+ - README.md
72
+ - Rakefile
73
+ - example/client.rb
74
+ - example/multiple-workers.rb
75
+ - example/worker.rb
76
+ - lib/.gitkeep
77
+ - lib/beehive/.gitkeep
78
+ - lib/beehive/client.rb
79
+ - lib/beehive/version.rb
80
+ - lib/beehive/worker.rb
81
+ - lib/beehive.rb
82
+ - license.txt
83
+ - spec/.gitkeep
84
+ - spec/.rspec
85
+ - spec/beehive/client.rb
86
+ - spec/beehive/worker.rb
87
+ - spec/helper.rb
88
+ - spec/tmp/.gitkeep
89
+ - task/build.rake
90
+ has_rdoc: yard
91
+ homepage: https://github.com/yorickpeterse/beehive/
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options: []
96
+
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: "0"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.6.2
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Beehive is a lightweight queue system that uses Redis.
118
+ test_files: []
119
+