lowdown 0.1.0 → 0.1.1
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.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/Rakefile +1 -0
- data/lib/lowdown/client.rb +12 -3
- data/lib/lowdown/connection.rb +39 -60
- data/lib/lowdown/mock.rb +15 -3
- data/lib/lowdown/threading.rb +126 -0
- data/lib/lowdown/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 044f0dfd72c7b5a984abac1cd844e06b71b1fac1
|
4
|
+
data.tar.gz: 68eae4bb641cf6d39edaf59681060f6b7ded8089
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65c0917420183c3408930d60645e00d54ffcf653bb3f28aa61aa00a8a85f623213652fbb7b8eded3c6524caa8025c57e488f8c5a58cd7786333e8b3eba0fed23
|
7
|
+
data.tar.gz: 2e20f00c914f2a95efff4a0a017f768004c892fdce49549b8aa269d30d2e431575c7b66e83103a4f3beb7af3a1c63dff6144955f6130e46b209677cbbf0df5f2
|
data/.yardopts
CHANGED
data/Rakefile
CHANGED
data/lib/lowdown/client.rb
CHANGED
@@ -160,8 +160,14 @@ module Lowdown
|
|
160
160
|
# @param [Notification] notification
|
161
161
|
# the notification object whose data to send to the service.
|
162
162
|
#
|
163
|
-
# @yield
|
164
|
-
#
|
163
|
+
# @yield [response, notification]
|
164
|
+
# called when the request is finished and a response is available.
|
165
|
+
#
|
166
|
+
# @yieldparam [Response] response
|
167
|
+
# the Response that holds the status data that came back from the service.
|
168
|
+
#
|
169
|
+
# @yieldparam [Notification] notification
|
170
|
+
# the originally passed in notification object.
|
165
171
|
#
|
166
172
|
# @raise [ArgumentError]
|
167
173
|
# raised if the Notification is not {Notification#valid?}.
|
@@ -180,7 +186,10 @@ module Lowdown
|
|
180
186
|
|
181
187
|
body = notification.formatted_payload.to_json
|
182
188
|
|
183
|
-
|
189
|
+
# No need to keep a strong reference to the notification object, unless the user really wants it.
|
190
|
+
actual_callback = callback.arity < 2 ? callback : lambda { |response| callback.call(response, notification) }
|
191
|
+
|
192
|
+
@connection.post("/3/device/#{notification.token}", headers, body, &actual_callback)
|
184
193
|
end
|
185
194
|
end
|
186
195
|
end
|
data/lib/lowdown/connection.rb
CHANGED
@@ -104,7 +104,7 @@ module Lowdown
|
|
104
104
|
Timeout.timeout(timeout) do
|
105
105
|
caller_thread = Thread.current
|
106
106
|
@worker.enqueue do |http|
|
107
|
-
http.ping('
|
107
|
+
http.ping('whatever') { caller_thread.run }
|
108
108
|
end
|
109
109
|
Thread.stop
|
110
110
|
end
|
@@ -120,7 +120,7 @@ module Lowdown
|
|
120
120
|
#
|
121
121
|
def flush
|
122
122
|
return unless @worker
|
123
|
-
sleep 0.1 until !@worker.
|
123
|
+
sleep 0.1 until !@worker.working? && @requests.zero?
|
124
124
|
end
|
125
125
|
|
126
126
|
# Sends the provided data as a `POST` request to the service.
|
@@ -137,10 +137,10 @@ module Lowdown
|
|
137
137
|
# the (JSON) encoded payload data to send to the service.
|
138
138
|
#
|
139
139
|
# @yield [response]
|
140
|
-
#
|
140
|
+
# called when the request is finished and a response is available.
|
141
141
|
#
|
142
142
|
# @yieldparam [Response] response
|
143
|
-
#
|
143
|
+
# the Response that holds the status data that came back from the service.
|
144
144
|
#
|
145
145
|
# @return [void]
|
146
146
|
#
|
@@ -169,7 +169,7 @@ module Lowdown
|
|
169
169
|
end
|
170
170
|
|
171
171
|
stream.on(:close) do
|
172
|
-
callbacks
|
172
|
+
callbacks.enqueue do
|
173
173
|
callback.call(response)
|
174
174
|
@requests.decrement!
|
175
175
|
end
|
@@ -187,75 +187,49 @@ module Lowdown
|
|
187
187
|
# * HTTP2 client
|
188
188
|
# * Another thread from where request callbacks are ran
|
189
189
|
#
|
190
|
-
class Worker <
|
190
|
+
class Worker < Threading::Consumer
|
191
191
|
def initialize(uri, ssl_context)
|
192
192
|
@uri, @ssl_context = uri, ssl_context
|
193
193
|
|
194
|
+
# Start the worker thread.
|
195
|
+
#
|
194
196
|
# Because a max size of 0 is not allowed, create with an initial max size of 1 and add a dummy job. This is so
|
195
197
|
# that any attempt to add a new job to the queue is going to halt the calling thread *until* we change the max.
|
196
|
-
|
197
|
-
@queue << lambda { |*_| }
|
198
|
-
|
199
|
-
# Setup the consumer that performs the callbacks passed to Connection#request
|
200
|
-
@callback_queue = Queue.new
|
201
|
-
@callback_thread = Thread.new(@callback_queue) { |q| loop { q.pop.call } }
|
202
|
-
|
203
|
-
# Store the caller thread to be able to resume it once connected and to send exceptions to.
|
204
|
-
@caller_thread = Thread.current
|
205
|
-
# Start the worker thread.
|
206
|
-
super(&method(:main))
|
198
|
+
super(queue: Thread::SizedQueue.new(1))
|
207
199
|
# Put caller thread into sleep until connected.
|
208
200
|
Thread.stop
|
209
201
|
end
|
210
202
|
|
211
|
-
#
|
212
|
-
#
|
213
|
-
# @yieldparam [HTTP2::Client] http
|
214
|
-
# the HTTP2 client instance.
|
215
|
-
#
|
216
|
-
# @yieldparam [Queue] callbacks_queue
|
217
|
-
# the queue on which request callbacks should be performed.
|
203
|
+
# Tells the runloop to stop and halts the caller until finished.
|
218
204
|
#
|
219
205
|
# @return [void]
|
220
206
|
#
|
221
|
-
def
|
222
|
-
|
207
|
+
def stop
|
208
|
+
thread[:should_exit] = true
|
209
|
+
thread.join
|
223
210
|
end
|
224
211
|
|
225
212
|
# @return [Boolean]
|
226
|
-
# whether or not the
|
227
|
-
#
|
228
|
-
def empty?
|
229
|
-
@queue.empty?
|
230
|
-
end
|
231
|
-
|
232
|
-
# Tells the runloop to stop and halts the caller until finished.
|
213
|
+
# whether or not the worker is still alive and kicking.
|
233
214
|
#
|
234
|
-
|
235
|
-
|
236
|
-
def stop
|
237
|
-
self[:should_exit] = true
|
238
|
-
join
|
215
|
+
def working?
|
216
|
+
alive? && !empty? && !@callbacks.empty?
|
239
217
|
end
|
240
218
|
|
241
219
|
private
|
242
220
|
|
243
|
-
def
|
244
|
-
|
245
|
-
runloop
|
246
|
-
rescue Exception => exception
|
247
|
-
# Send any unexpected exceptions back to the thread that started the loop.
|
248
|
-
@caller_thread.raise(exception)
|
249
|
-
ensure
|
250
|
-
cleanup
|
251
|
-
end
|
252
|
-
|
253
|
-
def cleanup
|
254
|
-
@callback_thread.kill
|
221
|
+
def post_runloop
|
222
|
+
@callbacks.kill
|
255
223
|
@ssl.close
|
224
|
+
super
|
256
225
|
end
|
257
226
|
|
258
|
-
def
|
227
|
+
def pre_runloop
|
228
|
+
super
|
229
|
+
|
230
|
+
# Setup the request callbacks consumer here so its parent thread will be this worker thread.
|
231
|
+
@callbacks = Threading::Consumer.new
|
232
|
+
|
259
233
|
@ssl = OpenSSL::SSL::SSLSocket.new(TCPSocket.new(@uri.host, @uri.port), @ssl_context)
|
260
234
|
@ssl.sync_close = true
|
261
235
|
@ssl.hostname = @uri.hostname
|
@@ -270,31 +244,36 @@ module Lowdown
|
|
270
244
|
end
|
271
245
|
end
|
272
246
|
|
247
|
+
# Called when the HTTP client changes its state to `:connected` and lets the parent thread (which was stopped in
|
248
|
+
# `#initialize`) continue.
|
249
|
+
#
|
250
|
+
# @return [void]
|
251
|
+
#
|
273
252
|
def change_to_connected_state
|
274
|
-
|
253
|
+
queue.max = @http.remote_settings[:settings_max_concurrent_streams]
|
275
254
|
@connected = true
|
276
|
-
|
255
|
+
parent_thread.run
|
277
256
|
end
|
278
257
|
|
279
258
|
# @note Only made into a method so it can be overriden from the tests, because our test setup doesn’t behave the
|
280
259
|
# same as the real APNS service.
|
281
260
|
#
|
261
|
+
# @return [Boolean]
|
262
|
+
# whether or not the HTTP client’s state is `:connected`.
|
263
|
+
#
|
282
264
|
def http_connected?
|
283
265
|
@http.state == :connected
|
284
266
|
end
|
285
267
|
|
286
268
|
# Start the main IO and HTTP processing loop.
|
287
269
|
def runloop
|
288
|
-
until
|
270
|
+
until thread[:should_exit] || @ssl.closed?
|
289
271
|
# Once connected, add requests while the max stream count has not yet been reached.
|
290
272
|
if !@connected
|
291
273
|
change_to_connected_state if http_connected?
|
292
|
-
elsif @http.active_stream_count <
|
293
|
-
|
294
|
-
|
295
|
-
@queue.pop(true).call(@http, @callback_queue)
|
296
|
-
rescue ThreadError
|
297
|
-
end
|
274
|
+
elsif @http.active_stream_count < queue.max
|
275
|
+
# Run dispatched jobs that add new requests.
|
276
|
+
perform_job(non_block: true, arguments: [@http, @callbacks])
|
298
277
|
end
|
299
278
|
# Try to read data from the SSL socket without blocking and process it.
|
300
279
|
begin
|
data/lib/lowdown/mock.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "lowdown/certificate"
|
2
2
|
require "lowdown/client"
|
3
3
|
require "lowdown/response"
|
4
|
+
require "lowdown/threading"
|
4
5
|
|
5
6
|
module Lowdown
|
6
7
|
# Provides a collection of test helpers.
|
@@ -122,17 +123,18 @@ module Lowdown
|
|
122
123
|
|
123
124
|
# @!group Real API: Instance Method Summary
|
124
125
|
|
125
|
-
# Yields stubbed {#responses} or if none are available defaults to success responses.
|
126
|
+
# Yields stubbed {#responses} or if none are available defaults to success responses. It does this on a different
|
127
|
+
# thread, just like the real API does.
|
126
128
|
#
|
127
129
|
# @param (see Lowdown::Connection#post)
|
128
130
|
# @yield (see Lowdown::Connection#post)
|
129
131
|
# @yieldparam (see Lowdown::Connection#post)
|
130
132
|
# @return (see Lowdown::Connection#post)
|
131
133
|
#
|
132
|
-
def post(path, headers, body)
|
134
|
+
def post(path, headers, body, &callback)
|
133
135
|
response = @responses.shift || Response.new(":status" => "200", "apns-id" => (headers["apns-id"] || generate_id))
|
134
136
|
@requests << Request.new(path, headers, body, response)
|
135
|
-
|
137
|
+
@callbacks.enqueue { callback.call(response) }
|
136
138
|
end
|
137
139
|
|
138
140
|
# Changes {#open?} to return `true`.
|
@@ -140,6 +142,7 @@ module Lowdown
|
|
140
142
|
# @return [void]
|
141
143
|
#
|
142
144
|
def open
|
145
|
+
@callbacks = Threading::Consumer.new
|
143
146
|
@open = true
|
144
147
|
end
|
145
148
|
|
@@ -148,6 +151,9 @@ module Lowdown
|
|
148
151
|
# @return [void]
|
149
152
|
#
|
150
153
|
def close
|
154
|
+
flush
|
155
|
+
@callbacks.kill
|
156
|
+
@callbacks = nil
|
151
157
|
@open = false
|
152
158
|
end
|
153
159
|
|
@@ -156,6 +162,12 @@ module Lowdown
|
|
156
162
|
# @return [void]
|
157
163
|
#
|
158
164
|
def flush
|
165
|
+
caller_thread = Thread.current
|
166
|
+
@callbacks.enqueue do
|
167
|
+
sleep 0.1
|
168
|
+
caller_thread.run
|
169
|
+
end
|
170
|
+
Thread.stop
|
159
171
|
end
|
160
172
|
|
161
173
|
# @return (see Lowdown::Connection#open?)
|
data/lib/lowdown/threading.rb
CHANGED
@@ -4,6 +4,132 @@ module Lowdown
|
|
4
4
|
# A collection of internal threading related helpers.
|
5
5
|
#
|
6
6
|
module Threading
|
7
|
+
# This class performs jobs on a private thread, provides lifecycle callbacks, and sends exceptions onto its parent
|
8
|
+
# thread.
|
9
|
+
#
|
10
|
+
class Consumer
|
11
|
+
# @param [Thread::Queue] queue
|
12
|
+
# a queue instance. Provide a `Thread::SizedQueue` if you want queue of a max size.
|
13
|
+
#
|
14
|
+
# @param [Thread] parent_thread
|
15
|
+
# the thread to send exceptions to.
|
16
|
+
#
|
17
|
+
def initialize(queue: Thread::Queue.new, parent_thread: Thread.current)
|
18
|
+
@queue, @parent_thread = queue, parent_thread
|
19
|
+
@thread = Thread.new(&method(:main))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Schedules a job to be performed.
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
#
|
26
|
+
def enqueue(&job)
|
27
|
+
queue << job
|
28
|
+
end
|
29
|
+
|
30
|
+
# Kills the private thread.
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
#
|
34
|
+
def kill
|
35
|
+
thread.kill
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Boolean]
|
39
|
+
# whether or not the private thread is still alive.
|
40
|
+
#
|
41
|
+
def alive?
|
42
|
+
thread.alive?
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Boolean]
|
46
|
+
# whether or not there are any scheduled jobs left in the queue.
|
47
|
+
#
|
48
|
+
def empty?
|
49
|
+
queue.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
# @return [Thread]
|
55
|
+
# the private thread.
|
56
|
+
#
|
57
|
+
attr_reader :thread
|
58
|
+
|
59
|
+
# @return [Thread]
|
60
|
+
# the thread to send exceptions to.
|
61
|
+
#
|
62
|
+
attr_reader :parent_thread
|
63
|
+
|
64
|
+
# @return [Thread::Queue]
|
65
|
+
# the jobs queue.
|
66
|
+
#
|
67
|
+
attr_reader :queue
|
68
|
+
|
69
|
+
# This represents the full lifecycle of the consumer thread. It performs the individual events, catches uncaught
|
70
|
+
# exceptions and sends those to the parent thread, and performs cleanup.
|
71
|
+
#
|
72
|
+
# Subclasses should override the individual events.
|
73
|
+
#
|
74
|
+
# @note This method is ran on the private thread.
|
75
|
+
#
|
76
|
+
# @return [void]
|
77
|
+
#
|
78
|
+
def main
|
79
|
+
pre_runloop
|
80
|
+
runloop
|
81
|
+
rescue Exception => exception
|
82
|
+
parent_thread.raise(exception)
|
83
|
+
ensure
|
84
|
+
post_runloop
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ran _before_ any jobs are performed.
|
88
|
+
#
|
89
|
+
# @note (see #main)
|
90
|
+
#
|
91
|
+
# @return [void]
|
92
|
+
#
|
93
|
+
def pre_runloop
|
94
|
+
end
|
95
|
+
|
96
|
+
# The loop that performs scheduled jobs.
|
97
|
+
#
|
98
|
+
# @note (see #main)
|
99
|
+
#
|
100
|
+
# @return [void]
|
101
|
+
#
|
102
|
+
def runloop
|
103
|
+
loop { perform_job(non_block: false) }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Ran when the thread is killed or an uncaught exception occurred.
|
107
|
+
#
|
108
|
+
# This kills the consumer thread, which means that any cleanup you need to perform should be done *before* calling
|
109
|
+
# this `super` implementation.
|
110
|
+
#
|
111
|
+
# @note (see #main)
|
112
|
+
#
|
113
|
+
# @return [void]
|
114
|
+
#
|
115
|
+
def post_runloop
|
116
|
+
thread.kill
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param [Boolean] non_block
|
120
|
+
# whether or not the thread should be halted if there are no jobs to perform.
|
121
|
+
#
|
122
|
+
# @param [Array<Object>] arguments
|
123
|
+
# arguments that should be passed to the invoked job.
|
124
|
+
#
|
125
|
+
# @return [void]
|
126
|
+
#
|
127
|
+
def perform_job(non_block:, arguments: nil)
|
128
|
+
queue.pop(non_block).call(*arguments)
|
129
|
+
rescue ThreadError
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
7
133
|
# A simple thread-safe counter.
|
8
134
|
#
|
9
135
|
class Counter
|
data/lib/lowdown/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lowdown
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eloy Durán
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2
|
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
114
|
version: '0'
|
115
115
|
requirements: []
|
116
116
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.
|
117
|
+
rubygems_version: 2.4.5.1
|
118
118
|
signing_key:
|
119
119
|
specification_version: 4
|
120
120
|
summary: A Ruby client for the HTTP/2 version of the Apple Push Notification Service.
|