pigeon 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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.gem
2
+
3
+ .DS_Store
4
+ *.tmproj
5
+ tmtags
6
+ *~
7
+ \#*
8
+ .\#*
9
+ *.swp
10
+ coverage
11
+ rdoc
12
+ pkg
13
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Scott Tadman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,107 @@
1
+ = pigeon
2
+
3
+ This is a simple framework for building EventMachine engines that are
4
+ constantly running. These are commonly used for background processing jobs,
5
+ batch processing, or for providing specific network services.
6
+
7
+ Installation should be as simple as:
8
+
9
+ gem install pigeon
10
+
11
+ Your first Pigeon engine can be defined quite simply:
12
+
13
+ class MyEngine < Pigeon::Engine
14
+ def self.included(engine)
15
+ engine.after_start do
16
+ # Operations to be performed after start
17
+ end
18
+ end
19
+ end
20
+
21
+ Other handlers can be defined:
22
+
23
+ after_initialize
24
+ before_start
25
+ after_start
26
+ before_stop
27
+ after_stop
28
+
29
+ A primary function of an engine might be to intermittently perform a task.
30
+ Several methods exist to facilitate this:
31
+
32
+ class MyEngine < Pigeon::Engine
33
+ def self.included(engine)
34
+ engine.after_start do
35
+ engine.periodically_trigger_task(10) do
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ Starting your application can be done with a wrapper script that is constructed somewhat like bin/launcher.example
42
+
43
+ An example would look like:
44
+
45
+ #!/usr/bin/env ruby
46
+
47
+ COMMAND_NAME = 'launcher'
48
+ engine = engine
49
+
50
+ options = {
51
+ :dir_mode => :normal,
52
+ :dir => engine.pid_dir,
53
+ :debug => true, # ((ENV['RAILS_ENV'] == 'production') ? ENV['PINGITY_DEBUG'] : true),
54
+ :modules => [ ]
55
+ }
56
+
57
+ begin
58
+ case (command)
59
+ when 'start'
60
+ engine.start(options) do |pid|
61
+ puts "Pigeon Engine now running. [%d]" % pid
62
+ end
63
+ when 'stop'
64
+ engine.stop(options) do |pid|
65
+ if (pid)
66
+ puts "Pigeon Engine shut down. [%d]" % pid
67
+ else
68
+ puts "Pigeon Engine was not running."
69
+ end
70
+ end
71
+ when 'restart'
72
+ engine.restart(options) do |old_pid, new_pid|
73
+ if (old_pid)
74
+ puts "Pigeon Engine terminated. [%d]" % old_pid
75
+ end
76
+ puts "Pigeon Engine now running. [%d]" % new_pid
77
+ end
78
+ when 'status'
79
+ engine.status(options) do |pid|
80
+ if (pid)
81
+ puts "Pigeon Engine running. [%d]" % pid
82
+ else
83
+ puts "Pigeon Engine is not running."
84
+ end
85
+ end
86
+ when 'run'
87
+ options[:logger] = Pigeon::Logger.new(STDOUT)
88
+
89
+ engine.run(options) do |pid|
90
+ puts "Pigeon Engine now running. [%d]" % pid
91
+ puts "Use ^C to terminate."
92
+ end
93
+ else
94
+ puts "Usage: #{COMMAND_NAME} [start|stop|restart|status|run]"
95
+ end
96
+ rescue Interrupt
97
+ puts "Shutting down."
98
+ exit(0)
99
+ end
100
+
101
+ == Status
102
+
103
+ This engine is currently in development.
104
+
105
+ == Copyright
106
+
107
+ Copyright (c) 2010 Scott Tadman, The Working Group
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pigeon"
8
+ gem.summary = %Q{Simple daemonized EventMachine engine framework with plug-in support}
9
+ gem.description = %Q{Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.}
10
+ gem.email = "github@tadman.ca"
11
+ gem.homepage = "http://github.com/tadman/pigeon"
12
+ gem.authors = %w[ tadman ]
13
+ gem.add_development_dependency 'eventmachine'
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/test_*.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
+
48
+ rdoc.rdoc_dir = 'rdoc'
49
+ rdoc.title = "pigeon #{version}"
50
+ rdoc.rdoc_files.include('README*')
51
+ rdoc.rdoc_files.include('lib/**/*.rb')
52
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ COMMAND_NAME = 'launcher'
4
+ engine = engine
5
+
6
+ options = {
7
+ :dir_mode => :normal,
8
+ :dir => engine.pid_dir,
9
+ :debug => true, # ((ENV['RAILS_ENV'] == 'production') ? ENV['PINGITY_DEBUG'] : true),
10
+ :modules => [ ]
11
+ }
12
+
13
+ begin
14
+ case (command)
15
+ when 'start'
16
+ engine.start(options) do |pid|
17
+ puts "Pigeon Engine now running. [%d]" % pid
18
+ end
19
+ when 'stop'
20
+ engine.stop(options) do |pid|
21
+ if (pid)
22
+ puts "Pigeon Engine shut down. [%d]" % pid
23
+ else
24
+ puts "Pigeon Engine was not running."
25
+ end
26
+ end
27
+ when 'restart'
28
+ engine.restart(options) do |old_pid, new_pid|
29
+ if (old_pid)
30
+ puts "Pigeon Engine terminated. [%d]" % old_pid
31
+ end
32
+ puts "Pigeon Engine now running. [%d]" % new_pid
33
+ end
34
+ when 'status'
35
+ engine.status(options) do |pid|
36
+ if (pid)
37
+ puts "Pigeon Engine running. [%d]" % pid
38
+ else
39
+ puts "Pigeon Engine is not running."
40
+ end
41
+ end
42
+ when 'run'
43
+ options[:logger] = Pigeon::Logger.new(STDOUT)
44
+
45
+ engine.run(options) do |pid|
46
+ puts "Pigeon Engine now running. [%d]" % pid
47
+ puts "Use ^C to terminate."
48
+ end
49
+ else
50
+ puts "Usage: #{COMMAND_NAME} [start|stop|restart|status|run]"
51
+ end
52
+ rescue Interrupt
53
+ puts "Shutting down."
54
+ exit(0)
55
+ end
data/lib/pigeon.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Pigeon
2
+ autoload(:Engine, 'pigeon/engine')
3
+ autoload(:Queue, 'pigeon/queue')
4
+ autoload(:Pidfile, 'pigeon/pidfile')
5
+ autoload(:Support, 'pigeon/support')
6
+ autoload(:Logger, 'pigeon/logger')
7
+ end
@@ -0,0 +1,246 @@
1
+ require 'eventmachine'
2
+ require 'socket'
3
+
4
+ class Pigeon::Engine
5
+ # == Submodules ===========================================================
6
+
7
+ class RuntimeError < Exception
8
+ end
9
+
10
+ # == Properties ===========================================================
11
+
12
+ attr_reader :logger
13
+
14
+ # == Constants ============================================================
15
+
16
+ CHAINS = %w[
17
+ after_initialize
18
+ before_start
19
+ after_start
20
+ before_stop
21
+ after_stop
22
+ ].collect(&:to_sym).freeze
23
+
24
+ PID_DIR = [
25
+ File.expand_path(File.join(*%w[ .. .. .. .. shared run ]), File.dirname(__FILE__)),
26
+ '/var/run',
27
+ '/tmp'
28
+ ].find { |path| File.exist?(path) and File.writable?(path) }
29
+
30
+ LOG_DIR = [
31
+ File.expand_path(File.join(*%w[ .. .. .. .. shared log ] ), File.dirname(__FILE__)),
32
+ File.expand_path(File.join(*%w[ .. .. log ]), File.dirname(__FILE__)),
33
+ '/tmp'
34
+ ].find { |path| File.exist?(path) and File.writable?(path) }
35
+
36
+ DEFAULT_OPTIONS = {
37
+ :pid_file => File.expand_path('pigeon-engine.pid', PID_DIR)
38
+ }.freeze
39
+
40
+ # == Class Methods ========================================================
41
+
42
+ def self.options_with_defaults(options = nil)
43
+ options ? DEFAULT_OPTIONS.merge(options) : DEFAULT_OPTIONS
44
+ end
45
+
46
+ def self.launch_with_options(options = nil)
47
+ EventMachine.run do
48
+ new(options_with_defaults(options)).run
49
+ end
50
+ end
51
+
52
+ def self.pid_dir
53
+ PID_DIR
54
+ end
55
+
56
+ def self.pid_file(options = nil)
57
+ Pigeon::Pidfile.new(options_with_defaults(options)[:pid_file])
58
+ end
59
+
60
+ def self.start(options = nil)
61
+ pid = Pigeon::Support.daemonize do
62
+ launch_with_options(options)
63
+ end
64
+
65
+ pid_file(options).create!(pid)
66
+ yield(pid.to_i)
67
+ end
68
+
69
+ def self.run(options = nil)
70
+ yield($$)
71
+ launch_with_options((options || { }).merge(:foreground => true))
72
+ end
73
+
74
+ def self.stop(options = nil)
75
+ pf = pid_file(options)
76
+ pid = pf.contents
77
+
78
+ if (pid)
79
+ begin
80
+ Process.kill('QUIT', pid)
81
+ rescue Errno::ESRCH
82
+ # No such process exception
83
+ pid = nil
84
+ end
85
+ pf.remove!
86
+ end
87
+
88
+ yield(pid)
89
+ end
90
+
91
+ def self.restart(options = nil)
92
+ self.stop(options)
93
+ self.start(options)
94
+ end
95
+
96
+ def self.status(options = nil)
97
+ yield(pid_file(options).contents)
98
+ end
99
+
100
+ def self.sql_logger
101
+ f = File.open(File.expand_path("query.log", LOG_DIR), 'w+')
102
+ f.sync = true
103
+
104
+ Pigeon::Logger.new(f)
105
+ end
106
+
107
+ def self.log_dir
108
+ LOG_DIR
109
+ end
110
+
111
+ # == Instance Methods =====================================================
112
+
113
+ def initialize(options = nil)
114
+ @options = options || { }
115
+
116
+ @task_lock = { }
117
+
118
+ @logger = @options[:logger] || Pigeon::Logger.new(File.open(File.expand_path('engine.log', LOG_DIR), 'w+'))
119
+
120
+ @logger.level = Pigeon::Logger::DEBUG if (@options[:debug])
121
+
122
+ @queue = { }
123
+
124
+ run_chain(:after_initialize)
125
+ end
126
+
127
+ def run
128
+ run_chain(:before_start)
129
+
130
+ STDOUT.sync = true
131
+
132
+ @logger.info("Engine \##{id} Running")
133
+
134
+ run_chain(:after_start)
135
+ end
136
+
137
+ def host
138
+ Socket.gethostname
139
+ end
140
+
141
+ def id
142
+ @id ||= '%8x%8x' % [ Time.now.to_i, rand(1 << 32) ]
143
+ end
144
+
145
+ # Used to periodically execute a task or block. When giving a task name,
146
+ # a method by that name is called, otherwise a block must be supplied.
147
+ # An interval can be specified in seconds, or will default to 1.
148
+ def periodically_trigger_task(task_name = nil, interval = 1, &block)
149
+ periodically(interval) do
150
+ trigger_task(task_name, &block)
151
+ end
152
+ end
153
+
154
+ # This acts as a lock to prevent over-lapping calls to the same method.
155
+ # While the first call is in progress, all subsequent calls will be ignored.
156
+ def trigger_task(task_name = nil, &block)
157
+ task_lock(task_name || block) do
158
+ block_given? ? yield : send(task_name)
159
+ end
160
+ end
161
+
162
+ def task_lock(task_name)
163
+ # NOTE: This is a somewhat naive locking mechanism that may break down
164
+ # when two requests are fired off within a nearly identical period.
165
+ # For now, this achieves a general purpose solution that should work
166
+ # under most circumstances. Refactor later to improve.
167
+
168
+ return if (@task_lock[task_name])
169
+
170
+ @task_lock[task_name] = true
171
+
172
+ yield if (block_given?)
173
+
174
+ @task_lock[task_name] = false
175
+ end
176
+
177
+ def timer(interval, &block)
178
+ EventMachine::Timer.new(interval, &block)
179
+ end
180
+
181
+ # Periodically calls a block. No check is performed to see if the block is
182
+ # already executing.
183
+ def periodically(interval, &block)
184
+ EventMachine::PeriodicTimer.new(interval, &block)
185
+ end
186
+
187
+ # Used to defer a block of work for near-immediate execution. Uses the
188
+ # EventMachine#defer method but is not as efficient as the queue method.
189
+ def defer(&block)
190
+ EventMachine.defer(&block)
191
+ end
192
+
193
+ # Shuts down the engine.
194
+ def terminate
195
+ EventMachine.stop_event_loop
196
+ end
197
+
198
+ # Used to queue a block for immediate processing on a background thread.
199
+ # An optional queue name can be used to sequence tasks properly.
200
+ def queue(name = :default, &block)
201
+ target_queue = @queue[name] ||= Pigeon::Queue.new(name == :default ? nil : 1)
202
+
203
+ target_queue.perform(&block)
204
+ end
205
+
206
+ class << self
207
+ CHAINS.each do |chain_name|
208
+ define_method(chain_name) do |&block|
209
+ chain_iv = :"@_#{chain_name}_chain"
210
+ instance_variable_set(chain_iv, [ ]) unless (instance_variable_get(chain_iv))
211
+
212
+ chain = instance_variable_get(chain_iv)
213
+
214
+ unless (chain)
215
+ chain = [ ]
216
+ instance_variable_set(chain_iv, chain)
217
+ end
218
+
219
+ chain << block
220
+ end
221
+ end
222
+
223
+ def run_chain(chain_name, instance)
224
+ chain = instance_variable_get(:"@_#{chain_name}_chain")
225
+
226
+ return unless (chain)
227
+
228
+ chain.each do |proc|
229
+ instance.instance_eval(&proc)
230
+ end
231
+ end
232
+ end
233
+
234
+ def debug?
235
+ !!@options[:debug]
236
+ end
237
+
238
+ def foreground?
239
+ !!@options[:foreground]
240
+ end
241
+
242
+ protected
243
+ def run_chain(chain_name)
244
+ self.class.run_chain(chain_name, self)
245
+ end
246
+ end
@@ -0,0 +1,15 @@
1
+ require 'logger'
2
+
3
+ class Pigeon::Logger < Logger
4
+ # Returns a sequential thread identifier which is human readable and much
5
+ # more concise than internal numbering system used.
6
+ def thread_id
7
+ @threads ||= { }
8
+ @threads[Thread.current.object_id] ||= @threads.length
9
+ end
10
+
11
+ # Over-rides the default log format.
12
+ def format_message(severity, datetime, progname, msg)
13
+ "[%s %6d] %s\n" % [ datetime.strftime("%Y-%m-%d %H:%M:%S"), thread_id, msg ]
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ class Pigeon::Pidfile
2
+ # == Constants ============================================================
3
+
4
+ # == Class Methods ========================================================
5
+
6
+ # == Instance Methods =====================================================
7
+
8
+ def initialize(path)
9
+ @path = path
10
+
11
+ @path += '.pid' unless (@path.match(/\./))
12
+ end
13
+
14
+ def contents
15
+ File.read(@path).to_i
16
+ rescue Errno::ENOENT
17
+ nil
18
+ end
19
+
20
+ def create!(pid = nil)
21
+ open(@path, 'w') do |fh|
22
+ fh.puts pid || $$
23
+ end
24
+ end
25
+
26
+ def remove!
27
+ return unless (exists?)
28
+
29
+ File.unlink(@path)
30
+ end
31
+
32
+ def exists?
33
+ File.exist?(@path)
34
+ end
35
+ end
@@ -0,0 +1,66 @@
1
+ class Pigeon::Queue
2
+ # == Constants ============================================================
3
+
4
+ DEFAULT_CONCURRENCY_LIMIT = 24
5
+
6
+ # == Properties ===========================================================
7
+
8
+ attr_reader :exceptions
9
+
10
+ # == Class Methods ========================================================
11
+
12
+ # == Instance Methods =====================================================
13
+
14
+ def initialize(limit = nil)
15
+ @limit = limit || DEFAULT_CONCURRENCY_LIMIT
16
+ @blocks = [ ]
17
+ @threads = [ ]
18
+ @exceptions = [ ]
19
+ end
20
+
21
+ def perform(*args, &block)
22
+ @blocks << [ block, args, caller(0) ]
23
+
24
+ if (@threads.length < @limit and @threads.length < @blocks.length)
25
+ create_thread
26
+ end
27
+ end
28
+
29
+ def empty?
30
+ @blocks.empty? and @threads.empty?
31
+ end
32
+
33
+ def exceptions?
34
+ !@exceptions.empty?
35
+ end
36
+
37
+ def length
38
+ @blocks.length
39
+ end
40
+
41
+ def threads
42
+ @threads.length
43
+ end
44
+
45
+ protected
46
+ def create_thread
47
+ @threads << Thread.new do
48
+ Thread.current.abort_on_exception = true
49
+
50
+ begin
51
+ while (block = @blocks.pop)
52
+ begin
53
+ block[0].call(*block[1])
54
+ rescue Object => e
55
+ puts "#{e.class}: #{e} #{e.backtrace.join("\n")}"
56
+ @exceptions << e
57
+ end
58
+
59
+ Thread.pass
60
+ end
61
+ ensure
62
+ @threads.delete(Thread.current)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,21 @@
1
+ module Pigeon::Support
2
+ def self.daemonize
3
+ rfd, wfd = IO.pipe
4
+
5
+ forked_pid = fork do
6
+ daemon_pid = fork do
7
+ yield
8
+ end
9
+
10
+ wfd.puts daemon_pid
11
+ wfd.flush
12
+ wfd.close
13
+ end
14
+
15
+ Process.wait(forked_pid)
16
+
17
+ daemon_pid = rfd.readline
18
+
19
+ daemon_pid.to_i
20
+ end
21
+ end
data/pigeon.gemspec ADDED
@@ -0,0 +1,66 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pigeon}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["tadman"]
12
+ s.date = %q{2010-10-01}
13
+ s.default_executable = %q{launcher.example}
14
+ s.description = %q{Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.}
15
+ s.email = %q{github@tadman.ca}
16
+ s.executables = ["launcher.example"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.rdoc"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/launcher.example",
29
+ "lib/pigeon.rb",
30
+ "lib/pigeon/engine.rb",
31
+ "lib/pigeon/logger.rb",
32
+ "lib/pigeon/pidfile.rb",
33
+ "lib/pigeon/queue.rb",
34
+ "lib/pigeon/support.rb",
35
+ "pigeon.gemspec",
36
+ "test/helper.rb",
37
+ "test/test_pigeon.rb",
38
+ "test/test_pigeon_engine.rb",
39
+ "test/test_pigeon_queue.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/tadman/pigeon}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.7}
45
+ s.summary = %q{Simple daemonized EventMachine engine framework with plug-in support}
46
+ s.test_files = [
47
+ "test/helper.rb",
48
+ "test/test_pigeon.rb",
49
+ "test/test_pigeon_engine.rb",
50
+ "test/test_pigeon_queue.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
55
+ s.specification_version = 3
56
+
57
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
58
+ s.add_development_dependency(%q<eventmachine>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<eventmachine>, [">= 0"])
61
+ end
62
+ else
63
+ s.add_dependency(%q<eventmachine>, [">= 0"])
64
+ end
65
+ end
66
+
data/test/helper.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.expand_path(*%w[ .. lib ]), File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+
7
+ require 'pigeon'
8
+
9
+ class Test::Unit::TestCase
10
+ # ...
11
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
2
+
3
+ class TestPigeon < Test::Unit::TestCase
4
+ def test_load_module
5
+ assert Pigeon
6
+ end
7
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
2
+
3
+ module TestModule
4
+ def self.included(engine)
5
+ engine.after_start do
6
+ notify_was_started
7
+ end
8
+ end
9
+ end
10
+
11
+ class TestEngine < Pigeon::Engine
12
+ include TestModule
13
+
14
+ def notify_was_started
15
+ pipe = @options[:pipe]
16
+
17
+ pipe.puts("STARTED")
18
+ pipe.flush
19
+ pipe.close
20
+ end
21
+ end
22
+
23
+ class TestPigeonEngine < Test::Unit::TestCase
24
+ def test_create_subclass
25
+ engine_pid = nil
26
+
27
+ read_fd, write_fd = IO.pipe
28
+
29
+ TestEngine.start(:pipe => write_fd) do |pid|
30
+ assert pid
31
+ engine_pid = pid
32
+ end
33
+
34
+ Timeout::timeout(5) do
35
+ assert_equal "STARTED\n", read_fd.readline
36
+ end
37
+
38
+ TestEngine.status do |pid|
39
+ assert_equal engine_pid, pid
40
+ end
41
+
42
+ TestEngine.stop do |pid|
43
+ assert_equal engine_pid, pid
44
+ end
45
+
46
+ TestEngine.status do |pid|
47
+ assert_equal nil, pid
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonQueueTest < Test::Unit::TestCase
4
+ def test_simple_queue
5
+ queue = Pigeon::Queue.new
6
+
7
+ checks = { }
8
+
9
+ count = 1000
10
+
11
+ count.times do |n|
12
+ queue.perform do
13
+ x = 0
14
+ 10_000.times { x += 1 }
15
+ checks[n] = true
16
+ end
17
+ end
18
+
19
+ while (!queue.empty?)
20
+ sleep(1)
21
+ end
22
+
23
+ assert queue.empty?
24
+ assert_equal [ ], queue.exceptions
25
+ assert !queue.exceptions?
26
+
27
+ assert_equal (0..count - 1).to_a, checks.keys.sort
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pigeon
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - tadman
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-01 00:00:00 -04:00
18
+ default_executable: launcher.example
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: eventmachine
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.
34
+ email: github@tadman.ca
35
+ executables:
36
+ - launcher.example
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - LICENSE
41
+ - README.rdoc
42
+ files:
43
+ - .document
44
+ - .gitignore
45
+ - LICENSE
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION
49
+ - bin/launcher.example
50
+ - lib/pigeon.rb
51
+ - lib/pigeon/engine.rb
52
+ - lib/pigeon/logger.rb
53
+ - lib/pigeon/pidfile.rb
54
+ - lib/pigeon/queue.rb
55
+ - lib/pigeon/support.rb
56
+ - pigeon.gemspec
57
+ - test/helper.rb
58
+ - test/test_pigeon.rb
59
+ - test/test_pigeon_engine.rb
60
+ - test/test_pigeon_queue.rb
61
+ has_rdoc: true
62
+ homepage: http://github.com/tadman/pigeon
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --charset=UTF-8
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.3.7
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Simple daemonized EventMachine engine framework with plug-in support
93
+ test_files:
94
+ - test/helper.rb
95
+ - test/test_pigeon.rb
96
+ - test/test_pigeon_engine.rb
97
+ - test/test_pigeon_queue.rb