rapns 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/rapns CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "optparse"
4
- require "rapns"
5
- require "rapns/daemon"
3
+ require 'optparse'
4
+ require 'rapns'
5
+ require 'rapns/daemon'
6
6
 
7
7
  foreground = false
8
8
  environment = ARGV[0]
9
- banner = "Usage: rapns <Rails environment> [options]"
9
+ banner = 'Usage: rapns <Rails environment> [options]'
10
10
  ARGV.options do |opts|
11
11
  opts.banner = banner
12
- opts.on("-f", "--foreground", "Run in the foreground.") { foreground = true }
13
- opts.on("-v", "--version", "Print this version of rapns.") { puts "rapns #{Rapns::VERSION}"; exit }
14
- opts.on("-h", "--help", "You're looking at it.") { puts opts; exit }
12
+ opts.on('-f', '--foreground', 'Run in the foreground.') { foreground = true }
13
+ opts.on('-v', '--version', 'Print this version of rapns.') { puts "rapns #{Rapns::VERSION}"; exit }
14
+ opts.on('-h', '--help', 'You\'re looking at it.') { puts opts; exit }
15
15
  opts.parse!
16
16
  end
17
17
 
@@ -20,7 +20,9 @@ if environment.nil?
20
20
  exit 1
21
21
  end
22
22
 
23
- ENV["RAILS_ENV"] = environment
24
- load "config/environment.rb"
23
+ ENV['RAILS_ENV'] = environment
24
+ load 'config/environment.rb'
25
+
26
+ require 'rapns/patches'
25
27
 
26
28
  Rapns::Daemon.start(environment, foreground)
@@ -1,3 +1,9 @@
1
+ class PGError < StandardError; end if !defined?(PGError)
2
+ module Mysql; class Error < StandardError; end; end if !defined?(Mysql)
3
+ module Mysql2; class Error < StandardError; end; end if !defined?(Mysql2)
4
+
5
+ ADAPTER_ERRORS = [PGError, Mysql::Error, Mysql2::Error]
6
+
1
7
  module Rapns
2
8
  module Daemon
3
9
  class Feeder
@@ -15,6 +21,9 @@ module Rapns
15
21
  end
16
22
 
17
23
  Rapns::Daemon.delivery_queue.wait_until_empty
24
+ rescue ActiveRecord::StatementInvalid, *ADAPTER_ERRORS => e
25
+ Rapns::Daemon.logger.error(e)
26
+ reconnect
18
27
  rescue StandardError => e
19
28
  Rapns::Daemon.logger.error(e)
20
29
  end
@@ -25,6 +34,24 @@ module Rapns
25
34
  def self.stop
26
35
  @stop = true
27
36
  end
37
+
38
+ def self.reconnect
39
+ Rapns::Daemon.logger.warn('Lost connection to database, reconnecting...')
40
+ attempts = 0
41
+ loop do
42
+ begin
43
+ Rapns::Daemon.logger.warn("Attempt #{attempts += 1}")
44
+ ActiveRecord::Base.clear_all_connections!
45
+ ActiveRecord::Base.establish_connection
46
+ Rapns::Notification.count
47
+ break
48
+ rescue *ADAPTER_ERRORS => e
49
+ Rapns::Daemon.logger.error(e, :airbrake_notify => false)
50
+ sleep 2 # Avoid thrashing.
51
+ end
52
+ end
53
+ Rapns::Daemon.logger.warn('Database reconnected')
54
+ end
28
55
  end
29
56
  end
30
57
  end
@@ -3,7 +3,7 @@ module Rapns
3
3
  class Logger
4
4
  def initialize(options)
5
5
  @options = options
6
- log_path = File.join(Rails.root, "log", "rapns.log")
6
+ log_path = File.join(Rails.root, 'log', 'rapns.log')
7
7
  @logger = ActiveSupport::BufferedLogger.new(log_path, Rails.logger.level)
8
8
  @logger.auto_flushing = Rails.logger.auto_flushing
9
9
  end
@@ -12,13 +12,13 @@ module Rapns
12
12
  log(:info, msg)
13
13
  end
14
14
 
15
- def error(msg)
16
- airbrake_notify(msg) if msg.is_a?(Exception)
17
- log(:error, msg, "ERROR")
15
+ def error(msg, options = {})
16
+ airbrake_notify(msg) if msg.is_a?(Exception) && options[:airbrake_notify] != false
17
+ log(:error, msg, 'ERROR')
18
18
  end
19
19
 
20
20
  def warn(msg)
21
- log(:warn, msg, "WARNING")
21
+ log(:warn, msg, 'WARNING')
22
22
  end
23
23
 
24
24
  private
data/lib/rapns/daemon.rb CHANGED
@@ -2,17 +2,17 @@ require 'thread'
2
2
  require 'socket'
3
3
  require 'pathname'
4
4
 
5
- require "rapns/daemon/configuration"
6
- require "rapns/daemon/certificate"
7
- require "rapns/daemon/delivery_error"
8
- require "rapns/daemon/pool"
9
- require "rapns/daemon/connection_pool"
10
- require "rapns/daemon/connection"
11
- require "rapns/daemon/delivery_queue"
12
- require "rapns/daemon/delivery_handler"
13
- require "rapns/daemon/delivery_handler_pool"
14
- require "rapns/daemon/feeder"
15
- require "rapns/daemon/logger"
5
+ require 'rapns/daemon/configuration'
6
+ require 'rapns/daemon/certificate'
7
+ require 'rapns/daemon/delivery_error'
8
+ require 'rapns/daemon/pool'
9
+ require 'rapns/daemon/connection_pool'
10
+ require 'rapns/daemon/connection'
11
+ require 'rapns/daemon/delivery_queue'
12
+ require 'rapns/daemon/delivery_handler'
13
+ require 'rapns/daemon/delivery_handler_pool'
14
+ require 'rapns/daemon/feeder'
15
+ require 'rapns/daemon/logger'
16
16
 
17
17
  module Rapns
18
18
  module Daemon
@@ -26,7 +26,7 @@ module Rapns
26
26
  @foreground = foreground
27
27
  setup_signal_hooks
28
28
 
29
- self.configuration = Configuration.new(environment, File.join(Rails.root, "config", "rapns", "rapns.yml"))
29
+ self.configuration = Configuration.new(environment, File.join(Rails.root, 'config', 'rapns', 'rapns.yml'))
30
30
  configuration.load
31
31
 
32
32
  self.logger = Logger.new(:foreground => foreground, :airbrake_notify => configuration.airbrake_notify)
@@ -49,6 +49,7 @@ module Rapns
49
49
  self.connection_pool = ConnectionPool.new(configuration.connections)
50
50
  connection_pool.populate
51
51
 
52
+ logger.info("Ready")
52
53
  Feeder.start
53
54
  end
54
55
 
@@ -57,7 +58,7 @@ module Rapns
57
58
  def self.setup_signal_hooks
58
59
  @shutting_down = false
59
60
 
60
- ["SIGINT", "SIGTERM"].each do |signal|
61
+ ['SIGINT', 'SIGTERM'].each do |signal|
61
62
  Signal.trap(signal) do
62
63
  handle_shutdown_signal
63
64
  end
@@ -93,7 +94,7 @@ module Rapns
93
94
 
94
95
  def self.write_pid_file
95
96
  if !configuration.pid_file.blank?
96
- File.open(configuration.pid_file, "w") do |f|
97
+ File.open(configuration.pid_file, 'w') do |f|
97
98
  f.puts $$
98
99
  end
99
100
  end
@@ -0,0 +1,12 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ def clear_cache!
5
+ @statements.each_value do |value|
6
+ @connection.query "DEALLOCATE #{value}" if active?
7
+ end
8
+ @statements.clear
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter < AbstractAdapter
4
+ class StatementPool < ConnectionAdapters::StatementPool
5
+ def dealloc(key)
6
+ @connection.query "DEALLOCATE #{key}" if connection_active?
7
+ end
8
+
9
+ def connection_active?
10
+ @connection.status == PGconn::CONNECTION_OK
11
+ rescue PGError
12
+ false
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ if ActiveRecord::Base.configurations[Rails.env]['adapter'] == 'postgresql'
2
+ if Rails::VERSION::STRING == '3.1.0' || Rails::VERSION::STRING == '3.1.1'
3
+ STDERR.puts "[WARNING] Detected Rails #{Rails::VERSION::STRING}, patching PostgreSQLAdapter to fix reconnection bug: https://github.com/rails/rails/issues/3160"
4
+ require "rapns/patches/rails/#{Rails::VERSION::STRING}/postgresql_adapter.rb"
5
+ end
6
+ end
data/lib/rapns/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rapns
2
- VERSION = '0.1.4'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -4,7 +4,7 @@ describe Rapns::Daemon::Feeder do
4
4
  before do
5
5
  Rapns::Daemon::Feeder.stub(:sleep)
6
6
  @notification = Rapns::Notification.create!(:device_token => "a" * 64)
7
- @logger = mock("Logger", :info => nil, :error => nil)
7
+ @logger = mock("Logger", :info => nil, :error => nil, :warn => nil)
8
8
  Rapns::Daemon.stub(:logger).and_return(@logger)
9
9
  @queue = mock(:push => nil, :wait_until_empty => nil)
10
10
  Rapns::Daemon.stub(:delivery_queue).and_return(@queue)
@@ -63,4 +63,82 @@ describe Rapns::Daemon::Feeder do
63
63
  Rapns::Daemon.logger.should_receive(:error).with(e)
64
64
  Rapns::Daemon::Feeder.enqueue_notifications
65
65
  end
66
+
67
+ context "when the database connection is lost" do
68
+ let(:error) { adapter_error.new("db down!") }
69
+ before do
70
+ ActiveRecord::Base.stub(:clear_all_connections!)
71
+ ActiveRecord::Base.stub(:establish_connection)
72
+ Rapns::Notification.stub(:ready_for_delivery).and_raise(error)
73
+ end
74
+
75
+ def adapter_error
76
+ case $adapter
77
+ when 'postgresql'
78
+ PGError
79
+ when 'mysql'
80
+ Mysql::Error
81
+ when 'mysql2'
82
+ Mysql2::Error
83
+ else
84
+ raise "Please update #{__FILE__} for adapter #{$adapter}"
85
+ end
86
+ end
87
+
88
+ it "should log the error raised" do
89
+ Rapns::Daemon.logger.should_receive(:error).with(error)
90
+ Rapns::Daemon::Feeder.enqueue_notifications
91
+ end
92
+
93
+ it "should log that the database is being reconnected" do
94
+ Rapns::Daemon.logger.should_receive(:warn).with("Lost connection to database, reconnecting...")
95
+ Rapns::Daemon::Feeder.enqueue_notifications
96
+ end
97
+
98
+ it "should log the reconnection attempt" do
99
+ Rapns::Daemon.logger.should_receive(:warn).with("Attempt 1")
100
+ Rapns::Daemon::Feeder.enqueue_notifications
101
+ end
102
+
103
+ it "should clear all connections" do
104
+ ActiveRecord::Base.should_receive(:clear_all_connections!)
105
+ Rapns::Daemon::Feeder.enqueue_notifications
106
+ end
107
+
108
+ it "should establish a new connection" do
109
+ ActiveRecord::Base.should_receive(:establish_connection)
110
+ Rapns::Daemon::Feeder.enqueue_notifications
111
+ end
112
+
113
+ it "should test out the new connection by performing a count" do
114
+ Rapns::Notification.should_receive(:count)
115
+ Rapns::Daemon::Feeder.enqueue_notifications
116
+ end
117
+
118
+ context "when the reconnection attempt is not successful" do
119
+ let(:error) { adapter_error.new("shit got real") }
120
+
121
+ before do
122
+ class << Rapns::Notification
123
+ def count
124
+ @count_calls += 1
125
+ return if @count_calls == 2
126
+ raise @error
127
+ end
128
+ end
129
+ Rapns::Notification.instance_variable_set("@count_calls", 0)
130
+ Rapns::Notification.instance_variable_set("@error", error)
131
+ end
132
+
133
+ it "should log errors raised when the reconnection is not successful without notifying airbrake" do
134
+ Rapns::Daemon.logger.should_receive(:error).with(error, :airbrake_notify => false)
135
+ Rapns::Daemon::Feeder.enqueue_notifications
136
+ end
137
+
138
+ it "should sleep to avoid thrashing when the database is down" do
139
+ Rapns::Daemon::Feeder.should_receive(:sleep).with(2)
140
+ Rapns::Daemon::Feeder.enqueue_notifications
141
+ end
142
+ end
143
+ end
66
144
  end
@@ -88,6 +88,13 @@ describe Rapns::Daemon::Logger do
88
88
  logger.error(e)
89
89
  end
90
90
 
91
+ it "should not notify Airbrake if explicitly disabled in the call to error" do
92
+ e = RuntimeError.new("hi mom")
93
+ logger = Rapns::Daemon::Logger.new(:foreground => false, :airbrake_notify => true)
94
+ Airbrake.should_not_receive(:notify).with(e)
95
+ logger.error(e, :airbrake_notify => false)
96
+ end
97
+
91
98
  it "should not attempt to notify Airbrake of the error is not an Exception" do
92
99
  logger = Rapns::Daemon::Logger.new(:foreground => false)
93
100
  Airbrake.should_not_receive(:notify)
@@ -27,7 +27,7 @@ describe Rapns::Daemon, "when starting" do
27
27
  Rapns::Daemon::Feeder.stub(:wait)
28
28
  Rapns::Daemon.stub(:daemonize)
29
29
  Rapns::Daemon.stub(:write_pid_file)
30
- @logger = mock("Logger")
30
+ @logger = mock("Logger", :info => nil)
31
31
  Rapns::Daemon::Logger.stub(:new).and_return(@logger)
32
32
  end
33
33
 
data/spec/spec_helper.rb CHANGED
@@ -1,30 +1,45 @@
1
- ENV["RAILS_ENV"] = "test"
1
+ ENV['RAILS_ENV'] = 'test'
2
2
 
3
3
  require 'simplecov'
4
4
  SimpleCov.start do
5
5
  add_filter '/spec/'
6
6
  end
7
7
 
8
- require "active_record"
8
+ require 'active_record'
9
+ adapters = ['mysql', 'mysql2', 'postgresql']
10
+ $adapter = ENV['ADAPTER'] || 'postgresql'
9
11
 
10
- ActiveRecord::Base.establish_connection("adapter" => "postgresql", "database" => "rapns_test")
11
- require "generators/templates/create_rapns_notifications"
12
+ if !adapters.include?($adapter)
13
+ puts "No such adapter '#{$adapter}'. Valid adapters are #{adapters.join(', ')}."
14
+ exit 1
15
+ end
16
+
17
+ puts "Using #{$adapter} adapter."
18
+ ActiveRecord::Base.establish_connection('adapter' => $adapter, 'database' => 'rapns_test')
19
+ require 'generators/templates/create_rapns_notifications'
12
20
 
13
21
  CreateRapnsNotifications.down rescue ActiveRecord::StatementInvalid
14
22
  CreateRapnsNotifications.up
15
23
 
16
24
  Bundler.require(:default)
17
25
 
18
- require "shoulda"
26
+ require 'shoulda'
19
27
  require 'database_cleaner'
28
+
20
29
  DatabaseCleaner.strategy = :truncation
21
30
 
22
- require "rapns"
23
- require "rapns/daemon"
31
+ require 'rapns'
32
+ require 'rapns/daemon'
33
+
34
+ require 'perftools'
24
35
 
25
36
  RSpec.configure do |config|
37
+ # config.before :suite do
38
+ # PerfTools::CpuProfiler.start('/tmp/rapns_profile')
39
+ # end
40
+ # config.after :suite do
41
+ # PerfTools::CpuProfiler.stop
42
+ # end
43
+
26
44
  config.before(:each) { DatabaseCleaner.clean }
27
45
  end
28
-
29
-
30
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rapns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-08 00:00:00.000000000Z
12
+ date: 2011-10-09 00:00:00.000000000Z
13
13
  dependencies: []
14
14
  description: Easy to use library for Apple's Push Notification Service with Rails
15
15
  3
@@ -38,6 +38,9 @@ files:
38
38
  - lib/rapns/daemon/logger.rb
39
39
  - lib/rapns/daemon/pool.rb
40
40
  - lib/rapns/notification.rb
41
+ - lib/rapns/patches.rb
42
+ - lib/rapns/patches/rails/3.1.0/postgresql_adapter.rb
43
+ - lib/rapns/patches/rails/3.1.1/postgresql_adapter.rb
41
44
  - lib/rapns/version.rb
42
45
  - README.md
43
46
  - spec/rapns/daemon/certificate_spec.rb