lowdown 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|