stackdriver-core 1.1.0 → 1.2.0

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