ocean-rails 3.8.8 → 3.8.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e7029f8a24a193b18d67675b993929c586fa83f
4
- data.tar.gz: fe5a91ec43553c934329db63ac353d98b6648cb9
3
+ metadata.gz: 25d86f8d69015565033c12e60edb7017186d6829
4
+ data.tar.gz: 83a3f9b85153a9708225aa5ab46ad58e5b7837fa
5
5
  SHA512:
6
- metadata.gz: 2a33fe06e8d9a47f50139608fcba2ca6bfc441237a054d21c7b9324f68941b7b2ac7f26dbf036c675fe05096a17effd6e1379d14844bc9c8a013a9f76d313432
7
- data.tar.gz: 47c8f5e6d2c47d92ced28e1ae53f62665f63b87c5e7b96cd7ea69162274108ab86023922549a8633eed4adcd8ccb1b38a471d280621a2a519deece28468af2ec
6
+ metadata.gz: e7540bf449eb3e0d3aa5755f2fa75fd9ed22b8e74f6fd7409908a4836bd6f3e858e4f82cadbeb50a6236fe50d17d39cc891b9a87068347d14201e7ba8b084a1e
7
+ data.tar.gz: 00618f27d71e1446ac3f1135757c8944a0b8dd69873d5978166f6d058b6694e47173892ccbbf22bd4104511dc1afc4b41d5a8542e9d3acff1cf783c732b24448
data/lib/ocean/api.rb CHANGED
@@ -172,7 +172,8 @@ class Api
172
172
  reauthentication: true,
173
173
  ssl_verifypeer: true, ssl_verifyhost: 2,
174
174
  retries: 0, backoff_time: 1, backoff_rate: 0.9, backoff_max: 30,
175
- x_metadata: Thread.current[:metadata])
175
+ x_metadata: Thread.current[:metadata],
176
+ &block)
176
177
  # Set up the request
177
178
  headers['Accept'] = "application/json"
178
179
  headers['Content-Type'] = "application/json" if [:post, :put].include?(http_method)
@@ -181,63 +182,109 @@ class Api
181
182
  headers['X-API-Token'] = x_api_token if x_api_token.present?
182
183
  headers['X-Metadata'] = x_metadata if x_metadata.present?
183
184
 
184
- response = nil
185
185
  tries_done = 0
186
186
  max_tries = [:get, "GET", "get"].include?(http_method) ? retries + 1 : 1
187
- while (true) do
188
- tries_done += 1
189
- last_try = tries_done >= max_tries
190
-
191
- while (true) do
192
- request = Typhoeus::Request.new(url,
193
- method: http_method,
194
- headers: headers,
195
- params: args,
196
- body: body,
197
- ssl_verifypeer: ssl_verifypeer,
198
- ssl_verifyhost: ssl_verifyhost)
199
- # Run it
200
- response = Response.new(request.run)
201
- # If successful, return
202
- return response if response.success?
203
-
204
- # Not successful, deal with it
205
- if last_try
206
- raise Api::TimeoutError, "Api.request timed out" if response.timed_out?
207
- raise Api::NoResponseError, "Api.request could not obtain a response" if response.status == 0
208
- else
209
- break if response.timed_out?
210
- break if response.status == 0
211
- end
212
187
 
213
- # Reauthenticate if necessary
214
- if [400, 419].include?(response.status) && reauthentication && x_api_token.present?
215
- # Re-authenticate and retry
216
- if credentials
217
- x_api_token = Api.authenticate(*Api.decode_credentials(credentials))
218
- headers['X-API-Token'] = x_api_token
219
- else
220
- Api.reset_service_token
221
- headers['X-API-Token'] = Api.service_token
188
+ @hydra ||= Typhoeus::Hydra.hydra
189
+ request = nil # For clarity
190
+ response = nil
191
+
192
+ # This is a Proc when run queues the request and schedules retries
193
+ enqueue_request = Proc.new do
194
+ # First construct a request. It will not be sent yet.
195
+ request = Typhoeus::Request.new(url,
196
+ method: http_method,
197
+ headers: headers,
198
+ params: args,
199
+ body: body,
200
+ ssl_verifypeer: ssl_verifypeer,
201
+ ssl_verifyhost: ssl_verifyhost)
202
+
203
+ # Define a callback to process the response and do retries
204
+ request.on_complete do |typhoeus_response|
205
+ response = Response.new(typhoeus_response)
206
+ case response.status
207
+ when 100..199
208
+ enqueue_request.call # Ignore and retry
209
+ when 200..299, 304
210
+ # Success, call the post-processor if any
211
+ if block
212
+ response = block.call response
222
213
  end
223
- reauthentication = false # This prevents us from ending up here twice
224
- elsif (400..499).include?(response.status)
225
- return response
214
+ when 300..399
215
+ nil # Done, redirect
216
+ when 400, 419
217
+ if reauthentication && x_api_token.present?
218
+ # Re-authenticate and retry
219
+ if credentials
220
+ x_api_token = Api.authenticate(*Api.decode_credentials(credentials))
221
+ headers['X-API-Token'] = x_api_token
222
+ else
223
+ Api.reset_service_token
224
+ headers['X-API-Token'] = Api.service_token
225
+ end
226
+ reauthentication = false # This prevents us from ending up here twice
227
+ enqueue_request.call
228
+ else
229
+ nil # Done, fail
230
+ end
231
+ when 400..499
232
+ nil # Done, fail
226
233
  else
227
- # Retry on 500s (will we ever get 300s?)
228
- break
234
+ # Retry if there are any retries left
235
+ if retries > 0
236
+ retries -= 1
237
+ sleep backoff_time
238
+ backoff_time = [backoff_time + backoff_time * backoff_rate, 30].min
239
+ enqueue_request.call
240
+ else
241
+ nil # Done, don't retry
242
+ end
229
243
  end
244
+ end
245
+
246
+ # Finally, queue the request (and its callback) for execution
247
+ @hydra.queue request
248
+ # Return nil, to emphasise that the side effects are what's important
249
+ nil
250
+ end
230
251
 
252
+ # So create and enqueue the request
253
+ enqueue_request.call
254
+ # Run it, unless we're accumulating calls inside an Api.simultaneously block
255
+ unless @inside_simultaneously
256
+ # The next line blocks until completed, possibly after several retries
257
+ @hydra.run
258
+ if response.is_a?(Response)
259
+ # Raise any exceptions
260
+ raise Api::TimeoutError, "Api.request timed out" if response.timed_out?
261
+ raise Api::NoResponseError, "Api.request could not obtain a response" if response.status == 0
231
262
  end
232
- # Retry
233
- break if last_try
234
- sleep backoff_time
235
- seconds = [backoff_time + backoff_time * backoff_rate, 30].min
236
- backoff_time = seconds
263
+ return response
237
264
  end
238
- # Return the wrapped response to the failed request
239
- response
240
- end
265
+ # Return nil: we have no response yet
266
+ nil
267
+ end
268
+
269
+
270
+ def self.simultaneously (&block)
271
+ raise "block required" unless block
272
+ @inside_simultaneously = true
273
+ @hydra = nil
274
+ block.call
275
+ ensure
276
+ @inside_simultaneously = false
277
+ end
278
+
279
+
280
+ def self.simultaneously?
281
+ @inside_simultaneously ||= false
282
+ end
283
+
284
+
285
+ # http://liufengyun.chaos-lab.com/prog/2013/10/23/continuation-in-ruby.html
286
+ # http://gnuu.org/2009/03/21/demystifying-continuations-in-ruby/
287
+ # http://www.ruby-doc.org/core-2.1.3/Continuation.html
241
288
 
242
289
 
243
290
  class TimeoutError < StandardError; end
@@ -556,6 +603,7 @@ class Api
556
603
  h
557
604
  end
558
605
 
606
+
559
607
  #
560
608
  # Takes the same args as +.async_job_body+, post an +AsyncJob+ and returns the +Response+.
561
609
  # The +AsyncJob+ is always created as the service +ApiUser+; the job carries its own
@@ -564,8 +612,13 @@ class Api
564
612
  # +:job+ as the only parameter.
565
613
  #
566
614
  # A block may be given. It will be called with any +TimeoutError+ or +NoResponseError+,
567
- # which allows +Api.run_async_job+ to be used very tersely in controllers.
615
+ # which allows +Api.run_async_job+ to be used very tersely in controllers, e.g.:
568
616
  #
617
+ # Api.run_async_job(...) do |e|
618
+ # render_api_error 422, e.message
619
+ # return
620
+ # end
621
+ #
569
622
  def self.run_async_job(href=nil, method=nil, job: nil, **keywords, &block)
570
623
  job ||= async_job_body(href, method, **keywords)
571
624
  Api.request "#{INTERNAL_OCEAN_API_URL}/v1/async_jobs", :post,
@@ -271,7 +271,7 @@ class Api
271
271
  # Instance method to GET a resource or any of its hyperlinks. Will raise exceptions
272
272
  # if they occur, otherwise will return the resource itself.
273
273
  #
274
- def get!(hlink=nil)
274
+ def get!(hlink=nil, args: nil)
275
275
  RemoteResource._retrieve self unless present?
276
276
  hlink = hlink.to_s if hlink
277
277
  if !hlink || hlink == 'self'
@@ -305,7 +305,7 @@ class Api
305
305
  # Instance method to do a PUT to a resource or any of its hyperlinks. Will raise exceptions
306
306
  # if they occur, otherwise will return the resource itself.
307
307
  #
308
- def put!(hlink=nil, body: {})
308
+ def put!(hlink=nil, args: nil, body: {})
309
309
  get!
310
310
  hlink = hlink.to_s if hlink
311
311
  if !hlink || hlink == 'self'
@@ -315,7 +315,7 @@ class Api
315
315
  hl_data = hyperlink[hlink]
316
316
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
317
317
  hl_res = RemoteResource.new(hl_data['href'])
318
- hl_res.send :_modify, body
318
+ hl_res.send :_modify, body, args: args
319
319
  hl_res
320
320
  end
321
321
  end
@@ -325,7 +325,7 @@ class Api
325
325
  # exceptions if they occur, but will always return the resource itself.
326
326
  # A missing hyperlink, though, will always raise a HyperlinkMissing exception.
327
327
  #
328
- def put(hlink=nil, body: {})
328
+ def put(hlink=nil, args: nil, body: {})
329
329
  get
330
330
  hlink = hlink.to_s if hlink
331
331
  if !hlink || hlink == 'self'
@@ -335,7 +335,7 @@ class Api
335
335
  hl_data = hyperlink[hlink]
336
336
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
337
337
  hl_res = RemoteResource.new(hl_data['href'])
338
- hl_res.send :_modify, body
338
+ hl_res.send :_modify, body, args: args
339
339
  hl_res
340
340
  end
341
341
  end
@@ -345,12 +345,12 @@ class Api
345
345
  # Instance method to do a POST to a resource or any of its hyperlinks. Will raise exceptions
346
346
  # if they occur, otherwise will return the resource itself.
347
347
  #
348
- def post!(hlink=nil, body: {})
348
+ def post!(hlink=nil, args: nil, body: {})
349
349
  get!
350
350
  hlink = (hlink || 'self').to_s
351
351
  hl_data = hyperlink[hlink]
352
352
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
353
- _create(hl_data['href'], body)
353
+ _create(hl_data['href'], body, args: args)
354
354
  end
355
355
 
356
356
  #
@@ -358,12 +358,12 @@ class Api
358
358
  # exceptions if they occur, but will always return the resource itself.
359
359
  # A missing hyperlink, though, will always raise a HyperlinkMissing exception.
360
360
  #
361
- def post(hlink=nil, body: {})
361
+ def post(hlink=nil, args: nil, body: {})
362
362
  get
363
363
  hlink = (hlink || 'self').to_s
364
364
  hl_data = hyperlink[hlink]
365
365
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
366
- _create(hl_data['href'], body) rescue nil
366
+ _create(hl_data['href'], body, args: args) rescue nil
367
367
  end
368
368
 
369
369
 
@@ -371,12 +371,12 @@ class Api
371
371
  # Instance method to do a DELETE to a resource or any of its hyperlinks. Will raise exceptions
372
372
  # if they occur, otherwise will return the resource itself.
373
373
  #
374
- def delete!(hlink=nil)
374
+ def delete!(hlink=nil, args: nil)
375
375
  get!
376
376
  hlink = (hlink || 'self').to_s
377
377
  hl_data = hyperlink[hlink]
378
378
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
379
- _destroy(hl_data['href'])
379
+ _destroy(hl_data['href'], args: args)
380
380
  self
381
381
  end
382
382
 
@@ -385,12 +385,12 @@ class Api
385
385
  # exceptions if they occur, but will always return the resource itself.
386
386
  # A missing hyperlink, though, will always raise a HyperlinkMissing exception.
387
387
  #
388
- def delete(hlink=nil)
388
+ def delete(hlink=nil, args: nil)
389
389
  get
390
390
  hlink = (hlink || 'self').to_s
391
391
  hl_data = hyperlink[hlink]
392
392
  raise HyperlinkMissing, "#{resource_type} has no #{hlink} hyperlink" unless hl_data
393
- _destroy(hl_data['href']) rescue nil
393
+ _destroy(hl_data['href'], args: args) rescue nil
394
394
  self
395
395
  end
396
396
 
@@ -466,6 +466,7 @@ class Api
466
466
  rr
467
467
  end
468
468
 
469
+
469
470
  def _conditional_get
470
471
  credentials, token = RemoteResource._credentials(self)
471
472
  response = Api.request Api.internalize_uri(uri), :get,
@@ -515,12 +516,12 @@ class Api
515
516
  end
516
517
 
517
518
 
518
- def _modify(body)
519
+ def _modify(body, args: nil)
519
520
  credentials, token = RemoteResource._credentials(self)
520
521
  response = Api.request Api.internalize_uri(uri), :put, headers: {}, body: body.to_json,
521
522
  credentials: credentials, x_api_token: token,
522
523
  retries: retries, backoff_time: backoff_time, backoff_rate: backoff_rate,
523
- backoff_max: backoff_max
524
+ backoff_max: backoff_max, args: args
524
525
  self.response = response
525
526
  self.status = response.status
526
527
  self.status_message = response.message
@@ -540,12 +541,12 @@ class Api
540
541
  end
541
542
 
542
543
 
543
- def _create(post_uri, body)
544
+ def _create(post_uri, body, args: nil)
544
545
  credentials, token = RemoteResource._credentials(self)
545
546
  response = Api.request Api.internalize_uri(post_uri), :post, headers: {}, body: body.to_json,
546
547
  credentials: credentials, x_api_token: token,
547
548
  retries: retries, backoff_time: backoff_time, backoff_rate: backoff_rate,
548
- backoff_max: backoff_max
549
+ backoff_max: backoff_max, args: args
549
550
  self.response = response
550
551
  self.status = response.status
551
552
  self.status_message = response.message
@@ -564,12 +565,12 @@ class Api
564
565
  end
565
566
 
566
567
 
567
- def _destroy(delete_uri)
568
+ def _destroy(delete_uri, args: nil)
568
569
  credentials, token = RemoteResource._credentials(self)
569
570
  response = Api.request Api.internalize_uri(delete_uri), :delete, headers: {},
570
571
  credentials: credentials, x_api_token: token,
571
572
  retries: retries, backoff_time: backoff_time, backoff_rate: backoff_rate,
572
- backoff_max: backoff_max
573
+ backoff_max: backoff_max, args: args
573
574
  self.response = response
574
575
  self.status = response.status
575
576
  self.status_message = response.message
@@ -0,0 +1,139 @@
1
+ require 'continuation'
2
+
3
+ # Api.simultaneously takes a block. It executes the block straight away.
4
+ #
5
+ # When executing inside an Api.simultaneously block, Api.request only enqueues its request,
6
+ # but doesn't run it. They register on_complete handlers that will be run asynchronously
7
+ # when Api.simultaneously fires hydra to run after the block.
8
+ #
9
+ # When Api.request runs outside Api.simultaneously, it simply returns an Api::Response.
10
+ # When running inside Api.simultaneously, however, Api.request creates a continuation
11
+ # and registers it with Api.simultaneously. After the block has run, Api.simultaneously
12
+ # invokes each continuation in turn with another continuation to allow the next
13
+ # continuation to be called. When there are no more continuations to process,
14
+ # Api.simultaneously returns normally. In this way, all continuations are executed serially
15
+ # to process the Api.request results, no matter who called them or from where.
16
+
17
+ class Quux
18
+
19
+ require 'continuation'
20
+
21
+
22
+ def self.simultaneously?
23
+ !!@continuations
24
+ end
25
+
26
+ def self.accumulating?
27
+ @accumulating
28
+ end
29
+
30
+
31
+ def self.simultaneously
32
+ raise "Api.simultaneously may not be nested" if simultaneously?
33
+ @continuations = []
34
+ # Execute the block
35
+ begin
36
+ puts "--------------------------- ACCUMULATING ------------------------"
37
+ @accumulating = true
38
+ yield @continuations
39
+ ensure
40
+ @accumulating = false
41
+ end
42
+
43
+ # The continuations array should now have entries. Create a continuation
44
+ # to return to this point.
45
+ begin
46
+ puts '', "--------------------------- EXECUTING ------------------------"
47
+ cc, results, result = callcc { |cc, results, result| [cc, [], :init] }
48
+ # '-BACK AT TOP LEVEL-----', [cc, results, result].inspect
49
+ raise "must be a continuation" unless cc.is_a? Continuation
50
+ results << result if result && result != :init
51
+ # Call the first accumulated continuation (if any) with it. When they call
52
+ # here, the next continuation will be called (if any). This is a loop,
53
+ # continuation style.
54
+ if @continuations.size > 0
55
+ @continuations.shift.call(cc, results)
56
+ end
57
+ ensure
58
+ # No longer nested
59
+ @continuations = nil
60
+ end
61
+ results
62
+ end
63
+
64
+
65
+ def self.doubler (arg, k: nil)
66
+ raise "K is not a continuation: #{k.inspect}" if k && !k.is_a?(Continuation)
67
+ cc, acc = callcc { |cc, acc| [cc, acc] }
68
+ puts '', '-CONTINUATION CALL TO DOUBLER-----', [cc, acc].inspect
69
+ return cc if accumulating?
70
+
71
+ # Execution phase
72
+
73
+ # Calculate a result
74
+ result = arg * 2 # Calculate the result
75
+
76
+ # Transfer back
77
+ k.call(k, result) if k # Go elsewhere if k is there (e.g. to a nested caller)
78
+ cc.call(cc, acc, result) # Return to caller
79
+ end
80
+
81
+
82
+ def self.nested(arg, k: nil)
83
+ raise "K is not a continuation: #{k.inspect}" if k && !k.is_a?(Continuation)
84
+ cc, acc = callcc { |cc, acc| [cc, acc] }
85
+ puts '', '-CONTINUATION CALL TO NESTED-----', [cc, acc].inspect
86
+ return cc if accumulating?
87
+
88
+ # Execution phase
89
+ # Set up the overriding continuation for the callee
90
+ callee_k, result = callcc { |callee_k, result| [callee_k, result] }
91
+ # The callee returns to the following statement
92
+ result = doubler(arg, k: callee_k) unless result # Can't be nil or false (fix later)
93
+
94
+ # Calculate a result
95
+ result = "#{result} processed"
96
+
97
+ # Transfer back
98
+ k.call(k, result) if k # Go elsewhere if k is there (e.g. to a nested caller)
99
+ cc.call(cc, acc, result) # Return to caller
100
+ end
101
+
102
+
103
+ def self.deeply_nested(arg, k: nil)
104
+ raise "K is not a continuation: #{k.inspect}" if k && !k.is_a?(Continuation)
105
+ cc, acc = callcc { |cc, acc| [cc, acc] }
106
+ puts '', '-CONTINUATION CALL TO DEEPLY_NESTED-----', [cc, acc].inspect
107
+ return cc if accumulating?
108
+
109
+ # Execution phase
110
+ # Set up the overriding continuation for the callee
111
+ callee_k, result = callcc { |callee_k, result| [callee_k, result] }
112
+ # The callee returns to the following statement
113
+ result = nested(arg, k: callee_k) unless result # Can't be nil or false (fix later)
114
+
115
+ # Calculate a result
116
+ result = "#{result} more deeply"
117
+
118
+ # Transfer back
119
+ k.call(k, result) if k # Go elsewhere if k is there (e.g. to a nested caller)
120
+ cc.call(cc, acc, result) # Return to caller
121
+ end
122
+
123
+ end
124
+
125
+
126
+
127
+ # This accumulates stuff
128
+ res = Quux.simultaneously do |r|
129
+ r << Quux.doubler(24)
130
+ r << Quux.nested("hej")
131
+ r << Quux.doubler(100)
132
+ r << Quux.deeply_nested("woo")
133
+ r << Quux.doubler(1)
134
+ end
135
+
136
+ puts
137
+ puts res.inspect
138
+ puts
139
+
data/lib/ocean/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ocean
2
- VERSION = "3.8.8"
2
+ VERSION = "3.8.9"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ocean-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.8.8
4
+ version: 3.8.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Bengtson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-09 00:00:00.000000000 Z
11
+ date: 2014-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -252,6 +252,7 @@ files:
252
252
  - lib/ocean/api.rb
253
253
  - lib/ocean/api_remote_resource.rb
254
254
  - lib/ocean/api_resource.rb
255
+ - lib/ocean/continuation_experiments.rb
255
256
  - lib/ocean/engine.rb
256
257
  - lib/ocean/flooding.rb
257
258
  - lib/ocean/ocean_application_controller.rb