ddtrace 1.23.2 → 1.23.3

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
  SHA256:
3
- metadata.gz: 836f8f52564c86b8ee1a4681309d3d168b1689f3742dfed4d1a8171e23852ffa
4
- data.tar.gz: f0ad632313b36e6550e33f952da8e7ea89349fd8abc2122f41d649292a700028
3
+ metadata.gz: 9e52d825fdd7cb0391c1e529a979862f24f8ec0412cbd1952f0ae21f3129b83f
4
+ data.tar.gz: 27ab67ddd6bd0c21a0f7de60c022c143ae2367051b4b1a744f33f01453c1d8e5
5
5
  SHA512:
6
- metadata.gz: 1021b4efd906f5cf362e27be17249ce82fad3100b62b9a39ba2fd65d508c07ac520cb94971d6986a5171d1575a7fc8e219ed2dc4785166d0bd0ec774cd447d19
7
- data.tar.gz: 6ef6b7f391bb98e8e7f039cd72d6df56ee821df539803d3398ef7aaebe2c414eef87f4c2f0cf361ef3a066faf0490bb4b75831563a17729d11115c84243ab349
6
+ metadata.gz: b0c94e18051c3fe9522493daf7321ae77246d9e5cf6d5888fd3b9c565668c882e684d3156d58867104743c6769f07437c4bc68f69f4884d0609c92279d1ea3bb
7
+ data.tar.gz: 83bab49e211de5749555906b2f59b0ee8cb8a501c2363ea625a52e6f2c318714be26dc0226e51aabfd8461b96f3036b15a856eb372729ce69bd29df7baa06ef8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [1.23.3] - 2024-07-01
6
+
7
+ ### Added
8
+
9
+ * Add post install message about 2.x upgrade ([#3723][])
10
+
11
+ ### Fixed
12
+
13
+ * Fix telemetry events blocking main thread ([#3740][])
14
+ * Fix deadlock from telemetry threads ([#3745][])
15
+
5
16
  ## [1.23.2] - 2024-06-13
6
17
 
7
18
  ### Fixed
@@ -2826,7 +2837,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
2826
2837
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
2827
2838
 
2828
2839
 
2829
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.2...master
2840
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.3...1.x-stable
2841
+ [1.23.3]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.2...v1.23.3
2830
2842
  [1.23.2]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.1...v1.23.2
2831
2843
  [1.23.1]: https://github.com/DataDog/dd-trace-rb/compare/v1.23.0...v1.23.1
2832
2844
  [1.23.0]: https://github.com/DataDog/dd-trace-rb/compare/v1.22.0...v1.23.0
@@ -4142,6 +4154,7 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4142
4154
  [#3623]: https://github.com/DataDog/dd-trace-rb/issues/3623
4143
4155
  [#3650]: https://github.com/DataDog/dd-trace-rb/issues/3650
4144
4156
  [#3683]: https://github.com/DataDog/dd-trace-rb/issues/3683
4157
+ [#3745]: https://github.com/DataDog/dd-trace-rb/issues/3745
4145
4158
  [@AdrianLC]: https://github.com/AdrianLC
4146
4159
  [@Azure7111]: https://github.com/Azure7111
4147
4160
  [@BabyGroot]: https://github.com/BabyGroot
@@ -4,7 +4,7 @@ require_relative '../diagnostics/environment_logger'
4
4
  require_relative '../diagnostics/health'
5
5
  require_relative '../logger'
6
6
  require_relative '../runtime/metrics'
7
- require_relative '../telemetry/client'
7
+ require_relative '../telemetry/component'
8
8
  require_relative '../workers/runtime_metrics'
9
9
 
10
10
  require_relative '../remote/component'
@@ -60,7 +60,7 @@ module Datadog
60
60
  logger.debug { "Telemetry disabled. Agent network adapter not supported: #{agent_settings.adapter}" }
61
61
  end
62
62
 
63
- Telemetry::Client.new(
63
+ Telemetry::Component.new(
64
64
  enabled: enabled,
65
65
  heartbeat_interval_seconds: settings.telemetry.heartbeat_interval_seconds,
66
66
  dependency_collection: settings.telemetry.dependency_collection
@@ -165,8 +165,9 @@ module Datadog
165
165
  unused_statsd = (old_statsd - (old_statsd & new_statsd))
166
166
  unused_statsd.each(&:close)
167
167
 
168
- telemetry.stop!
168
+ # enqueue closing event before stopping telemetry so it will be send out on shutdown
169
169
  telemetry.emit_closing! unless replacement
170
+ telemetry.stop!
170
171
  end
171
172
  end
172
173
  end
@@ -81,23 +81,16 @@ module Datadog
81
81
  configuration = self.configuration
82
82
  yield(configuration)
83
83
 
84
- built_components = false
85
-
86
- components = safely_synchronize do |write_components|
84
+ safely_synchronize do |write_components|
87
85
  write_components.call(
88
86
  if components?
89
87
  replace_components!(configuration, @components)
90
88
  else
91
- components = build_components(configuration)
92
- built_components = true
93
- components
89
+ build_components(configuration)
94
90
  end
95
91
  )
96
92
  end
97
93
 
98
- # Should only be called the first time components are built
99
- components.telemetry.started! if built_components
100
-
101
94
  configuration
102
95
  end
103
96
 
@@ -197,20 +190,13 @@ module Datadog
197
190
  current_components = COMPONENTS_READ_LOCK.synchronize { defined?(@components) && @components }
198
191
  return current_components if current_components || !allow_initialization
199
192
 
200
- built_components = false
201
-
202
- components = safely_synchronize do |write_components|
193
+ safely_synchronize do |write_components|
203
194
  if defined?(@components) && @components
204
195
  @components
205
196
  else
206
- built_components = true
207
197
  write_components.call(build_components(configuration))
208
198
  end
209
199
  end
210
-
211
- # Should only be called the first time components are built
212
- components.telemetry.started! if built_components && components && components.telemetry
213
- components
214
200
  end
215
201
 
216
202
  private
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'emitter'
4
+ require_relative 'event'
5
+ require_relative 'worker'
6
+ require_relative '../utils/forking'
7
+
8
+ module Datadog
9
+ module Core
10
+ module Telemetry
11
+ # Telemetry entrypoint, coordinates sending telemetry events at various points in app lifecycle.
12
+ class Component
13
+ attr_reader :enabled
14
+
15
+ include Core::Utils::Forking
16
+
17
+ # @param enabled [Boolean] Determines whether telemetry events should be sent to the API
18
+ # @param heartbeat_interval_seconds [Float] How frequently heartbeats will be reported, in seconds.
19
+ # @param [Boolean] dependency_collection Whether to send the `app-dependencies-loaded` event
20
+ def initialize(heartbeat_interval_seconds:, dependency_collection:, enabled: true)
21
+ @enabled = enabled
22
+ @stopped = false
23
+
24
+ @worker = Telemetry::Worker.new(
25
+ enabled: @enabled,
26
+ heartbeat_interval_seconds: heartbeat_interval_seconds,
27
+ emitter: Emitter.new,
28
+ dependency_collection: dependency_collection
29
+ )
30
+ @worker.start
31
+ end
32
+
33
+ def disable!
34
+ @enabled = false
35
+ @worker.enabled = false
36
+ end
37
+
38
+ def stop!
39
+ return if @stopped
40
+
41
+ @worker.stop(true)
42
+ @stopped = true
43
+ end
44
+
45
+ def emit_closing!
46
+ return if !@enabled || forked?
47
+
48
+ @worker.enqueue(Event::AppClosing.new)
49
+ end
50
+
51
+ def integrations_change!
52
+ return if !@enabled || forked?
53
+
54
+ @worker.enqueue(Event::AppIntegrationsChange.new)
55
+ end
56
+
57
+ # Report configuration changes caused by Remote Configuration.
58
+ def client_configuration_change!(changes)
59
+ return if !@enabled || forked?
60
+
61
+ @worker.enqueue(Event::AppClientConfigurationChange.new(changes, 'remote_config'))
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -38,6 +38,7 @@ module Datadog
38
38
  private
39
39
 
40
40
  def products
41
+ # @type var products: Hash[Symbol, Hash[Symbol, Object]]
41
42
  products = {
42
43
  appsec: {
43
44
  enabled: Datadog::AppSec.enabled?,
@@ -13,7 +13,7 @@ module Datadog
13
13
  :timeout,
14
14
  :ssl
15
15
 
16
- DEFAULT_TIMEOUT = 30
16
+ DEFAULT_TIMEOUT = 2
17
17
 
18
18
  def initialize(hostname:, port: nil, timeout: DEFAULT_TIMEOUT, ssl: true)
19
19
  @hostname = hostname
@@ -0,0 +1,158 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'event'
4
+
5
+ require_relative '../utils/only_once_successful'
6
+ require_relative '../workers/polling'
7
+ require_relative '../workers/queue'
8
+
9
+ module Datadog
10
+ module Core
11
+ module Telemetry
12
+ # Accumulates events and sends them to the API at a regular interval, including heartbeat event.
13
+ class Worker
14
+ include Core::Workers::Queue
15
+ include Core::Workers::Polling
16
+
17
+ DEFAULT_BUFFER_MAX_SIZE = 1000
18
+ APP_STARTED_EVENT_RETRIES = 10
19
+
20
+ TELEMETRY_STARTED_ONCE = Utils::OnlyOnceSuccessful.new(APP_STARTED_EVENT_RETRIES)
21
+
22
+ def initialize(
23
+ heartbeat_interval_seconds:,
24
+ emitter:,
25
+ dependency_collection:,
26
+ enabled: true,
27
+ shutdown_timeout: Workers::Polling::DEFAULT_SHUTDOWN_TIMEOUT,
28
+ buffer_size: DEFAULT_BUFFER_MAX_SIZE
29
+ )
30
+ @emitter = emitter
31
+ @dependency_collection = dependency_collection
32
+
33
+ # Workers::Polling settings
34
+ self.enabled = enabled
35
+ # Workers::IntervalLoop settings
36
+ self.loop_base_interval = heartbeat_interval_seconds
37
+ self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
38
+
39
+ @shutdown_timeout = shutdown_timeout
40
+ @buffer_size = buffer_size
41
+
42
+ self.buffer = buffer_klass.new(@buffer_size)
43
+ end
44
+
45
+ def start
46
+ return if !enabled? || forked?
47
+
48
+ # starts async worker
49
+ perform
50
+ end
51
+
52
+ def stop(force_stop = false, timeout = @shutdown_timeout)
53
+ buffer.close if running?
54
+
55
+ super
56
+ end
57
+
58
+ def enqueue(event)
59
+ return if !enabled? || forked?
60
+
61
+ buffer.push(event)
62
+ end
63
+
64
+ def sent_started_event?
65
+ TELEMETRY_STARTED_ONCE.success?
66
+ end
67
+
68
+ def failed_to_start?
69
+ TELEMETRY_STARTED_ONCE.failed?
70
+ end
71
+
72
+ private
73
+
74
+ def perform(*events)
75
+ return if !enabled? || forked?
76
+
77
+ started! unless sent_started_event?
78
+
79
+ heartbeat!
80
+
81
+ flush_events(events)
82
+ end
83
+
84
+ def flush_events(events)
85
+ return if events.nil?
86
+ return if !enabled? || !sent_started_event?
87
+
88
+ Datadog.logger.debug { "Sending #{events.count} telemetry events" }
89
+ events.each do |event|
90
+ send_event(event)
91
+ end
92
+ end
93
+
94
+ def heartbeat!
95
+ return if !enabled? || !sent_started_event?
96
+
97
+ send_event(Event::AppHeartbeat.new)
98
+ end
99
+
100
+ def started!
101
+ return unless enabled?
102
+
103
+ if failed_to_start?
104
+ Datadog.logger.debug('Telemetry app-started event exhausted retries, disabling telemetry worker')
105
+ self.enabled = false
106
+ return
107
+ end
108
+
109
+ TELEMETRY_STARTED_ONCE.run do
110
+ res = send_event(Event::AppStarted.new)
111
+
112
+ if res.ok?
113
+ Datadog.logger.debug('Telemetry app-started event is successfully sent')
114
+
115
+ send_event(Event::AppDependenciesLoaded.new) if @dependency_collection
116
+
117
+ true
118
+ else
119
+ Datadog.logger.debug('Error sending telemetry app-started event, retry after heartbeat interval...')
120
+ false
121
+ end
122
+ end
123
+ end
124
+
125
+ def send_event(event)
126
+ res = @emitter.request(event)
127
+
128
+ disable_on_not_found!(res)
129
+
130
+ res
131
+ end
132
+
133
+ def dequeue
134
+ buffer.pop
135
+ end
136
+
137
+ def work_pending?
138
+ run_loop? || !buffer.empty?
139
+ end
140
+
141
+ def buffer_klass
142
+ if Core::Environment::Ext::RUBY_ENGINE == 'ruby'
143
+ Core::Buffer::CRuby
144
+ else
145
+ Core::Buffer::ThreadSafe
146
+ end
147
+ end
148
+
149
+ def disable_on_not_found!(response)
150
+ return unless response.not_found?
151
+
152
+ Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
153
+ self.enabled = false
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'only_once'
4
+
5
+ module Datadog
6
+ module Core
7
+ module Utils
8
+ # Helper class to execute something with only one success.
9
+ #
10
+ # This is useful for cases where we want to ensure that a block of code is only executed once, and only if it
11
+ # succeeds. One such example is sending app-started telemetry event.
12
+ #
13
+ # Successful execution is determined by the return value of the block: any truthy value is considered success.
14
+ #
15
+ # Thread-safe when used correctly (e.g. be careful of races when lazily initializing instances of this class).
16
+ #
17
+ # Note: In its current state, this class is not Ractor-safe.
18
+ # In https://github.com/DataDog/dd-trace-rb/pull/1398#issuecomment-797378810 we have a discussion of alternatives,
19
+ # including an alternative implementation that is Ractor-safe once spent.
20
+ class OnlyOnceSuccessful < OnlyOnce
21
+ def initialize(limit = 0)
22
+ super()
23
+
24
+ @limit = limit
25
+ @failed = false
26
+ @retries = 0
27
+ end
28
+
29
+ def run
30
+ @mutex.synchronize do
31
+ return if @ran_once
32
+
33
+ result = yield
34
+ @ran_once = !!result
35
+
36
+ if !@ran_once && limited?
37
+ @retries += 1
38
+ check_limit!
39
+ end
40
+
41
+ result
42
+ end
43
+ end
44
+
45
+ def success?
46
+ @mutex.synchronize { @ran_once && !@failed }
47
+ end
48
+
49
+ def failed?
50
+ @mutex.synchronize { @ran_once && @failed }
51
+ end
52
+
53
+ private
54
+
55
+ def check_limit!
56
+ if @retries >= @limit
57
+ @failed = true
58
+ @ran_once = true
59
+ end
60
+ end
61
+
62
+ def limited?
63
+ !@limit.nil? && @limit > 0
64
+ end
65
+
66
+ def reset_ran_once_state_for_tests
67
+ @mutex.synchronize do
68
+ @ran_once = false
69
+ @failed = false
70
+ @retries = 0
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -4,7 +4,7 @@ module DDTrace
4
4
  module VERSION
5
5
  MAJOR = 1
6
6
  MINOR = 23
7
- PATCH = 2
7
+ PATCH = 3
8
8
  PRE = nil
9
9
  BUILD = nil
10
10
  # PRE and BUILD above are modified for dev gems during gem build GHA workflow
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ddtrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.2
4
+ version: 1.23.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Datadog, Inc.
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -296,17 +296,17 @@ files:
296
296
  - lib/datadog/core/remote/worker.rb
297
297
  - lib/datadog/core/runtime/ext.rb
298
298
  - lib/datadog/core/runtime/metrics.rb
299
- - lib/datadog/core/telemetry/client.rb
299
+ - lib/datadog/core/telemetry/component.rb
300
300
  - lib/datadog/core/telemetry/emitter.rb
301
301
  - lib/datadog/core/telemetry/event.rb
302
302
  - lib/datadog/core/telemetry/ext.rb
303
- - lib/datadog/core/telemetry/heartbeat.rb
304
303
  - lib/datadog/core/telemetry/http/adapters/net.rb
305
304
  - lib/datadog/core/telemetry/http/env.rb
306
305
  - lib/datadog/core/telemetry/http/ext.rb
307
306
  - lib/datadog/core/telemetry/http/response.rb
308
307
  - lib/datadog/core/telemetry/http/transport.rb
309
308
  - lib/datadog/core/telemetry/request.rb
309
+ - lib/datadog/core/telemetry/worker.rb
310
310
  - lib/datadog/core/transport/ext.rb
311
311
  - lib/datadog/core/transport/http/adapters/net.rb
312
312
  - lib/datadog/core/transport/http/adapters/registry.rb
@@ -327,6 +327,7 @@ files:
327
327
  - lib/datadog/core/utils/hash.rb
328
328
  - lib/datadog/core/utils/network.rb
329
329
  - lib/datadog/core/utils/only_once.rb
330
+ - lib/datadog/core/utils/only_once_successful.rb
330
331
  - lib/datadog/core/utils/safe_dup.rb
331
332
  - lib/datadog/core/utils/sequence.rb
332
333
  - lib/datadog/core/utils/time.rb
@@ -880,9 +881,16 @@ licenses:
880
881
  - Apache-2.0
881
882
  metadata:
882
883
  allowed_push_host: https://rubygems.org
883
- changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v1.23.2/CHANGELOG.md
884
- source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v1.23.2
885
- post_install_message:
884
+ changelog_uri: https://github.com/DataDog/dd-trace-rb/blob/v1.23.3/CHANGELOG.md
885
+ source_code_uri: https://github.com/DataDog/dd-trace-rb/tree/v1.23.3
886
+ post_install_message: |2
887
+ Thank you for installing ddtrace. We have released our next major version!
888
+
889
+ As of version 2, `ddtrace` gem has been renamed to `datadog`.
890
+ The 1.x series will now only receive maintenance updates for security and critical bug fixes.
891
+
892
+ To upgrade, please replace gem `ddtrace` with gem `datadog`.
893
+ For detailed instructions on migration, see: https://dtdg.co/ruby-v2-upgrade
886
894
  rdoc_options: []
887
895
  require_paths:
888
896
  - lib
@@ -900,8 +908,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
900
908
  - !ruby/object:Gem::Version
901
909
  version: 2.0.0
902
910
  requirements: []
903
- rubygems_version: 3.4.10
904
- signing_key:
911
+ rubygems_version: 3.4.21
912
+ signing_key:
905
913
  specification_version: 4
906
914
  summary: Datadog tracing code for your Ruby applications
907
915
  test_files: []
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'emitter'
4
- require_relative 'event'
5
- require_relative 'heartbeat'
6
- require_relative '../utils/forking'
7
-
8
- module Datadog
9
- module Core
10
- module Telemetry
11
- # Telemetry entrypoint, coordinates sending telemetry events at various points in app lifecycle.
12
- class Client
13
- attr_reader \
14
- :enabled,
15
- :unsupported
16
-
17
- include Core::Utils::Forking
18
-
19
- # @param enabled [Boolean] Determines whether telemetry events should be sent to the API
20
- # @param heartbeat_interval_seconds [Float] How frequently heartbeats will be reported, in seconds.
21
- # @param [Boolean] dependency_collection Whether to send the `app-dependencies-loaded` event
22
- def initialize(heartbeat_interval_seconds:, dependency_collection:, enabled: true)
23
- @enabled = enabled
24
- @emitter = Emitter.new
25
- @stopped = false
26
- @unsupported = false
27
- @started = false
28
- @dependency_collection = dependency_collection
29
-
30
- @worker = Telemetry::Heartbeat.new(enabled: @enabled, heartbeat_interval_seconds: heartbeat_interval_seconds) do
31
- next unless @started # `started!` should be the first event, thus ensure that `heartbeat!` is not sent first.
32
-
33
- heartbeat!
34
- end
35
- end
36
-
37
- def disable!
38
- @enabled = false
39
- @worker.enabled = false
40
- end
41
-
42
- def started!
43
- return if !@enabled || forked?
44
-
45
- res = @emitter.request(Event::AppStarted.new)
46
-
47
- if res.not_found? # Telemetry is only supported by agent versions 7.34 and up
48
- Datadog.logger.debug('Agent does not support telemetry; disabling future telemetry events.')
49
- disable!
50
- @unsupported = true # Prevent telemetry from getting re-enabled
51
- return res
52
- end
53
-
54
- @emitter.request(Event::AppDependenciesLoaded.new) if @dependency_collection
55
-
56
- @started = true
57
- end
58
-
59
- def emit_closing!
60
- return if !@enabled || forked?
61
-
62
- @emitter.request(Event::AppClosing.new)
63
- end
64
-
65
- def stop!
66
- return if @stopped
67
-
68
- @worker.stop(true, 0)
69
- @stopped = true
70
- end
71
-
72
- def integrations_change!
73
- return if !@enabled || forked?
74
-
75
- @emitter.request(Event::AppIntegrationsChange.new)
76
- end
77
-
78
- # Report configuration changes caused by Remote Configuration.
79
- def client_configuration_change!(changes)
80
- return if !@enabled || forked?
81
-
82
- @emitter.request(Event::AppClientConfigurationChange.new(changes, 'remote_config'))
83
- end
84
-
85
- private
86
-
87
- def heartbeat!
88
- return if !@enabled || forked?
89
-
90
- @emitter.request(Event::AppHeartbeat.new)
91
- end
92
- end
93
- end
94
- end
95
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../worker'
4
- require_relative '../workers/polling'
5
-
6
- module Datadog
7
- module Core
8
- module Telemetry
9
- # Periodically (every DEFAULT_INTERVAL_SECONDS) sends a heartbeat event to the telemetry API.
10
- class Heartbeat < Core::Worker
11
- include Core::Workers::Polling
12
-
13
- def initialize(heartbeat_interval_seconds:, enabled: true, &block)
14
- # Workers::Polling settings
15
- self.enabled = enabled
16
- # Workers::IntervalLoop settings
17
- self.loop_base_interval = heartbeat_interval_seconds
18
- self.fork_policy = Core::Workers::Async::Thread::FORK_POLICY_STOP
19
- super(&block)
20
- start
21
- end
22
-
23
- def loop_wait_before_first_iteration?; end
24
-
25
- private
26
-
27
- def start
28
- perform
29
- end
30
- end
31
- end
32
- end
33
- end