julewire-core 1.0.1 → 1.1.1

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/docs/configuration.md +14 -7
  4. data/docs/context-and-propagation.md +20 -0
  5. data/docs/contracts.md +23 -49
  6. data/docs/extensions-and-api.md +78 -82
  7. data/docs/health-schema.md +12 -2
  8. data/docs/internals.md +21 -20
  9. data/docs/outputs-and-lifecycle.md +10 -6
  10. data/docs/records-and-data-policy.md +36 -5
  11. data/lib/julewire/core/cli/doctor.rb +20 -16
  12. data/lib/julewire/core/cli/line_helpers.rb +23 -12
  13. data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
  14. data/lib/julewire/core/cli/log_formats.rb +9 -9
  15. data/lib/julewire/core/cli/tail.rb +19 -14
  16. data/lib/julewire/core/cli/transcode.rb +4 -5
  17. data/lib/julewire/core/cli.rb +1 -1
  18. data/lib/julewire/core/configuration.rb +1 -1
  19. data/lib/julewire/core/context_store.rb +51 -87
  20. data/lib/julewire/core/destinations/chaos_output.rb +2 -2
  21. data/lib/julewire/core/destinations/collection.rb +8 -16
  22. data/lib/julewire/core/destinations/definition.rb +1 -9
  23. data/lib/julewire/core/destinations/destination.rb +13 -63
  24. data/lib/julewire/core/destinations/processor_handling.rb +78 -0
  25. data/lib/julewire/core/destinations/registry.rb +1 -1
  26. data/lib/julewire/core/destinations/sink.rb +0 -1
  27. data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
  28. data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
  29. data/lib/julewire/core/destinations/write_step.rb +5 -5
  30. data/lib/julewire/core/destinations.rb +0 -7
  31. data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
  32. data/lib/julewire/core/diagnostics/doctor.rb +16 -9
  33. data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
  34. data/lib/julewire/core/diagnostics/health.rb +41 -57
  35. data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
  36. data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
  37. data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
  38. data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
  39. data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
  40. data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
  41. data/lib/julewire/core/diagnostics/tail.rb +18 -22
  42. data/lib/julewire/core/execution/boundary.rb +2 -8
  43. data/lib/julewire/core/execution/handle.rb +12 -17
  44. data/lib/julewire/core/execution/lineage.rb +16 -56
  45. data/lib/julewire/core/execution/measurement_handle.rb +6 -10
  46. data/lib/julewire/core/execution/scope.rb +15 -20
  47. data/lib/julewire/core/execution/scope_fields.rb +3 -12
  48. data/lib/julewire/core/execution/scope_identity.rb +1 -9
  49. data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
  50. data/lib/julewire/core/execution/summary_state.rb +16 -19
  51. data/lib/julewire/core/execution/view.rb +8 -17
  52. data/lib/julewire/core/facade_methods.rb +67 -65
  53. data/lib/julewire/core/fields/attribute_keys.rb +0 -2
  54. data/lib/julewire/core/fields/bags.rb +23 -27
  55. data/lib/julewire/core/fields/field_set.rb +29 -19
  56. data/lib/julewire/core/fields/field_stack.rb +68 -134
  57. data/lib/julewire/core/fields/internal/deletion.rb +6 -9
  58. data/lib/julewire/core/fields/internal.rb +19 -38
  59. data/lib/julewire/core/fields/lookup.rb +9 -13
  60. data/lib/julewire/core/fields/section_proxy.rb +1 -9
  61. data/lib/julewire/core/fields/stack_set.rb +7 -12
  62. data/lib/julewire/core/fields/static_labels.rb +1 -1
  63. data/lib/julewire/core/integration/configurable.rb +4 -3
  64. data/lib/julewire/core/integration/destination_health.rb +17 -7
  65. data/lib/julewire/core/integration/event_subscriber.rb +1 -1
  66. data/lib/julewire/core/integration/facade.rb +12 -15
  67. data/lib/julewire/core/integration/fork_hooks.rb +12 -36
  68. data/lib/julewire/core/integration/health.rb +0 -3
  69. data/lib/julewire/core/integration/ivar_state.rb +0 -5
  70. data/lib/julewire/core/integration/protocol.rb +20 -0
  71. data/lib/julewire/core/integration/settings.rb +4 -4
  72. data/lib/julewire/core/integration/subscriber_install.rb +2 -2
  73. data/lib/julewire/core/integration/subscription.rb +3 -2
  74. data/lib/julewire/core/integration/values.rb +0 -17
  75. data/lib/julewire/core/local_storage.rb +3 -19
  76. data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
  77. data/lib/julewire/core/processing/level_threshold.rb +7 -14
  78. data/lib/julewire/core/processing/match.rb +6 -4
  79. data/lib/julewire/core/processing/pipeline.rb +46 -40
  80. data/lib/julewire/core/processing/processor_chain.rb +22 -11
  81. data/lib/julewire/core/processing/processor_registry.rb +3 -4
  82. data/lib/julewire/core/processing/record_field_transform.rb +0 -4
  83. data/lib/julewire/core/processing/sampling.rb +1 -3
  84. data/lib/julewire/core/processing.rb +2 -2
  85. data/lib/julewire/core/propagation/carrier.rb +14 -14
  86. data/lib/julewire/core/propagation.rb +15 -2
  87. data/lib/julewire/core/records/build_input.rb +75 -0
  88. data/lib/julewire/core/records/console_formatter.rb +6 -6
  89. data/lib/julewire/core/records/deconstruct.rb +1 -1
  90. data/lib/julewire/core/records/display_message.rb +2 -4
  91. data/lib/julewire/core/records/draft.rb +125 -262
  92. data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
  93. data/lib/julewire/core/records/metadata.rb +4 -3
  94. data/lib/julewire/core/records/public_projection.rb +2 -2
  95. data/lib/julewire/core/records/raw_input.rb +1 -1
  96. data/lib/julewire/core/records/record.rb +19 -37
  97. data/lib/julewire/core/records/severity.rb +7 -15
  98. data/lib/julewire/core/runtime.rb +67 -71
  99. data/lib/julewire/core/runtime_registry.rb +20 -15
  100. data/lib/julewire/core/runtime_state.rb +5 -3
  101. data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
  102. data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
  103. data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
  104. data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
  105. data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
  106. data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
  107. data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
  108. data/lib/julewire/core/serialization/exception_shape.rb +7 -6
  109. data/lib/julewire/core/serialization/json_encoder.rb +10 -13
  110. data/lib/julewire/core/serialization/serializer.rb +23 -50
  111. data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
  112. data/lib/julewire/core/serialization/text_encoder.rb +5 -5
  113. data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
  114. data/lib/julewire/core/serialization/value_copy.rb +60 -227
  115. data/lib/julewire/core/serialization/value_traversal.rb +10 -127
  116. data/lib/julewire/core/testing.rb +6 -56
  117. data/lib/julewire/core/validation.rb +2 -3
  118. data/lib/julewire/core/version.rb +1 -1
  119. data/lib/julewire/core.rb +2 -7
  120. metadata +6 -19
  121. data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
  122. data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
  123. data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
  124. data/lib/julewire/core/testing/chaos/destination.rb +0 -55
  125. data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
  126. data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
  127. data/lib/julewire/core/testing/chaos.rb +0 -80
  128. data/lib/julewire/core/testing/contracts/component.rb +0 -162
  129. data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
  130. data/lib/julewire/core/testing/contracts/integration.rb +0 -166
  131. data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
  132. data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
  133. data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
  134. data/lib/julewire/core/testing/contracts/wire.rb +0 -60
  135. data/lib/julewire/core/testing/contracts.rb +0 -24
  136. data/lib/julewire/core/testing/coverage.rb +0 -58
  137. data/lib/julewire/core/testing/test_reports.rb +0 -78
@@ -1,64 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_boolean"
4
+ require "concurrent/atomic/atomic_fixnum"
5
+
3
6
  module Julewire
4
7
  module Core
5
8
  module Diagnostics
6
9
  module InvalidSeverityReporter
7
- @warned = false
8
- @mutex = Mutex.new
10
+ @warned = Concurrent::AtomicBoolean.new
9
11
 
10
12
  class RuntimeCounter
11
13
  def initialize
12
- @mutex = Mutex.new
13
- @count = 0
14
- @last = nil
14
+ @count = Concurrent::AtomicFixnum.new
15
15
  end
16
16
 
17
- def call(value, source: nil, event: nil)
18
- metadata = InvalidSeverityReporter.metadata(value, source: source, event: event)
19
- @mutex.synchronize do
20
- @count += 1
21
- @last = metadata
22
- end
17
+ def call(value)
18
+ metadata = InvalidSeverityReporter.metadata(value)
19
+ @count.increment
23
20
  InvalidSeverityReporter.warn_once(metadata)
24
21
  rescue StandardError
25
22
  nil
26
23
  end
27
24
 
28
25
  def health
29
- @mutex.synchronize do
30
- {
31
- count: @count,
32
- last_event: @last&.fetch(:event, nil),
33
- last_source: @last&.fetch(:source, nil),
34
- last_value_class: @last&.fetch(:value_class, nil)
35
- }.compact
36
- end
26
+ { count: @count.value }
37
27
  end
38
28
 
39
29
  def reset!
40
- @mutex.synchronize do
41
- @count = 0
42
- @last = nil
43
- end
44
- nil
45
- end
46
-
47
- def reset_after_fork!
48
- @mutex = Mutex.new
49
- @count = 0
50
- @last = nil
51
- nil
30
+ @count.value = 0
52
31
  end
53
32
  end
54
33
 
55
34
  private_constant :RuntimeCounter
56
35
 
57
36
  class << self
58
- def call(value, source: nil, event: nil)
59
- warning_only.call(value, source: source, event: event)
60
- rescue StandardError
61
- nil
37
+ def call(value)
38
+ warning_only.call(value)
62
39
  end
63
40
 
64
41
  def counter = RuntimeCounter.new
@@ -71,22 +48,11 @@ module Julewire
71
48
  end
72
49
 
73
50
  def reset!
74
- @mutex.synchronize { @warned = false }
75
- nil
76
- end
77
-
78
- def reset_after_fork!
79
- @mutex = Mutex.new
80
- @warned = false
81
- nil
51
+ @warned.make_false
82
52
  end
83
53
 
84
- def metadata(value, source:, event:)
85
- {
86
- event: event,
87
- source: source,
88
- value_class: value.class.name || value.class.to_s
89
- }.compact.freeze
54
+ def metadata(value)
55
+ { value_class: value.class.to_s }.freeze
90
56
  rescue StandardError
91
57
  { value_class: "unknown" }.freeze
92
58
  end
@@ -98,12 +64,7 @@ module Julewire
98
64
  end
99
65
 
100
66
  def first_warning?
101
- @mutex.synchronize do
102
- return false if @warned
103
-
104
- @warned = true
105
- true
106
- end
67
+ @warned.make_true
107
68
  end
108
69
  end
109
70
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_boolean"
4
+ require "concurrent/atomic/atomic_reference"
5
+
3
6
  module Julewire
4
7
  module Core
5
8
  module Diagnostics
@@ -39,31 +42,26 @@ module Julewire
39
42
  @interval = Validation.validate_integer_limit!(interval, name: :interval, positive: true)
40
43
  @include_ok = include_ok ? true : false
41
44
  @scheduler = scheduler
42
- @mutex = Mutex.new
43
- @last_signature = nil
44
- @last_failure = nil
45
- @started = false
46
- @stopped = false
47
- @token = nil
45
+ @last_failure = Concurrent::AtomicReference.new
46
+ @last_signature = Concurrent::AtomicReference.new
47
+ @running = Concurrent::AtomicBoolean.new
48
+ @schedule_mutex = Mutex.new
48
49
  @serializer_pool_key = :"julewire_core_meta_observer_serializers_#{object_id}"
49
50
  end
50
51
 
51
52
  def start!
52
- @mutex.synchronize do
53
- return self if @started && !@stopped
53
+ return self unless @running.make_true
54
54
 
55
- @started = true
56
- @stopped = false
57
- schedule_next
58
- end
55
+ schedule_next
59
56
  self
60
57
  end
61
58
 
62
59
  def stop!
63
- token = @mutex.synchronize do
64
- @stopped = true
65
- @started = false
66
- @token
60
+ token = @schedule_mutex.synchronize do
61
+ @running.make_false
62
+ token = @token
63
+ @token = nil
64
+ token
67
65
  end
68
66
  @scheduler.cancel(token) if token
69
67
  self
@@ -72,12 +70,7 @@ module Julewire
72
70
  def sample!
73
71
  health = @runtime.health
74
72
  signature = signature_for(health)
75
- changed = @mutex.synchronize do
76
- changed = signature != @last_signature
77
- @last_signature = signature
78
- changed
79
- end
80
- return false unless changed
73
+ return false if signature.eql?(@last_signature.get_and_set(signature))
81
74
  return false unless emit_health?(health)
82
75
 
83
76
  emit_health(health)
@@ -88,35 +81,38 @@ module Julewire
88
81
  end
89
82
 
90
83
  def health
91
- @mutex.synchronize do
92
- {
93
- event: @event,
94
- include_ok: @include_ok,
95
- interval: @interval,
96
- last_failure: @last_failure,
97
- observed_runtime: @runtime_name,
98
- running: @started && !@stopped,
99
- status: @last_failure ? :degraded : :ok,
100
- target_runtime: @target_name
101
- }.compact.freeze
102
- end
84
+ failure = @last_failure.get
85
+ {
86
+ event: @event,
87
+ include_ok: @include_ok,
88
+ interval: @interval,
89
+ last_failure: failure,
90
+ observed_runtime: @runtime_name,
91
+ running: @running.true?,
92
+ status: failure ? :degraded : :ok,
93
+ target_runtime: @target_name
94
+ }.compact.freeze
103
95
  end
104
96
 
105
97
  private
106
98
 
107
99
  def schedule_next
108
- @token = @scheduler.schedule(@interval) { scheduled_sample }
100
+ @schedule_mutex.synchronize do
101
+ @token = @scheduler.schedule(@interval) { scheduled_sample } if @running.true?
102
+ end
109
103
  end
110
104
 
111
105
  def scheduled_sample
106
+ return false unless @running.true?
107
+
112
108
  sample!
113
- @mutex.synchronize { schedule_next unless @stopped }
109
+ schedule_next
114
110
  rescue StandardError => e
115
111
  record_failure(e)
116
112
  end
117
113
 
118
114
  def emit_health?(health)
119
- @include_ok || health[:status] != :ok
115
+ @include_ok || !health[:status].eql?(:ok)
120
116
  end
121
117
 
122
118
  def emit_health(health)
@@ -137,23 +133,20 @@ module Julewire
137
133
  end
138
134
 
139
135
  def signature_for(health)
140
- serializer = cached_serializer
141
- return build_serializer.serialize(health).hash if serializer.in_use?
142
-
143
- serializer.serialize(health).hash
136
+ Serialization::SerializerPool.serialize_with(cached_serializer, health) { build_serializer }
144
137
  end
145
138
 
146
139
  def cached_serializer
147
- Serialization::SerializerPool.serializer(@serializer_pool_key, :signature) { build_serializer }
140
+ Serialization::SerializerPool.serializer(@serializer_pool_key, nil) { build_serializer }
148
141
  end
149
142
 
150
143
  def build_serializer
151
- Serialization::Serializer.new(compact_empty: true)
144
+ Serializer.new(compact_empty: true)
152
145
  end
153
146
 
154
147
  def record_failure(error)
155
148
  failure = FailureSnapshot.build(error, phase: :meta_observer)
156
- @mutex.synchronize { @last_failure = failure }
149
+ @last_failure.set(failure)
157
150
  end
158
151
  end
159
152
  end
@@ -14,11 +14,6 @@ module Julewire
14
14
  def health = @store.health
15
15
 
16
16
  def reset! = @store.reset!
17
-
18
- def after_fork!
19
- @store = IntegrationHealthStore.new
20
- nil
21
- end
22
17
  end
23
18
  end
24
19
  end
@@ -27,7 +27,7 @@ module Julewire
27
27
 
28
28
  def payload_for(entry)
29
29
  record = entry.record
30
- record.merge("timestamp" => record["timestamp"] || entry.at.iso8601(6))
30
+ record.merge("timestamp" => record["timestamp"] || entry.at)
31
31
  end
32
32
  end
33
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_reference"
4
+
3
5
  module Julewire
4
6
  module Core
5
7
  module Diagnostics
@@ -8,6 +10,9 @@ module Julewire
8
10
  DEFAULT_NAME = :tail
9
11
  COUNTER_KEYS = %i[captured failures].freeze
10
12
  Entry = Data.define(:sequence, :at, :record)
13
+ State = Data.define(:sequence, :items)
14
+ EMPTY_ENTRIES = [].freeze
15
+ private_constant :State, :EMPTY_ENTRIES
11
16
 
12
17
  class << self
13
18
  def attach!(runtime = Julewire, **)
@@ -39,11 +44,10 @@ module Julewire
39
44
 
40
45
  def emit(record)
41
46
  snapshot = snapshot_record(record)
42
- @mutex.synchronize do
43
- @sequence += 1
44
- entry = Entry.new(@sequence, Time.now.utc, snapshot)
45
- @entries << entry
46
- @entries.shift while @entries.length > @capacity
47
+ @state_ref.update do |state|
48
+ sequence = state.sequence + 1
49
+ entries = (state.items + [Entry.new(sequence, Time.now.utc, snapshot)]).last(@capacity)
50
+ State.new(sequence, entries)
47
51
  end
48
52
  @health.increment(:captured)
49
53
  nil
@@ -54,7 +58,7 @@ module Julewire
54
58
 
55
59
  def entries(limit: nil)
56
60
  limit = normalize_limit(limit)
57
- snapshot = @mutex.synchronize { @entries.dup }
61
+ snapshot = @state_ref.get.items.dup
58
62
  limit ? snapshot.last(limit) : snapshot
59
63
  end
60
64
 
@@ -72,10 +76,8 @@ module Julewire
72
76
  end
73
77
 
74
78
  def clear
75
- @mutex.synchronize do
76
- @entries.clear
77
- end
78
- @health.clear_degraded!
79
+ @state_ref.update { State.new(it.sequence, EMPTY_ENTRIES) }
80
+ @health.clear_failures!
79
81
  self
80
82
  end
81
83
 
@@ -89,17 +91,14 @@ module Julewire
89
91
  end
90
92
 
91
93
  def health
92
- size = @mutex.synchronize { @entries.length }
93
- @health.snapshot(capacity: @capacity, size: size)
94
+ @health.snapshot(capacity: @capacity, size: entries.length)
94
95
  end
95
96
 
96
97
  private
97
98
 
98
99
  def initialize_state
99
- @mutex = Mutex.new
100
- @entries = []
100
+ @state_ref = Concurrent::AtomicReference.new(State.new(0, EMPTY_ENTRIES))
101
101
  @health = Integration::DestinationHealth.new(counter_keys: COUNTER_KEYS)
102
- @sequence = 0
103
102
  @serializer_mutex = Mutex.new
104
103
  end
105
104
 
@@ -123,7 +122,7 @@ module Julewire
123
122
  message = Records::DisplayMessage.call(record)
124
123
  return payload if blank?(message)
125
124
 
126
- payload = payload.dup if payload.frozen?
125
+ payload = payload.dup
127
126
  payload["message"] = message
128
127
  payload
129
128
  end
@@ -131,10 +130,7 @@ module Julewire
131
130
  def serialize_payload(payload)
132
131
  return serialize_custom_payload(payload) if @serializer
133
132
 
134
- serializer = cached_serializer
135
- return build_serializer.serialize(payload) if serializer.in_use?
136
-
137
- serializer.serialize(payload)
133
+ Serialization::SerializerPool.serialize_with(cached_serializer, payload) { build_serializer }
138
134
  end
139
135
 
140
136
  def serialize_custom_payload(payload)
@@ -142,11 +138,11 @@ module Julewire
142
138
  end
143
139
 
144
140
  def cached_serializer
145
- Serialization::SerializerPool.serializer(@serializer_pool_key, :default) { build_serializer }
141
+ Serialization::SerializerPool.serializer(@serializer_pool_key, nil) { build_serializer }
146
142
  end
147
143
 
148
144
  def build_serializer
149
- Serialization::Serializer.new(compact_empty: true)
145
+ Serializer.new(compact_empty: true)
150
146
  end
151
147
 
152
148
  def blank?(value)
@@ -74,15 +74,11 @@ module Julewire
74
74
 
75
75
  EXECUTION_OPTION_DEFAULTS.merge(options).tap do |normalized|
76
76
  normalized[:execution] = execution_fields(normalized.delete(:fields))
77
- normalized[:attributes] ||= EMPTY_HASH
78
- normalized[:neutral] ||= EMPTY_HASH
79
- normalized[:labels] ||= EMPTY_HASH
80
77
  end
81
78
  end
82
79
 
83
80
  def execution_fields(fields)
84
- return {} if fields.nil?
85
- raise ArgumentError, "execution fields must be a Hash" unless fields.is_a?(Hash)
81
+ raise ArgumentError, "execution fields must be a Hash" unless fields.nil? || fields.is_a?(Hash)
86
82
 
87
83
  fields
88
84
  end
@@ -91,9 +87,7 @@ module Julewire
91
87
  return unless emit_summary_enabled
92
88
 
93
89
  @summary_finalizer ||= lambda do |scope|
94
- next if suppress_summary_for_non_standard_exception?(scope)
95
-
96
- @emit_summary_record.call(scope)
90
+ @emit_summary_record.call(scope) unless suppress_summary_for_non_standard_exception?(scope)
97
91
  end
98
92
  end
99
93
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_boolean"
4
+
3
5
  module Julewire
4
6
  module Core
5
7
  module Execution
@@ -8,12 +10,9 @@ module Julewire
8
10
  @scope = scope
9
11
  @on_finish = on_finish
10
12
  @on_finish_failure = on_finish_failure
11
- @mutex = Mutex.new
12
- @finished = false
13
+ @finished = Concurrent::AtomicBoolean.new
13
14
  end
14
15
 
15
- attr_reader :scope
16
-
17
16
  def snapshot = View.new(@scope)
18
17
 
19
18
  def run
@@ -32,44 +31,40 @@ module Julewire
32
31
  ContextStore.current.with_scope(@scope, &)
33
32
  end
34
33
 
35
- def finish(reason: :closed, fields: {}, attributes: {}, error: nil, severity: nil)
34
+ def finish(reason: :closed, fields: nil, attributes: nil, error: nil, severity: nil)
36
35
  # Finishing is one-shot: retrying after partial summary mutation can
37
36
  # duplicate completion data, so failures are reported and stop here.
38
37
  return false unless mark_finished
39
38
 
40
39
  add_completion_attributes(reason)
41
- @scope.add_summary(fields) unless fields.empty?
42
- @scope.add_summary_attributes(attributes) unless attributes.empty?
40
+ @scope.add_summary(fields)
41
+ @scope.add_summary_attributes(attributes)
43
42
  @scope.finish_owned(error: error, severity: severity)
44
43
  call_finish
45
44
  true
46
45
  rescue StandardError => e
47
- report_finish_failure(e)
46
+ report_finish_failure(e, phase: :summary_finish)
48
47
  false
49
48
  end
50
49
 
51
50
  private
52
51
 
53
52
  def mark_finished
54
- @mutex.synchronize do
55
- return false if @finished
56
-
57
- @finished = true
58
- end
53
+ @finished.make_true
59
54
  end
60
55
 
61
56
  def add_completion_attributes(reason)
62
- @scope.add_summary_attributes({ "julewire.completion": reason.to_s }, owned: true)
57
+ @scope.add_summary_attributes({ "julewire.completion": reason.to_s })
63
58
  end
64
59
 
65
60
  def call_finish
66
61
  @on_finish&.call(@scope)
67
62
  rescue StandardError => e
68
- report_finish_failure(e)
63
+ report_finish_failure(e, phase: :summary_emit)
69
64
  end
70
65
 
71
- def report_finish_failure(error)
72
- @on_finish_failure&.call(error)
66
+ def report_finish_failure(error, phase:)
67
+ @on_finish_failure.call(error, phase: phase)
73
68
  rescue StandardError
74
69
  nil
75
70
  end
@@ -24,7 +24,6 @@ module Julewire
24
24
  end
25
25
 
26
26
  def from_execution_hash(execution)
27
- execution = {} unless execution.is_a?(Hash)
28
27
  new(
29
28
  reference: execution_reference(execution),
30
29
  root_reference: relationship_value(execution, :root),
@@ -68,7 +67,9 @@ module Julewire
68
67
  id = reference_value(execution, :id)
69
68
  reference[:type] = type unless type.equal?(MISSING)
70
69
  reference[:id] = id unless id.equal?(MISSING)
71
- reference.empty? ? nil : reference
70
+ return if reference.empty?
71
+
72
+ reference
72
73
  end
73
74
  end
74
75
 
@@ -81,15 +82,18 @@ module Julewire
81
82
  ancestors: nil,
82
83
  ancestors_truncated: false
83
84
  )
84
- @parent_lineage = parent_lineage
85
85
  @depth = depth_value(depth, parent_lineage)
86
- @root_reference = freeze_reference(root_reference || root_reference_for(reference, parent_lineage))
86
+ @root_reference = freeze_reference(
87
+ root_reference || parent_lineage&.root_reference_for_child || reference
88
+ )
87
89
  @parent_reference = freeze_reference(parent_reference)
88
90
  @ancestor_references = freeze_ancestors(ancestors)
89
91
  @ancestors_truncated = ancestors_truncated ? true : false
90
- @ancestor_references_for_child = nil
91
- @truncated = nil
92
- @truncated_computed = false
92
+ return if @ancestor_references
93
+
94
+ references = inherited_ancestor_references(parent_lineage)
95
+ @ancestor_references = references.last(MAX_ANCESTORS).freeze
96
+ @ancestors_truncated ||= references.length > MAX_ANCESTORS
93
97
  end
94
98
 
95
99
  def merge_into_frozen(execution)
@@ -100,34 +104,14 @@ module Julewire
100
104
  hash.freeze
101
105
  end
102
106
 
103
- def ancestors
104
- references = @ancestor_references_for_child
105
- return references if references
106
-
107
- materialize_ancestor_references_for_child
108
- end
109
-
110
- def truncated?
111
- return @truncated if @truncated_computed
107
+ def ancestors = @ancestor_references
112
108
 
113
- @truncated = @ancestors_truncated || ancestor_count_exceeds_limit?
114
- @truncated_computed = true
115
- @truncated
116
- end
117
-
118
- def freeze
119
- return self if frozen?
120
-
121
- ancestors
122
- truncated?
123
- @parent_lineage = nil
124
- super
125
- end
109
+ def truncated? = @ancestors_truncated
126
110
 
127
111
  protected
128
112
 
129
113
  def ancestor_references_for_child
130
- ancestors
114
+ @ancestor_references
131
115
  end
132
116
 
133
117
  def root_reference_for_child
@@ -136,37 +120,14 @@ module Julewire
136
120
 
137
121
  private
138
122
 
139
- attr_reader :parent_lineage
140
-
141
- def materialize_ancestor_references_for_child
142
- if @ancestor_references
143
- @ancestor_references_for_child = @ancestor_references.freeze
144
- else
145
- references = build_ancestor_references
146
- @truncated = references.length > MAX_ANCESTORS
147
- @truncated_computed = true
148
- @ancestor_references_for_child = references.last(MAX_ANCESTORS).freeze
149
- end
150
- end
151
-
152
- def ancestor_count_exceeds_limit?
153
- return false if @ancestor_references_for_child
154
-
155
- build_ancestor_references.length > MAX_ANCESTORS
156
- end
157
-
158
123
  def depth_value(depth, parent_lineage)
159
- return depth if depth.is_a?(Integer) && depth.positive?
124
+ return depth if depth.instance_of?(Integer) && depth.positive?
160
125
  return parent_lineage.depth + 1 if parent_lineage
161
126
 
162
127
  1
163
128
  end
164
129
 
165
- def root_reference_for(reference, parent_lineage)
166
- parent_lineage ? parent_lineage.root_reference_for_child : reference
167
- end
168
-
169
- def build_ancestor_references
130
+ def inherited_ancestor_references(parent_lineage)
170
131
  return [] unless parent_lineage && @parent_reference
171
132
 
172
133
  parent_lineage.ancestor_references_for_child + [@parent_reference]
@@ -180,7 +141,6 @@ module Julewire
180
141
 
181
142
  def freeze_reference(reference)
182
143
  return unless reference
183
- return reference if reference.frozen?
184
144
 
185
145
  Serialization::ValueCopy.call(reference, freeze_values: true)
186
146
  end
@@ -1,27 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/atomic/atomic_boolean"
4
+
3
5
  module Julewire
4
6
  module Core
5
7
  module Execution
6
8
  class MeasurementHandle
7
9
  def initialize(&finish)
8
10
  @finish = finish
9
- @finished = false
10
- @mutex = Mutex.new
11
+ @finished = Concurrent::AtomicBoolean.new
11
12
  end
12
13
 
13
14
  def finish
14
- @mutex.synchronize do
15
- return if @finished
15
+ return unless @finished.make_true
16
16
 
17
- @finished = true
18
- @finish.call
19
- end
17
+ @finish.call
20
18
  end
21
19
 
22
- def finished?
23
- @mutex.synchronize { @finished }
24
- end
20
+ def finished? = @finished.true?
25
21
  end
26
22
  end
27
23
  end