beehive 0.1

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