rapns 0.1.4 → 0.2.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/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