ocean-rails 3.8.8 → 3.8.9

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 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