Skip to content

Commit 6792e8c

Browse files
authored
Fix APNs P8 HTTP2 error recovery (#734)
1 parent e5688a2 commit 6792e8c

File tree

5 files changed

+95
-3
lines changed

5 files changed

+95
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Support for proxying HTTP requests [\#728](https://github.com/rpush/rpush/pull/728) ([jaspreet-3911](https://github.com/jaspreet-3911))
66
* Fix active record logger dependency for older rails versions that was breaking the build [\#731](https://github.com/rpush/rpush/pull/731) ([SixiS](https://github.com/sixis))
77
* Specify ostruct dependency ([not bundled with ruby since 3.5](https://github.com/ruby/ruby/blob/4eaa245fccd3dd9a61fe1b5f114a6fb47907640a/lib/bundled_gems.rb#L18)) [\#740](https://github.com/rpush/rpush/pull/740) ([grekko-headacy](https://github.com/grekko-headacy))
8+
* Fix APNs P8 HTTP2 error recovery [\#734](https://github.com/rpush/rpush/pull/734) ([kjvarga](https://github.com/kjvarga)) ([SixiS](https://github.com/SixiS))
89

910
[Full Changelog](https://github.com/rpush/rpush/compare/v9.2.0...HEAD)
1011

lib/rpush/daemon/apnsp8/delivery.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,17 @@ def perform
2525

2626
# Send all preprocessed requests at once
2727
@client.join(timeout: CLIENT_JOIN_TIMEOUT)
28+
error = @client.check_for_error
29+
raise error if error
2830
rescue NetHttp2::AsyncRequestTimeout => error
2931
mark_batch_retryable(Time.now + 10.seconds, error)
3032
@client.close
3133
raise
32-
rescue Errno::ECONNREFUSED, SocketError, HTTP2::Error::StreamLimitExceeded => error
34+
rescue OpenSSL::SSL::SSLError,
35+
Errno::ECONNRESET,
36+
Errno::ECONNREFUSED,
37+
SocketError,
38+
HTTP2::Error::StreamLimitExceeded => error
3339
# TODO restart connection when StreamLimitExceeded
3440
mark_batch_retryable(Time.now + 10.seconds, error)
3541
raise

lib/rpush/daemon/dispatcher/apnsp8_http2.rb

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,34 @@ def cleanup
3535
def create_http2_client(app)
3636
url = URLS[app.environment.to_sym]
3737
options = { connect_timeout: DEFAULT_TIMEOUT }
38-
3938
configure_proxy(options)
40-
4139
client = NetHttp2::Client.new(url, options)
40+
41+
client.instance_eval do
42+
@error_mutex = Mutex.new
43+
@error = nil
44+
45+
def record_error(error)
46+
@error_mutex.synchronize { @error = error }
47+
end
48+
49+
def check_for_error
50+
@error_mutex.synchronize do
51+
return unless @error
52+
53+
error = @error.dup
54+
@error = nil
55+
error
56+
end
57+
end
58+
end
59+
4260
client.on(:error) do |error|
61+
client.record_error(error)
4362
log_error(error)
4463
reflect(:error, error)
4564
end
65+
4666
client
4767
end
4868
end

spec/unit/daemon/apnsp8/delivery_spec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,16 @@
4949
end
5050
end
5151
end
52+
53+
context 'with an HTTP2 client that raises Errno::ECONNRESET' do
54+
before do
55+
allow(http2_client).to receive(:call_async).and_raise(Errno::ECONNRESET)
56+
end
57+
58+
it 'marks the batch as retryable' do
59+
expect(batch).to receive(:mark_all_retryable).with(anything, anything)
60+
expect { delivery.perform }.to raise_error(Errno::ECONNRESET)
61+
end
62+
end
5263
end
5364
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require 'unit_spec_helper'
2+
3+
describe Rpush::Daemon::Dispatcher::Apnsp8Http2 do
4+
let(:app) { double(environment: "sandbox", apn_key: "my_key") }
5+
let(:delivery_class) { double }
6+
let(:notification) { double }
7+
let(:batch) { double(mark_delivered: nil, all_processed: nil) }
8+
let(:http2) { double(on: true) }
9+
let!(:token_provider) { double }
10+
let(:queue_payload) { Rpush::Daemon::QueuePayload.new(batch, notification) }
11+
let(:dispatcher) { described_class.new(app, delivery_class) }
12+
13+
it 'constructs a new persistent connection' do
14+
expect(NetHttp2::Client).to receive(:new).and_call_original
15+
described_class.new(app, delivery_class)
16+
end
17+
18+
describe 'dispatch' do
19+
before do
20+
allow(NetHttp2::Client).to receive_messages(new: http2)
21+
allow(Rpush::Daemon::Apnsp8::Token).to receive_messages(new: token_provider)
22+
end
23+
24+
it 'delivers the notification' do
25+
delivery = double
26+
expect(delivery_class).to receive(:new).with(app, http2, token_provider, batch).and_return(delivery)
27+
expect(delivery).to receive(:perform)
28+
dispatcher.dispatch(queue_payload)
29+
end
30+
end
31+
32+
describe 'error catching' do
33+
let(:dispatcher) { described_class.new(app, Rpush::Daemon::Apnsp8::Delivery) }
34+
let(:client) { dispatcher.instance_variable_get("@client") }
35+
let(:notification1) { double('Notification 1', data: {}, as_json: {}).as_null_object }
36+
37+
before do
38+
allow(batch).to receive(:each_notification) do |&blk|
39+
[notification1].each(&blk)
40+
end
41+
allow_any_instance_of(Rpush::Daemon::Apnsp8::Delivery).to receive(:prepare_headers).and_return({})
42+
allow(client).to receive(:socket_loop).and_raise(Errno::ECONNRESET)
43+
end
44+
45+
it 'records and raises errors' do
46+
expect(batch).to receive(:mark_all_retryable)
47+
expect(client).to receive(:record_error).with(Errno::ECONNRESET).and_call_original
48+
payload = Rpush::Daemon::QueuePayload.new(batch, notification)
49+
expect do
50+
dispatcher.dispatch(payload)
51+
end.to raise_error(Errno::ECONNRESET)
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy