ddtrace 0.46.0 → 0.47.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.
Files changed (70) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +52 -12
  3. data/.circleci/images/primary/{Dockerfile-jruby-9.2 → Dockerfile-jruby-9.2-latest} +2 -1
  4. data/.circleci/images/primary/Dockerfile-jruby-9.2.0.0 +73 -0
  5. data/.circleci/images/primary/Dockerfile-truffleruby-21.0.0 +73 -0
  6. data/.github/workflows/create-next-milestone.yml +2 -2
  7. data/.rubocop_todo.yml +3 -2
  8. data/.simplecov +6 -0
  9. data/Appraisals +1 -1
  10. data/CHANGELOG.md +83 -1
  11. data/Gemfile +19 -4
  12. data/LICENSE-3rdparty.csv +2 -0
  13. data/docker-compose.yml +75 -7
  14. data/docs/GettingStarted.md +61 -8
  15. data/lib/ddtrace/configuration.rb +92 -23
  16. data/lib/ddtrace/configuration/options.rb +2 -4
  17. data/lib/ddtrace/context_provider.rb +0 -2
  18. data/lib/ddtrace/contrib/active_record/configuration/resolver.rb +97 -26
  19. data/lib/ddtrace/contrib/aws/services.rb +1 -0
  20. data/lib/ddtrace/contrib/configurable.rb +63 -39
  21. data/lib/ddtrace/contrib/configuration/resolver.rb +70 -5
  22. data/lib/ddtrace/contrib/configuration/resolvers/pattern_resolver.rb +19 -17
  23. data/lib/ddtrace/contrib/configuration/settings.rb +7 -6
  24. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +1 -0
  25. data/lib/ddtrace/contrib/extensions.rb +26 -3
  26. data/lib/ddtrace/contrib/httpclient/instrumentation.rb +12 -16
  27. data/lib/ddtrace/contrib/httpclient/patcher.rb +5 -2
  28. data/lib/ddtrace/contrib/httprb/instrumentation.rb +12 -17
  29. data/lib/ddtrace/contrib/httprb/patcher.rb +5 -2
  30. data/lib/ddtrace/contrib/patcher.rb +8 -5
  31. data/lib/ddtrace/contrib/presto/patcher.rb +5 -2
  32. data/lib/ddtrace/contrib/rails/patcher.rb +6 -2
  33. data/lib/ddtrace/contrib/redis/configuration/resolver.rb +11 -4
  34. data/lib/ddtrace/contrib/resque/integration.rb +1 -1
  35. data/lib/ddtrace/ext/runtime.rb +2 -0
  36. data/lib/ddtrace/patcher.rb +23 -1
  37. data/lib/ddtrace/pin.rb +5 -9
  38. data/lib/ddtrace/runtime/cgroup.rb +1 -1
  39. data/lib/ddtrace/runtime/container.rb +25 -27
  40. data/lib/ddtrace/runtime/identity.rb +8 -0
  41. data/lib/ddtrace/sync_writer.rb +5 -2
  42. data/lib/ddtrace/tracer.rb +6 -4
  43. data/lib/ddtrace/transport/http.rb +14 -5
  44. data/lib/ddtrace/transport/http/adapters/net.rb +18 -4
  45. data/lib/ddtrace/transport/http/builder.rb +5 -1
  46. data/lib/ddtrace/transport/http/env.rb +8 -0
  47. data/lib/ddtrace/transport/io/response.rb +1 -3
  48. data/lib/ddtrace/transport/io/traces.rb +6 -0
  49. data/lib/ddtrace/transport/traces.rb +15 -1
  50. data/lib/ddtrace/utils/compression.rb +27 -0
  51. data/lib/ddtrace/utils/object_set.rb +41 -0
  52. data/lib/ddtrace/utils/only_once.rb +40 -0
  53. data/lib/ddtrace/utils/sequence.rb +17 -0
  54. data/lib/ddtrace/utils/string_table.rb +45 -0
  55. data/lib/ddtrace/utils/time.rb +7 -0
  56. data/lib/ddtrace/vendor/multipart-post/LICENSE +11 -0
  57. data/lib/ddtrace/vendor/multipart-post/multipart.rb +12 -0
  58. data/lib/ddtrace/vendor/multipart-post/multipart/post.rb +8 -0
  59. data/lib/ddtrace/vendor/multipart-post/multipart/post/composite_read_io.rb +116 -0
  60. data/lib/ddtrace/vendor/multipart-post/multipart/post/multipartable.rb +57 -0
  61. data/lib/ddtrace/vendor/multipart-post/multipart/post/parts.rb +135 -0
  62. data/lib/ddtrace/vendor/multipart-post/multipart/post/version.rb +9 -0
  63. data/lib/ddtrace/vendor/multipart-post/net/http/post/multipart.rb +32 -0
  64. data/lib/ddtrace/version.rb +1 -1
  65. data/lib/ddtrace/workers/async.rb +3 -3
  66. data/lib/ddtrace/workers/loop.rb +14 -3
  67. data/lib/ddtrace/workers/queue.rb +1 -0
  68. data/lib/ddtrace/workers/trace_writer.rb +1 -0
  69. data/lib/ddtrace/writer.rb +4 -1
  70. metadata +21 -4
data/Gemfile CHANGED
@@ -16,9 +16,15 @@ gem 'minitest', '= 5.10.1'
16
16
  gem 'minitest-around', '0.5.0'
17
17
  gem 'minitest-stub_any_instance', '1.0.2'
18
18
  gem 'pimpmychangelog', '>= 0.1.2'
19
- gem 'pry', '~> 0.12.2'
20
- gem 'pry-nav', '~> 0.3.0'
21
- gem 'pry-stack_explorer', '~> 0.4.9' if RUBY_PLATFORM != 'java'
19
+ gem 'pry'
20
+ if RUBY_PLATFORM != 'java'
21
+ # There's a few incompatibilities between pry/pry-byebug on older Rubies
22
+ gem 'pry-byebug' if RUBY_VERSION >= '2.6.0' && RUBY_ENGINE != 'truffleruby'
23
+ gem 'pry-nav' if RUBY_VERSION < '2.6.0'
24
+ gem 'pry-stack_explorer'
25
+ else
26
+ gem 'pry-debugger-jruby'
27
+ end
22
28
  gem 'rake', '>= 10.5'
23
29
  gem 'redcarpet', '~> 3.4' if RUBY_PLATFORM != 'java'
24
30
  gem 'rspec', '~> 3.10'
@@ -26,7 +32,16 @@ gem 'rspec-collection_matchers', '~> 1.1'
26
32
  gem 'rspec_junit_formatter', '>= 0.4.1'
27
33
  gem 'rspec_n', '~> 1.3' if RUBY_VERSION >= '2.3.0'
28
34
  gem 'ruby-prof', '~> 1.4' if RUBY_PLATFORM != 'java' && RUBY_VERSION >= '2.4.0'
29
- gem 'simplecov', '~> 0.17'
35
+ if RUBY_VERSION >= '2.5.0'
36
+ # Merging branch coverage results does not work for old, unsupported rubies.
37
+ # We have a fix up for review, https://github.com/simplecov-ruby/simplecov/pull/972,
38
+ # but given it only affects unsupported version of Ruby, it might not get merged.
39
+ gem 'simplecov', git: 'https://github.com/DataDog/simplecov', ref: '3bb6b7ee58bf4b1954ca205f50dd44d6f41c57db'
40
+ else
41
+ # Compatible with older rubies. This version still produces compatible output
42
+ # with a newer version when the reports are merged.
43
+ gem 'simplecov', '~> 0.17'
44
+ end
30
45
  gem 'warning', '~> 1' if RUBY_VERSION >= '2.5.0'
31
46
  gem 'webmock', '>= 3.10.0'
32
47
  gem 'webrick', '>= 1.7.0' if RUBY_VERSION >= '3.0.0' # No longer bundled by default since Ruby 3.0
@@ -0,0 +1,2 @@
1
+ Component,Origin,License,Copyright
2
+ ddtrace/vendor/multipart-post,https://github.com/socketry/multipart-post,MIT,"Copyright (c) 2007-2013 Nick Sieger."
data/docker-compose.yml CHANGED
@@ -277,8 +277,8 @@ services:
277
277
  - bundle-3.0:/usr/local/bundle
278
278
  - gemfiles-3.0:/app/gemfiles
279
279
  # JRuby
280
- tracer-jruby-9.2:
281
- image: marcotc/docker-library:ddtrace_rb_jruby_9_2
280
+ tracer-jruby-9.2.0.0:
281
+ image: marcotc/docker-library:ddtrace_rb_jruby_9_2_0_0
282
282
  command: /bin/bash
283
283
  depends_on:
284
284
  - ddagent
@@ -306,8 +306,71 @@ services:
306
306
  tty: true
307
307
  volumes:
308
308
  - .:/app
309
- - bundle-jruby-9.2:/usr/local/bundle
310
- - gemfiles-jruby-9.2:/app/gemfiles
309
+ - bundle-jruby-9.2.0.0:/usr/local/bundle
310
+ - gemfiles-jruby-9.2.0.0:/app/gemfiles
311
+ tracer-jruby-9.2-latest:
312
+ image: marcotc/docker-library:ddtrace_rb_jruby_9_2_11_1
313
+ command: /bin/bash
314
+ depends_on:
315
+ - ddagent
316
+ - elasticsearch
317
+ - memcached
318
+ - mongodb
319
+ - mysql
320
+ - postgres
321
+ - presto
322
+ - redis
323
+ env_file: ./.env
324
+ environment:
325
+ - BUNDLE_GEMFILE=/app/Gemfile
326
+ - DD_AGENT_HOST=ddagent
327
+ - TEST_DATADOG_INTEGRATION=1
328
+ - TEST_ELASTICSEARCH_HOST=elasticsearch
329
+ - TEST_MEMCACHED_HOST=memcached
330
+ - TEST_MONGODB_HOST=mongodb
331
+ - TEST_MYSQL_HOST=mysql
332
+ - TEST_POSTGRES_HOST=postgres
333
+ - TEST_PRESTO_HOST=presto
334
+ - TEST_PRESTO_PORT=8080
335
+ - TEST_REDIS_HOST=redis
336
+ stdin_open: true
337
+ tty: true
338
+ volumes:
339
+ - .:/app
340
+ - bundle-jruby-9.2-latest:/usr/local/bundle
341
+ - gemfiles-jruby-9.2-latest:/app/gemfiles
342
+ # TruffleRuby
343
+ tracer-truffleruby-21.0.0:
344
+ image: ivoanjo/docker-library:ddtrace_rb_truffleruby_21_0_0
345
+ command: /bin/bash
346
+ depends_on:
347
+ - ddagent
348
+ - elasticsearch
349
+ - memcached
350
+ - mongodb
351
+ - mysql
352
+ - postgres
353
+ - presto
354
+ - redis
355
+ env_file: ./.env
356
+ environment:
357
+ - BUNDLE_GEMFILE=/app/Gemfile
358
+ - DD_AGENT_HOST=ddagent
359
+ - TEST_DATADOG_INTEGRATION=1
360
+ - TEST_ELASTICSEARCH_HOST=elasticsearch
361
+ - TEST_MEMCACHED_HOST=memcached
362
+ - TEST_MONGODB_HOST=mongodb
363
+ - TEST_MYSQL_HOST=mysql
364
+ - TEST_POSTGRES_HOST=postgres
365
+ - TEST_PRESTO_HOST=presto
366
+ - TEST_PRESTO_PORT=8080
367
+ - TEST_REDIS_HOST=redis
368
+ stdin_open: true
369
+ tty: true
370
+ volumes:
371
+ - .:/app
372
+ - bundle-truffleruby-21.0.0:/usr/local/bundle
373
+ - gemfiles-truffleruby-21.0.0:/app/gemfiles
311
374
  ddagent:
312
375
  image: datadog/agent
313
376
  environment:
@@ -366,7 +429,8 @@ services:
366
429
  ports:
367
430
  - "${TEST_POSTGRES_PORT}:5432"
368
431
  presto:
369
- image: prestosql/presto
432
+ # Move to trinodb/trino after https://github.com/treasure-data/presto-client-ruby/issues/64 is resolved.
433
+ image: starburstdata/presto:332-e.9
370
434
  expose:
371
435
  - "8080"
372
436
  ports:
@@ -387,7 +451,9 @@ volumes:
387
451
  bundle-2.6:
388
452
  bundle-2.7:
389
453
  bundle-3.0:
390
- bundle-jruby-9.2:
454
+ bundle-jruby-9.2.0.0:
455
+ bundle-jruby-9.2-latest:
456
+ bundle-truffleruby-21.0.0:
391
457
  gemfiles-2.0:
392
458
  gemfiles-2.1:
393
459
  gemfiles-2.2:
@@ -397,4 +463,6 @@ volumes:
397
463
  gemfiles-2.6:
398
464
  gemfiles-2.7:
399
465
  gemfiles-3.0:
400
- gemfiles-jruby-9.2:
466
+ gemfiles-jruby-9.2.0.0:
467
+ gemfiles-jruby-9.2-latest:
468
+ gemfiles-truffleruby-21.0.0:
@@ -46,6 +46,7 @@ To contribute, check out the [contribution guidelines][contribution docs] and [d
46
46
  - [gRPC](#grpc)
47
47
  - [http.rb](#http-rb)
48
48
  - [httpclient](#httpclient)
49
+ - [httpx](#httpx)
49
50
  - [MongoDB](#mongodb)
50
51
  - [MySQL2](#mysql2)
51
52
  - [Net/HTTP](#net-http)
@@ -398,6 +399,7 @@ For a list of available integrations, and their configuration options, please re
398
399
  | gRPC | `grpc` | `>= 1.7` | *gem not available* | *[Link](#grpc)* | *[Link](https://github.com/grpc/grpc/tree/master/src/rubyc)* |
399
400
  | http.rb | `httprb` | `>= 2.0` | `>= 2.0` | *[Link](#http-rb)* | *[Link](https://github.com/httprb/http)* |
400
401
  | httpclient | `httpclient` | `>= 2.2` | `>= 2.2` | *[Link](#httpclient)* | *[Link](https://github.com/nahi/httpclient)* |
402
+ | httpx | `httpx` | `>= 0.11` | `>= 0.11` | *[Link](#httpx)* | *[Link](https://gitlab.com/honeyryderchuck/httpx)* |
401
403
  | Kafka | `ruby-kafka` | `>= 0.7.10` | `>= 0.7.10` | *[Link](#kafka)* | *[Link](https://github.com/zendesk/ruby-kafka)* |
402
404
  | MongoDB | `mongo` | `>= 2.1` | `>= 2.1` | *[Link](#mongodb)* | *[Link](https://github.com/mongodb/mongo-ruby-driver)* |
403
405
  | MySQL2 | `mysql2` | `>= 0.3.21` | *gem not available* | *[Link](#mysql2)* | *[Link](https://github.com/brianmario/mysql2)* |
@@ -546,17 +548,20 @@ Datadog.configure do |c|
546
548
  # Symbol matching your database connection in config/database.yml
547
549
  # Only available if you are using Rails with ActiveRecord.
548
550
  c.use :active_record, describes: :secondary_database, service_name: 'secondary-db'
549
-
551
+
552
+ # Block configuration pattern.
550
553
  c.use :active_record, describes: :secondary_database do |second_db|
551
554
  second_db.service_name = 'secondary-db'
552
555
  end
553
556
 
554
557
  # Connection string with the following connection settings:
555
- # Adapter, user, host, port, database
558
+ # adapter, username, host, port, database
559
+ # Other fields are ignored.
556
560
  c.use :active_record, describes: 'mysql2://root@127.0.0.1:3306/mysql', service_name: 'secondary-db'
557
561
 
558
- # Hash with following connection settings
559
- # Adapter, user, host, port, database
562
+ # Hash with following connection settings:
563
+ # adapter, username, host, port, database
564
+ # Other fields are ignored.
560
565
  c.use :active_record, describes: {
561
566
  adapter: 'mysql2',
562
567
  host: '127.0.0.1',
@@ -568,6 +573,27 @@ Datadog.configure do |c|
568
573
  end
569
574
  ```
570
575
 
576
+ You can also create configurations based on partial matching of database connection fields:
577
+
578
+ ```ruby
579
+ Datadog.configure do |c|
580
+ # Matches any connection on host `127.0.0.1`.
581
+ c.use :active_record, describes: { host: '127.0.0.1' }, service_name: 'local-db'
582
+
583
+ # Matches any `mysql2` connection.
584
+ c.use :active_record, describes: { adapter: 'mysql2'}, service_name: 'mysql-db'
585
+
586
+ # Matches any `mysql2` connection to the `reports` database.
587
+ #
588
+ # In case of multiple matching `describe` configurations, the latest one applies.
589
+ # In this case a connection with both adapter `mysql` and database `reports`
590
+ # will be configured `service_name: 'reports-db'`, not `service_name: 'mysql-db'`.
591
+ c.use :active_record, describes: { adapter: 'mysql2', database: 'reports'}, service_name: 'reports-db'
592
+ end
593
+ ```
594
+
595
+ When multiple `describes` configurations match a connection, the latest configured rule that matches will be applied.
596
+
571
597
  If ActiveRecord traces an event that uses a connection that matches a key defined by `describes`, it will use the trace settings assigned to that connection. If the connection does not match any of the described connections, it will use default settings defined by `c.use :active_record` instead.
572
598
 
573
599
  ### Active Support
@@ -1078,6 +1104,25 @@ Where `options` is an optional `Hash` that accepts the following parameters:
1078
1104
  | `service_name` | Service name for `httpclient` instrumentation. | `'httpclient'` |
1079
1105
  | `split_by_domain` | Uses the request domain as the service name when set to `true`. | `false` |
1080
1106
 
1107
+ ### httpx
1108
+
1109
+ `httpx` maintains its [own integration with `ddtrace`](https://honeyryderchuck.gitlab.io/httpx/wiki/Datadog-Adapter):
1110
+
1111
+ ```ruby
1112
+ require "ddtrace"
1113
+ require "httpx/adapters/datadog"
1114
+
1115
+ Datadog.configure do |c|
1116
+ c.use :httpx
1117
+
1118
+ # optionally, specify a different service name for hostnames matching a regex
1119
+ c.use :httpx, describes: /user-[^.]+\.example\.com/ do |http|
1120
+ http.service_name = 'user.example.com'
1121
+ http.split_by_domain = false # Only necessary if split_by_domain is true by default
1122
+ end
1123
+ end
1124
+ ```
1125
+
1081
1126
  ### Kafka
1082
1127
 
1083
1128
  The Kafka integration provides tracing of the `ruby-kafka` gem:
@@ -1537,12 +1582,17 @@ Datadog.configure do |c|
1537
1582
  # The default configuration for any redis client
1538
1583
  c.use :redis, service_name: 'redis-default'
1539
1584
 
1540
- # The configuration matching a given unix socket
1585
+ # The configuration matching a given unix socket.
1541
1586
  c.use :redis, describes: { url: 'unix://path/to/file' }, service_name: 'redis-unix'
1542
1587
 
1543
- # Connection string
1544
- c.use :redis, describes: { url: 'redis://127.0.0.1:6379/0' }, service_name: 'redis-connection-string'
1545
- # Client host, port, db, scheme
1588
+ # For network connections, only these fields are considered during matching:
1589
+ # scheme, host, port, db
1590
+ # Other fields are ignored.
1591
+
1592
+ # Network connection string
1593
+ c.use :redis, describes: 'redis://127.0.0.1:6379/0', service_name: 'redis-connection-string'
1594
+ c.use :redis, describes: { url: 'redis://127.0.0.1:6379/1' }, service_name: 'redis-connection-url'
1595
+ # Network client hash
1546
1596
  c.use :redis, describes: { host: 'my-host.com', port: 6379, db: 1, scheme: 'redis' }, service_name: 'redis-connection-hash'
1547
1597
  # Only a subset of the connection hash
1548
1598
  c.use :redis, describes: { host: ENV['APP_CACHE_HOST'], port: ENV['APP_CACHE_PORT'] }, service_name: 'redis-cache'
@@ -1550,6 +1600,8 @@ Datadog.configure do |c|
1550
1600
  end
1551
1601
  ```
1552
1602
 
1603
+ When multiple `describes` configurations match a connection, the latest configured rule that matches will be applied.
1604
+
1553
1605
  ### Resque
1554
1606
 
1555
1607
  The Resque integration uses Resque hooks that wraps the `perform` method.
@@ -2087,6 +2139,7 @@ For more details on how to activate distributed tracing for integrations, see th
2087
2139
  - [Sinatra](#sinatra)
2088
2140
  - [http.rb](#http-rb)
2089
2141
  - [httpclient](#httpclient)
2142
+ - [httpx](#httpx)
2090
2143
 
2091
2144
  **Using the HTTP propagator**
2092
2145
 
@@ -1,5 +1,4 @@
1
1
  require 'forwardable'
2
-
3
2
  require 'ddtrace/configuration/pin_setup'
4
3
  require 'ddtrace/configuration/settings'
5
4
  require 'ddtrace/configuration/components'
@@ -9,6 +8,33 @@ module Datadog
9
8
  module Configuration
10
9
  extend Forwardable
11
10
 
11
+ # Used to ensure that @components initialization/reconfiguration is performed one-at-a-time, by a single thread.
12
+ #
13
+ # This is important because components can end up being accessed from multiple application threads (for instance on
14
+ # a threaded webserver), and we don't want their initialization to clash (for instance, starting two profilers...).
15
+ #
16
+ # Note that a Mutex **IS NOT** reentrant: the same thread cannot grab the same Mutex more than once.
17
+ # This means below we are careful not to nest calls to methods that would trigger initialization and grab the lock.
18
+ #
19
+ # Every method that directly or indirectly mutates @components should be holding the lock (through
20
+ # #safely_synchronize) while doing so.
21
+ COMPONENTS_WRITE_LOCK = Mutex.new
22
+ private_constant :COMPONENTS_WRITE_LOCK
23
+
24
+ # We use a separate lock when reading the @components, so that they continue to be accessible during reconfiguration.
25
+ # This was needed because we ran into several issues where we still needed to read the old
26
+ # components while the COMPONENTS_WRITE_LOCK was being held (see https://github.com/DataDog/dd-trace-rb/pull/1387
27
+ # and https://github.com/DataDog/dd-trace-rb/pull/1373#issuecomment-799593022 ).
28
+ #
29
+ # Technically on MRI we could get away without this lock, but on non-MRI Rubies, we may run into issues because
30
+ # we fall into the "UnsafeDCLFactory" case of https://shipilev.net/blog/2014/safe-public-construction/ .
31
+ # Specifically, on JRuby reads from the @components do NOT have volatile semantics, and on TruffleRuby they do
32
+ # BUT just as an implementation detail, see https://github.com/jruby/jruby/wiki/Concurrency-in-jruby#volatility and
33
+ # https://github.com/DataDog/dd-trace-rb/pull/1329#issuecomment-776750377 .
34
+ # Concurrency is hard.
35
+ COMPONENTS_READ_LOCK = Mutex.new
36
+ private_constant :COMPONENTS_READ_LOCK
37
+
12
38
  attr_writer :configuration
13
39
 
14
40
  def configuration
@@ -19,13 +45,15 @@ module Datadog
19
45
  if target.is_a?(Settings)
20
46
  yield(target) if block_given?
21
47
 
22
- # Build immutable components from settings
23
- @components ||= nil
24
- @components = if @components
25
- replace_components!(target, @components)
26
- else
27
- build_components(target)
28
- end
48
+ safely_synchronize do |write_components|
49
+ write_components.call(
50
+ if components?
51
+ replace_components!(target, @components)
52
+ else
53
+ build_components(target)
54
+ end
55
+ )
56
+ end
29
57
 
30
58
  target
31
59
  else
@@ -40,18 +68,14 @@ module Datadog
40
68
  :tracer
41
69
 
42
70
  def logger
43
- if instance_variable_defined?(:@components) && @components
71
+ # avoid initializing components if they didn't already exist
72
+ current_components = components(allow_initialization: false)
73
+
74
+ if current_components
44
75
  @temp_logger = nil
45
- components.logger
76
+ current_components.logger
46
77
  else
47
- # Use default logger without initializing components.
48
- # This prevents recursive loops while initializing.
49
- # e.g. Get logger --> Build components --> Log message --> Repeat...
50
- @temp_logger ||= begin
51
- logger = configuration.logger.instance || Datadog::Logger.new($stdout)
52
- logger.level = configuration.diagnostics.debug ? ::Logger::DEBUG : configuration.logger.level
53
- logger
54
- end
78
+ logger_without_components
55
79
  end
56
80
  end
57
81
 
@@ -65,7 +89,9 @@ module Datadog
65
89
  #
66
90
  # Components won't be automatically reinitialized after a shutdown.
67
91
  def shutdown!
68
- components.shutdown! if instance_variable_defined?(:@components) && @components
92
+ safely_synchronize do
93
+ @components.shutdown! if components?
94
+ end
69
95
  end
70
96
 
71
97
  # Gracefully shuts down the tracer and disposes of component references,
@@ -74,18 +100,51 @@ module Datadog
74
100
  # In contrast with +#shutdown!+, components will be automatically
75
101
  # reinitialized after a reset.
76
102
  def reset!
77
- shutdown!
78
- @components = nil
103
+ safely_synchronize do |write_components|
104
+ @components.shutdown! if components?
105
+ write_components.call(nil)
106
+ end
79
107
  end
80
108
 
81
109
  protected
82
110
 
83
- def components
84
- @components ||= build_components(configuration)
111
+ def components(allow_initialization: true)
112
+ current_components = COMPONENTS_READ_LOCK.synchronize { defined?(@components) && @components }
113
+ return current_components if current_components || !allow_initialization
114
+
115
+ safely_synchronize do |write_components|
116
+ (defined?(@components) && @components) || write_components.call(build_components(configuration))
117
+ end
85
118
  end
86
119
 
87
120
  private
88
121
 
122
+ def safely_synchronize
123
+ # Writes to @components should only happen through this proc. Because this proc is only accessible to callers of
124
+ # safely_synchronize, this forces all writers to go through this method.
125
+ write_components = proc do |new_value|
126
+ COMPONENTS_READ_LOCK.synchronize { @components = new_value }
127
+ end
128
+
129
+ COMPONENTS_WRITE_LOCK.synchronize do
130
+ begin
131
+ yield write_components
132
+ rescue ThreadError => e
133
+ logger_without_components.error(
134
+ 'Detected deadlock during ddtrace initialization. ' \
135
+ 'Please report this at https://github.com/DataDog/dd-trace-rb/blob/master/CONTRIBUTING.md#found-a-bug' \
136
+ "\n\tSource:\n\t#{e.backtrace.join("\n\t")}"
137
+ )
138
+ nil
139
+ end
140
+ end
141
+ end
142
+
143
+ def components?
144
+ # This does not need to grab the COMPONENTS_READ_LOCK because it's not returning the components
145
+ (defined?(@components) && @components) != nil
146
+ end
147
+
89
148
  def build_components(settings)
90
149
  components = Components.new(settings)
91
150
  components.startup!(settings)
@@ -99,5 +158,15 @@ module Datadog
99
158
  components.startup!(settings)
100
159
  components
101
160
  end
161
+
162
+ def logger_without_components
163
+ # Use default logger without initializing components.
164
+ # This enables logging during initialization, otherwise we'd run into deadlocks.
165
+ @temp_logger ||= begin
166
+ logger = configuration.logger.instance || Datadog::Logger.new($stdout)
167
+ logger.level = configuration.diagnostics.debug ? ::Logger::DEBUG : configuration.logger.level
168
+ logger
169
+ end
170
+ end
102
171
  end
103
172
  end