stackdriver-core 1.1.0 → 1.2.0

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: b45c580aedf6db0249b85c998c299b1b549e79fc
4
- data.tar.gz: b0102ed722cc628e16836cdf64dde18493162399
3
+ metadata.gz: 4bb62b8cdb5f28de4f303da6a8ff2e0a31d6cbcd
4
+ data.tar.gz: 2e6eda3a499805fef92f27a3d0b5167d8d508e31
5
5
  SHA512:
6
- metadata.gz: 9a85f64d8201cfa290097d414d95c092be448b5eb6ad3ae55788f837be9da77bd7ef2a5fc5886453d0cc6f2cfcfea7a0b642c49063018ef2c3856ee87444244a
7
- data.tar.gz: 5b616f814cf67f966d2ab60ec651a446e99df26acb85a8ee959e6dcfe32ecd15d3f47004da7342d482f9f94b7eaf5891c3f8fb690930fb9522f3783c78f471b4
6
+ metadata.gz: 122c12d22560686b2a45f63aca85b33d68876d5d3a513dc5bf8deef952fa060dd4065f948447dc096ef96ec0e29102fb70d429bbdba0b18becda93c141e2faa8
7
+ data.tar.gz: c88f340bd0a325928c9d94d2587e8c7c72a38f1735631416541070a1b8ed30999b725b871bbca1e5580d8baeaff451708ed4c3c761f3a206e1500ef9e44c4037
@@ -14,6 +14,7 @@
14
14
 
15
15
 
16
16
  require "google/cloud/configuration"
17
+ require "stackdriver/core/async_actor"
17
18
  require "stackdriver/core/configuration"
18
19
  require "stackdriver/core/trace_context"
19
20
  require "stackdriver/core/version"
@@ -0,0 +1,359 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "set"
16
+
17
+
18
+ module Stackdriver
19
+ module Core
20
+ ##
21
+ # # AsyncActor
22
+ #
23
+ # @private An module that provides a class asynchronous capability when
24
+ # included. It can create a child thread to run background jobs, and
25
+ # help make sure the child thread terminates properly when process
26
+ # is interrupted.
27
+ #
28
+ # To use AsyncActor, the classes that are including this module need to
29
+ # define a run_backgrounder method that does the background job. The
30
+ # classes can then control the child thread job through instance methods
31
+ # like async_start, async_stop, etc.
32
+ #
33
+ # @example
34
+ # class Foo
35
+ # include AsyncActor
36
+ #
37
+ # def initialize
38
+ # super()
39
+ # end
40
+ #
41
+ # def run_backgrounder
42
+ # # run async job
43
+ # end
44
+ # end
45
+ #
46
+ # foo = Foo.new
47
+ # foo.async_start
48
+ #
49
+ module AsyncActor
50
+ include MonitorMixin
51
+
52
+ CLEANUP_TIMEOUT = 10.0
53
+ WAIT_INTERVAL = 1.0
54
+
55
+ @cleanup_list = nil
56
+ @exit_lock = Monitor.new
57
+
58
+ ##
59
+ # @private The async actor state
60
+ attr_reader :async_state
61
+
62
+ ##
63
+ # Starts the child thread and asynchronous job
64
+ def async_start
65
+ ensure_thread
66
+ end
67
+
68
+ ##
69
+ # Nicely ask the child thread to stop by setting the state to
70
+ # :stopping if it's not stopped already.
71
+ #
72
+ # @return [Boolean] False if child thread is already stopped. Otherwise
73
+ # true.
74
+ def async_stop
75
+ ensure_thread
76
+ synchronize do
77
+ if async_state != :stopped
78
+ @async_state = :stopping
79
+ async_state_change
80
+ true
81
+ else
82
+ false
83
+ end
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Set the state to :suspend if the current state is :running.
89
+ #
90
+ # @return [Boolean] Returns true if the state has been set to
91
+ # :suspended. Otherwise return false.
92
+ #
93
+ def async_suspend
94
+ ensure_thread
95
+ synchronize do
96
+ if async_state == :running
97
+ @async_state = :suspended
98
+ async_state_change
99
+ true
100
+ else
101
+ false
102
+ end
103
+ end
104
+ end
105
+
106
+ ##
107
+ # Set the state to :running if the current state is :suspended.
108
+ #
109
+ # @return [Boolean] True if the state has been set to :running.
110
+ # Otherwise return false.
111
+ #
112
+ def async_resume
113
+ ensure_thread
114
+ synchronize do
115
+ if async_state == :suspended
116
+ @async_state = :running
117
+ async_state_change
118
+ true
119
+ else
120
+ false
121
+ end
122
+ end
123
+ end
124
+
125
+ ##
126
+ # Check if async job is running
127
+ #
128
+ # @return [Boolean] True if state equals :running. Otherwise false.
129
+ def async_running?
130
+ synchronize do
131
+ async_state == :running
132
+ end
133
+ end
134
+
135
+ ##
136
+ # Check if async job is suspended
137
+ #
138
+ # @return [Boolean] True if state equals :suspended. Otherwise false.
139
+ def async_suspended?
140
+ synchronize do
141
+ async_state == :suspended
142
+ end
143
+ end
144
+
145
+ ##
146
+ # Check if async job is working.
147
+ #
148
+ # @return [Boolean] True if state is either :running or :suspended.
149
+ # Otherwise false.
150
+ def async_working?
151
+ synchronize do
152
+ async_state == :suspended || async_state == :running
153
+ end
154
+ end
155
+
156
+ ##
157
+ # Check if async job has stopped
158
+ #
159
+ # @return [Boolean] True if state equals :stopped. Otherwise false.
160
+ def async_stopped?
161
+ synchronize do
162
+ async_state == :stopped
163
+ end
164
+ end
165
+
166
+ ##
167
+ # Check if async job is stopping
168
+ #
169
+ # @return [Boolean] True if state equals :stopping. Otherwise false.
170
+ def async_stopping?
171
+ synchronize do
172
+ async_state == :stopping
173
+ end
174
+ end
175
+
176
+ ##
177
+ # Ask async job to stop. Then forcefully kill thread if it doesn't stop
178
+ # after timeout if needed.
179
+ #
180
+ # @return [Symbol] :stopped if async job already stopped. :waited if
181
+ # async job terminates within timeout range. :timeout if async job
182
+ # doesn't terminate after timeout. :forced if thread is killed by
183
+ # force after timeout.
184
+ def async_stop!
185
+ ensure_thread
186
+
187
+ timeout = @cleanup_options[:timeout]
188
+ force = @cleanup_options[:force]
189
+
190
+ return :stopped unless async_stop
191
+
192
+ return :waited if timeout.to_f > 0 && wait_until_async_stopped(timeout)
193
+
194
+ return :timeout unless force
195
+
196
+ @thread.kill
197
+ @thread.join
198
+ :forced
199
+ end
200
+
201
+ ##
202
+ # Block current thread until the async job is fully stopped.
203
+ #
204
+ # @param [Integer] timeout If given, lift the blocking when the time has
205
+ # exceeded the timeout. If nil, block indefinitely.
206
+ #
207
+ # @return [Boolean] True if async job is fully stopped. False if timeout.
208
+ #
209
+ def wait_until_async_stopped timeout = nil
210
+ ensure_thread
211
+ deadline = timeout ? ::Time.new.to_f + timeout : nil
212
+ synchronize do
213
+ until async_state == :stopped
214
+ cur_time = ::Time.new.to_f
215
+ return false if deadline && cur_time >= deadline
216
+ max_interval = @cleanup_options[:wait_interval]
217
+ interval = deadline ? deadline - cur_time : max_interval
218
+ interval = max_interval if interval > max_interval
219
+ @lock_cond.wait interval
220
+ end
221
+ end
222
+ true
223
+ end
224
+
225
+ ##
226
+ # Abstract method that the inheriting classes should implement.
227
+ #
228
+ # This should be the main task job that will be run asynchronously and
229
+ # repeatly.
230
+ def run_backgrounder
231
+ fail "#{self.class} class should override #run_backgrounder method"
232
+ end
233
+
234
+ ##
235
+ # @private Cleanup this async job when process exists
236
+ #
237
+ def self.register_for_cleanup actor
238
+ @exit_lock.synchronize do
239
+ unless @cleanup_list
240
+ @cleanup_list = []
241
+ at_exit { run_cleanup }
242
+ end
243
+ @cleanup_list.push actor
244
+ end
245
+ end
246
+
247
+ ##
248
+ # @private Take this async job off exit cleanup list
249
+ #
250
+ def self.unregister_for_cleanup actor
251
+ @exit_lock.synchronize do
252
+ @cleanup_list.delete actor if @cleanup_list
253
+ end
254
+ end
255
+
256
+ ##
257
+ # @private Cleanup the async job
258
+ #
259
+ def self.run_cleanup
260
+ @exit_lock.synchronize do
261
+ if @cleanup_list
262
+ @cleanup_list.shift.async_stop! until @cleanup_list.empty?
263
+ end
264
+ end
265
+ end
266
+
267
+ private_class_method :run_cleanup
268
+
269
+ private
270
+
271
+ ##
272
+ # @private Constructor to initialize MonitorMixin
273
+ #
274
+ def initialize
275
+ super()
276
+ @startup_lock = Mutex.new
277
+ @cleanup_options = {
278
+ wait_interval: WAIT_INTERVAL,
279
+ timeout: CLEANUP_TIMEOUT,
280
+ force: true
281
+ }
282
+ end
283
+
284
+ ##
285
+ # @private Wrapper method for running the async job. It runs a loop
286
+ # while the condition allows, and it calls the run_backgrounder method.
287
+ # This method also ensures the state variable gets set to :stopped when
288
+ # it exits.
289
+ def async_run_job
290
+ until async_stopped?
291
+ run_backgrounder
292
+
293
+ return if async_stopping? && backgrounder_stoppable?
294
+ end
295
+ ensure
296
+ @async_state = :stopped
297
+ async_state_change
298
+ end
299
+
300
+ ##
301
+ # @private Ensures the child thread is started and kick off the job
302
+ # to run async_run_job. Also make calls register_for_cleanup on the
303
+ # async job to make sure it exits properly.
304
+ def ensure_thread
305
+ fail "async_actor not initialized" if @startup_lock.nil?
306
+ @startup_lock.synchronize do
307
+ if (@thread.nil? || !@thread.alive?) && @async_state != :stopped
308
+ @lock_cond = new_cond
309
+ AsyncActor.register_for_cleanup self
310
+
311
+ @async_state = :running
312
+ async_state_change
313
+
314
+ @thread = Thread.new do
315
+ async_run_job
316
+ AsyncActor.unregister_for_cleanup self
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ ##
323
+ # @private Set cleanup options.
324
+ #
325
+ # @param [Hash] *kwargs Hash of cleanup options. :timeout is the cleanup
326
+ # wait timeout. :wait_interval is the cleanup wait interval. :force for
327
+ # forcefully terminate actor when all other options fail.
328
+ def set_cleanup_options **kwargs
329
+ @cleanup_options.merge! kwargs
330
+ end
331
+
332
+ ##
333
+ # @private Default backgrounder stop condition when asked to be stopped
334
+ # gracefully. Called from #async_run_job when async actor state changes
335
+ # to :stopping
336
+ def backgrounder_stoppable?
337
+ true
338
+ end
339
+
340
+ ##
341
+ # @private Handler when the async actor's state changes. Call
342
+ # the `on_async_state_change` callback function if it's defined.
343
+ def async_state_change
344
+ on_async_state_change
345
+
346
+ synchronize do
347
+ @lock_cond.broadcast
348
+ end
349
+ end
350
+
351
+ ##
352
+ # @private Default abstract definition of this function that's a no-op.
353
+ # The extending classes can override this method to handle state changing
354
+ # logic.
355
+ def on_async_state_change
356
+ end
357
+ end
358
+ end
359
+ end
@@ -15,6 +15,6 @@
15
15
 
16
16
  module Stackdriver
17
17
  module Core
18
- VERSION = "1.1.0".freeze
18
+ VERSION = "1.2.0".freeze
19
19
  end
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackdriver-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Azuma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-26 00:00:00.000000000 Z
11
+ date: 2017-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -150,6 +150,7 @@ files:
150
150
  - lib/google/cloud/configuration.rb
151
151
  - lib/stackdriver-core.rb
152
152
  - lib/stackdriver/core.rb
153
+ - lib/stackdriver/core/async_actor.rb
153
154
  - lib/stackdriver/core/configuration.rb
154
155
  - lib/stackdriver/core/trace_context.rb
155
156
  - lib/stackdriver/core/version.rb