forked from copycopter/copycopter-ruby-client
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extracted process guards into a separate class
- Loading branch information
Showing
8 changed files
with
243 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
module CopycopterClient | ||
# Starts the sync from a worker process, or register hooks for a spawner | ||
# process (such as in Unicorn or Passenger). Also registers hooks for exiting | ||
# processes and completing background jobs. Applications using the client | ||
# will not need to interact with this class directly. | ||
class ProcessGuard | ||
# @param options [Hash] | ||
# @option options [Logger] :logger where errors should be logged | ||
def initialize(sync, options) | ||
@sync = sync | ||
@logger = options[:logger] | ||
end | ||
|
||
# Starts the sync or registers hooks | ||
def start | ||
if spawner? | ||
register_spawn_hooks | ||
else | ||
register_exit_hooks | ||
register_job_hooks | ||
start_sync | ||
end | ||
end | ||
|
||
private | ||
|
||
def start_sync | ||
@sync.start | ||
end | ||
|
||
def spawner? | ||
passenger_spawner? || unicorn_spawner? | ||
end | ||
|
||
def passenger_spawner? | ||
$0.include?("ApplicationSpawner") | ||
end | ||
|
||
def unicorn_spawner? | ||
$0.include?("unicorn") && !caller.any? { |line| line.include?("worker_loop") } | ||
end | ||
|
||
def register_spawn_hooks | ||
if defined?(PhusionPassenger) | ||
register_passenger_hook | ||
elsif defined?(Unicorn::HttpServer) | ||
register_unicorn_hook | ||
end | ||
end | ||
|
||
def register_passenger_hook | ||
@logger.info("Registered Phusion Passenger fork hook") | ||
PhusionPassenger.on_event(:starting_worker_process) do |forked| | ||
start_sync | ||
end | ||
end | ||
|
||
def register_unicorn_hook | ||
@logger.info("Registered Unicorn fork hook") | ||
sync = @sync | ||
Unicorn::HttpServer.class_eval do | ||
alias_method :worker_loop_without_copycopter, :worker_loop | ||
define_method :worker_loop do |worker| | ||
sync.start | ||
worker_loop_without_copycopter(worker) | ||
end | ||
end | ||
end | ||
|
||
def register_exit_hooks | ||
at_exit do | ||
@sync.flush | ||
end | ||
end | ||
|
||
def register_job_hooks | ||
if defined?(Resque) | ||
@logger.info("Registered Resque after_perform hook") | ||
sync = @sync | ||
Resque::Job.class_eval do | ||
alias_method :perform_without_copycopter, :perform | ||
define_method :perform do | ||
job_was_performed = perform_without_copycopter | ||
sync.flush | ||
job_was_performed | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
require 'spec_helper' | ||
|
||
describe CopycopterClient::ProcessGuard do | ||
include DefinesConstants | ||
|
||
before do | ||
@original_process_name = $0 | ||
end | ||
|
||
after do | ||
$0 = @original_process_name | ||
end | ||
|
||
let(:sync) { stub('sync', :start => nil, :flush => nil) } | ||
|
||
def build_process_guard(options = {}) | ||
options[:logger] ||= FakeLogger.new | ||
CopycopterClient::ProcessGuard.new(sync, options) | ||
end | ||
|
||
it "starts polling from a worker process" do | ||
process_guard = build_process_guard | ||
|
||
process_guard.start | ||
|
||
sync.should have_received(:start) | ||
end | ||
|
||
it "registers passenger hooks from the passenger master" do | ||
logger = FakeLogger.new | ||
passenger = define_constant('PhusionPassenger', FakePassenger.new) | ||
passenger.become_master | ||
|
||
process_guard = build_process_guard(:logger => logger) | ||
process_guard.start | ||
|
||
logger.should have_entry(:info, "Registered Phusion Passenger fork hook") | ||
sync.should have_received(:start).never | ||
end | ||
|
||
it "starts polling from a passenger worker" do | ||
logger = FakeLogger.new | ||
passenger = define_constant('PhusionPassenger', FakePassenger.new) | ||
passenger.become_master | ||
process_guard = build_process_guard(:logger => logger) | ||
|
||
process_guard.start | ||
passenger.spawn | ||
|
||
sync.should have_received(:start) | ||
end | ||
|
||
it "registers unicorn hooks from the unicorn master" do | ||
logger = FakeLogger.new | ||
define_constant('Unicorn', Module.new) | ||
http_server = Class.new(FakeUnicornServer) | ||
unicorn = define_constant('Unicorn::HttpServer', http_server).new | ||
unicorn.become_master | ||
|
||
process_guard = build_process_guard(:logger => logger) | ||
process_guard.start | ||
|
||
logger.should have_entry(:info, "Registered Unicorn fork hook") | ||
sync.should have_received(:start).never | ||
end | ||
|
||
it "starts polling from a unicorn worker" do | ||
logger = FakeLogger.new | ||
define_constant('Unicorn', Module.new) | ||
http_server = Class.new(FakeUnicornServer) | ||
unicorn = define_constant('Unicorn::HttpServer', http_server).new | ||
unicorn.become_master | ||
process_guard = build_process_guard(:logger => logger) | ||
|
||
process_guard.start | ||
unicorn.spawn | ||
|
||
sync.should have_received(:start) | ||
end | ||
|
||
it "flushes when the process terminates" do | ||
api_key = "12345" | ||
FakeCopycopterApp.add_project api_key | ||
pid = fork do | ||
config = { :logger => FakeLogger.new, :polling_delay => 86400, :api_key => api_key } | ||
default_config = CopycopterClient::Configuration.new.to_hash.update(config) | ||
client = CopycopterClient::Client.new(default_config) | ||
sync = CopycopterClient::Sync.new(client, default_config) | ||
process_guard = CopycopterClient::ProcessGuard.new(sync, default_config) | ||
process_guard.start | ||
sync['test.key'] = 'value' | ||
Signal.trap("INT") { exit } | ||
sleep | ||
end | ||
sleep(0.5) | ||
Process.kill("INT", pid) | ||
Process.wait | ||
project = FakeCopycopterApp.project(api_key) | ||
project.draft['test.key'].should == 'value' | ||
end | ||
|
||
it "flushes after running a resque job" do | ||
define_constant('Resque', Module.new) | ||
job_class = define_constant('Resque::Job', FakeResqueJob) | ||
|
||
api_key = "12345" | ||
FakeCopycopterApp.add_project api_key | ||
logger = FakeLogger.new | ||
|
||
config = { :logger => logger, :polling_delay => 86400, :api_key => api_key } | ||
default_config = CopycopterClient::Configuration.new.to_hash.update(config) | ||
client = CopycopterClient::Client.new(default_config) | ||
sync = CopycopterClient::Sync.new(client, default_config) | ||
job = job_class.new { sync["test.key"] = "expected value" } | ||
process_guard = CopycopterClient::ProcessGuard.new(sync, default_config) | ||
|
||
process_guard.start | ||
|
||
if fork | ||
Process.wait | ||
else | ||
job.perform | ||
exit! | ||
end | ||
|
||
project = FakeCopycopterApp.project(api_key) | ||
project.draft['test.key'].should == 'expected value' | ||
logger.should have_entry(:info, "Registered Resque after_perform hook") | ||
end | ||
end |
Oops, something went wrong.