julewire-core 1.0.1 → 1.1.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 +4 -4
- data/CHANGELOG.md +18 -0
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +20 -0
- data/docs/contracts.md +23 -49
- data/docs/extensions-and-api.md +78 -82
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +21 -20
- data/docs/outputs-and-lifecycle.md +10 -6
- data/docs/records-and-data-policy.md +36 -5
- data/lib/julewire/core/cli/doctor.rb +20 -16
- data/lib/julewire/core/cli/line_helpers.rb +23 -12
- data/lib/julewire/core/cli/log_formats/core_json_decoder.rb +3 -3
- data/lib/julewire/core/cli/log_formats.rb +9 -9
- data/lib/julewire/core/cli/tail.rb +19 -14
- data/lib/julewire/core/cli/transcode.rb +4 -5
- data/lib/julewire/core/cli.rb +1 -1
- data/lib/julewire/core/configuration.rb +1 -1
- data/lib/julewire/core/context_store.rb +51 -87
- data/lib/julewire/core/destinations/chaos_output.rb +2 -2
- data/lib/julewire/core/destinations/collection.rb +8 -16
- data/lib/julewire/core/destinations/definition.rb +1 -9
- data/lib/julewire/core/destinations/destination.rb +13 -63
- data/lib/julewire/core/destinations/processor_handling.rb +78 -0
- data/lib/julewire/core/destinations/registry.rb +1 -1
- data/lib/julewire/core/destinations/sink.rb +0 -1
- data/lib/julewire/core/destinations/synchronized_output.rb +14 -13
- data/lib/julewire/core/destinations/tail_sampling.rb +18 -27
- data/lib/julewire/core/destinations/write_step.rb +5 -5
- data/lib/julewire/core/destinations.rb +0 -7
- data/lib/julewire/core/diagnostics/callback_notifier.rb +4 -2
- data/lib/julewire/core/diagnostics/doctor.rb +16 -9
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +6 -2
- data/lib/julewire/core/diagnostics/health.rb +41 -57
- data/lib/julewire/core/diagnostics/integration_health_store.rb +11 -24
- data/lib/julewire/core/diagnostics/internal_records.rb +8 -12
- data/lib/julewire/core/diagnostics/invalid_severity_reporter.rb +16 -55
- data/lib/julewire/core/diagnostics/meta_observer.rb +37 -44
- data/lib/julewire/core/diagnostics/process_integration_health.rb +0 -5
- data/lib/julewire/core/diagnostics/tail/renderer.rb +1 -1
- data/lib/julewire/core/diagnostics/tail.rb +18 -22
- data/lib/julewire/core/execution/boundary.rb +2 -8
- data/lib/julewire/core/execution/handle.rb +12 -17
- data/lib/julewire/core/execution/lineage.rb +16 -56
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +15 -20
- data/lib/julewire/core/execution/scope_fields.rb +3 -12
- data/lib/julewire/core/execution/scope_identity.rb +1 -9
- data/lib/julewire/core/execution/scope_snapshot.rb +18 -39
- data/lib/julewire/core/execution/summary_state.rb +16 -19
- data/lib/julewire/core/execution/view.rb +8 -17
- data/lib/julewire/core/facade_methods.rb +67 -65
- data/lib/julewire/core/fields/attribute_keys.rb +0 -2
- data/lib/julewire/core/fields/bags.rb +23 -27
- data/lib/julewire/core/fields/field_set.rb +29 -19
- data/lib/julewire/core/fields/field_stack.rb +68 -134
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +19 -38
- data/lib/julewire/core/fields/lookup.rb +9 -13
- data/lib/julewire/core/fields/section_proxy.rb +1 -9
- data/lib/julewire/core/fields/stack_set.rb +7 -12
- data/lib/julewire/core/fields/static_labels.rb +1 -1
- data/lib/julewire/core/integration/configurable.rb +4 -3
- data/lib/julewire/core/integration/destination_health.rb +17 -7
- data/lib/julewire/core/integration/event_subscriber.rb +1 -1
- data/lib/julewire/core/integration/facade.rb +12 -15
- data/lib/julewire/core/integration/fork_hooks.rb +12 -36
- data/lib/julewire/core/integration/health.rb +0 -3
- data/lib/julewire/core/integration/ivar_state.rb +0 -5
- data/lib/julewire/core/integration/protocol.rb +20 -0
- data/lib/julewire/core/integration/settings.rb +4 -4
- data/lib/julewire/core/integration/subscriber_install.rb +2 -2
- data/lib/julewire/core/integration/subscription.rb +3 -2
- data/lib/julewire/core/integration/values.rb +0 -17
- data/lib/julewire/core/local_storage.rb +3 -19
- data/lib/julewire/core/processing/invalid_result_failure.rb +23 -0
- data/lib/julewire/core/processing/level_threshold.rb +7 -14
- data/lib/julewire/core/processing/match.rb +6 -4
- data/lib/julewire/core/processing/pipeline.rb +46 -40
- data/lib/julewire/core/processing/processor_chain.rb +22 -11
- data/lib/julewire/core/processing/processor_registry.rb +3 -4
- data/lib/julewire/core/processing/record_field_transform.rb +0 -4
- data/lib/julewire/core/processing/sampling.rb +1 -3
- data/lib/julewire/core/processing.rb +2 -2
- data/lib/julewire/core/propagation/carrier.rb +14 -14
- data/lib/julewire/core/propagation.rb +15 -2
- data/lib/julewire/core/records/build_input.rb +75 -0
- data/lib/julewire/core/records/console_formatter.rb +6 -6
- data/lib/julewire/core/records/deconstruct.rb +1 -1
- data/lib/julewire/core/records/display_message.rb +2 -4
- data/lib/julewire/core/records/draft.rb +125 -262
- data/lib/julewire/core/records/lazy_emit_input.rb +2 -12
- data/lib/julewire/core/records/metadata.rb +4 -3
- data/lib/julewire/core/records/public_projection.rb +2 -2
- data/lib/julewire/core/records/raw_input.rb +1 -1
- data/lib/julewire/core/records/record.rb +19 -37
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +67 -71
- data/lib/julewire/core/runtime_registry.rb +20 -15
- data/lib/julewire/core/runtime_state.rb +5 -3
- data/lib/julewire/core/scheduling/shared_scheduler.rb +28 -22
- data/lib/julewire/core/serialization/backtrace_limiter.rb +13 -9
- data/lib/julewire/core/serialization/bounded_transform.rb +2 -12
- data/lib/julewire/core/serialization/bounded_traversal.rb +49 -66
- data/lib/julewire/core/serialization/deep_compact_empty.rb +9 -11
- data/lib/julewire/core/serialization/deep_freeze.rb +45 -6
- data/lib/julewire/core/serialization/encoding_sanitizer.rb +2 -3
- data/lib/julewire/core/serialization/exception_shape.rb +7 -6
- data/lib/julewire/core/serialization/json_encoder.rb +10 -13
- data/lib/julewire/core/serialization/serializer.rb +23 -50
- data/lib/julewire/core/serialization/serializer_pool.rb +9 -0
- data/lib/julewire/core/serialization/text_encoder.rb +5 -5
- data/lib/julewire/core/serialization/truncation_metadata.rb +63 -48
- data/lib/julewire/core/serialization/value_copy.rb +60 -227
- data/lib/julewire/core/serialization/value_traversal.rb +10 -127
- data/lib/julewire/core/testing.rb +6 -56
- data/lib/julewire/core/validation.rb +2 -3
- data/lib/julewire/core/version.rb +1 -1
- data/lib/julewire/core.rb +2 -7
- metadata +6 -19
- data/lib/julewire/core/scheduling/deadline_scheduler.rb +0 -207
- data/lib/julewire/core/testing/chaos/catalog.rb +0 -72
- data/lib/julewire/core/testing/chaos/core_runtime.rb +0 -120
- data/lib/julewire/core/testing/chaos/destination.rb +0 -55
- data/lib/julewire/core/testing/chaos/emitter.rb +0 -20
- data/lib/julewire/core/testing/chaos/raising_output.rb +0 -42
- data/lib/julewire/core/testing/chaos.rb +0 -80
- data/lib/julewire/core/testing/contracts/component.rb +0 -162
- data/lib/julewire/core/testing/contracts/deadline_scheduler.rb +0 -59
- data/lib/julewire/core/testing/contracts/integration.rb +0 -166
- data/lib/julewire/core/testing/contracts/integration_fields.rb +0 -36
- data/lib/julewire/core/testing/contracts/record_draft.rb +0 -37
- data/lib/julewire/core/testing/contracts/runtime.rb +0 -178
- data/lib/julewire/core/testing/contracts/wire.rb +0 -60
- data/lib/julewire/core/testing/contracts.rb +0 -24
- data/lib/julewire/core/testing/coverage.rb +0 -58
- data/lib/julewire/core/testing/test_reports.rb +0 -78
|
@@ -29,7 +29,7 @@ module Julewire
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def each
|
|
32
|
-
return enum_for
|
|
32
|
+
return enum_for unless block_given?
|
|
33
33
|
|
|
34
34
|
@record.each do |key, value|
|
|
35
35
|
next if INTERNAL_KEYS.include?(key)
|
|
@@ -41,7 +41,7 @@ module Julewire
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
43
|
def output_value(key, value)
|
|
44
|
-
return self.class.public_execution(value) if key == :execution
|
|
44
|
+
return self.class.public_execution(value) if key == :execution
|
|
45
45
|
|
|
46
46
|
value
|
|
47
47
|
end
|
|
@@ -26,7 +26,7 @@ module Julewire
|
|
|
26
26
|
def value(input, key, default: nil)
|
|
27
27
|
return default unless hash_input?(input)
|
|
28
28
|
return input[key] if input.key?(key)
|
|
29
|
-
return input
|
|
29
|
+
return input.fetch(key.to_s) if input.key?(key.to_s)
|
|
30
30
|
|
|
31
31
|
default
|
|
32
32
|
end
|
|
@@ -29,11 +29,14 @@ module Julewire
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def from_owned_hash(record, lineage: nil, trust_frozen: false)
|
|
32
|
-
|
|
32
|
+
validate_normalized_hash_structure!(record)
|
|
33
33
|
lineage ||= Execution::Lineage.from_execution_hash(record.fetch(:execution))
|
|
34
34
|
execution = Execution::Lineage.clean_normalized_lazy_relationship_hash(record.fetch(:execution))
|
|
35
35
|
record = record.frozen? ? record.merge(execution: execution) : replace_execution(record, execution)
|
|
36
|
-
new(
|
|
36
|
+
new(
|
|
37
|
+
Serialization::DeepFreeze.call(record, trust_frozen: trust_frozen, validate_symbol_keys: true),
|
|
38
|
+
lineage: lineage
|
|
39
|
+
)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def validate_normalized!(record)
|
|
@@ -54,11 +57,22 @@ module Julewire
|
|
|
54
57
|
record
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
def
|
|
58
|
-
|
|
60
|
+
def validate_normalized_hash_structure!(record)
|
|
61
|
+
validate_record_hash!(record)
|
|
62
|
+
validate_structure!(record)
|
|
63
|
+
end
|
|
59
64
|
|
|
65
|
+
def validate_hash!(record)
|
|
66
|
+
validate_record_hash!(record)
|
|
60
67
|
validate_symbol_keys!(record)
|
|
68
|
+
validate_structure!(record)
|
|
69
|
+
end
|
|
61
70
|
|
|
71
|
+
def validate_record_hash!(record)
|
|
72
|
+
raise TypeError, "record must be a normalized Hash" unless record.is_a?(Hash)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def validate_structure!(record)
|
|
62
76
|
validate_required_keys!(record)
|
|
63
77
|
validate_known_keys!(record)
|
|
64
78
|
|
|
@@ -69,39 +83,7 @@ module Julewire
|
|
|
69
83
|
end
|
|
70
84
|
|
|
71
85
|
def validate_symbol_keys!(record)
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def validate_value_symbol_keys!(root)
|
|
76
|
-
queue = [[root, 0]]
|
|
77
|
-
seen = {}.compare_by_identity
|
|
78
|
-
|
|
79
|
-
queue.each do |value, depth|
|
|
80
|
-
break if depth == NORMALIZATION_MAX_DEPTH
|
|
81
|
-
next unless mark_symbol_key_container(value, seen)
|
|
82
|
-
|
|
83
|
-
enqueue_symbol_key_children(queue, value, depth + 1)
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def mark_symbol_key_container(value, seen)
|
|
88
|
-
return unless value.is_a?(Hash) || value.is_a?(Array)
|
|
89
|
-
return if seen.key?(value)
|
|
90
|
-
|
|
91
|
-
seen[value] = nil
|
|
92
|
-
value
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def enqueue_symbol_key_children(queue, value, depth)
|
|
96
|
-
if value.is_a?(Hash)
|
|
97
|
-
value.each do |key, item|
|
|
98
|
-
raise TypeError, "record must not use string keys" if key.is_a?(String)
|
|
99
|
-
|
|
100
|
-
queue << [item, depth]
|
|
101
|
-
end
|
|
102
|
-
else
|
|
103
|
-
value.each { queue << [it, depth] }
|
|
104
|
-
end
|
|
86
|
+
Serialization::DeepFreeze.validate_symbol_keys(record)
|
|
105
87
|
end
|
|
106
88
|
|
|
107
89
|
def validate_required_keys!(record)
|
|
@@ -12,29 +12,21 @@ module Julewire
|
|
|
12
12
|
|
|
13
13
|
class << self
|
|
14
14
|
def normalize(value)
|
|
15
|
-
return value if RANKS.key?(value)
|
|
16
|
-
|
|
17
|
-
severity = severity_symbol(value)
|
|
18
|
-
return severity if RANKS.key?(severity)
|
|
19
|
-
|
|
20
|
-
raise ArgumentError, "unsupported severity: #{value.inspect}"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def severity_symbol(value)
|
|
24
15
|
case value
|
|
25
16
|
when Symbol
|
|
26
|
-
value.
|
|
17
|
+
return value if RANKS.key?(value)
|
|
27
18
|
when String
|
|
28
|
-
STRING_VALUES[value.downcase]
|
|
19
|
+
severity = STRING_VALUES[value.downcase]
|
|
20
|
+
return severity unless severity.nil?
|
|
29
21
|
when Integer
|
|
30
|
-
LOGGER_INTEGER_VALUES[value]
|
|
22
|
+
severity = LOGGER_INTEGER_VALUES[value]
|
|
23
|
+
return severity unless severity.nil?
|
|
31
24
|
end
|
|
25
|
+
|
|
26
|
+
raise ArgumentError, "unsupported severity: #{value.inspect}"
|
|
32
27
|
end
|
|
33
28
|
|
|
34
29
|
def rank(value)
|
|
35
|
-
rank = RANKS[value]
|
|
36
|
-
return rank unless rank.nil?
|
|
37
|
-
|
|
38
30
|
RANKS.fetch(normalize(value))
|
|
39
31
|
end
|
|
40
32
|
end
|
|
@@ -17,7 +17,7 @@ module Julewire
|
|
|
17
17
|
runtime_callback_failures
|
|
18
18
|
runtime_failures
|
|
19
19
|
].freeze
|
|
20
|
-
CloseTransition = Data.define(:state, :
|
|
20
|
+
CloseTransition = Data.define(:state, :timeout)
|
|
21
21
|
PipelineReplacement = Data.define(
|
|
22
22
|
:old_pipeline,
|
|
23
23
|
:close_timeout,
|
|
@@ -29,15 +29,12 @@ module Julewire
|
|
|
29
29
|
|
|
30
30
|
def initialize
|
|
31
31
|
@configure_mutex = Mutex.new
|
|
32
|
-
@configure_generation = Concurrent::AtomicFixnum.new
|
|
33
|
-
@
|
|
34
|
-
@post_close_emit_count = Concurrent::AtomicFixnum.new(0)
|
|
32
|
+
@configure_generation = Concurrent::AtomicFixnum.new
|
|
33
|
+
@post_close_emit_count = Concurrent::AtomicFixnum.new
|
|
35
34
|
@runtime_health = build_runtime_health
|
|
36
35
|
@integration_health = Diagnostics::IntegrationHealthStore.new
|
|
37
36
|
@invalid_severity_reporter = Diagnostics::InvalidSeverityReporter.counter
|
|
38
|
-
@state_ref = Concurrent::AtomicReference.new(
|
|
39
|
-
RuntimeState.default(invalid_severity_reporter: @invalid_severity_reporter)
|
|
40
|
-
)
|
|
37
|
+
@state_ref = Concurrent::AtomicReference.new(RuntimeState.default)
|
|
41
38
|
@execution_boundary = build_execution_boundary
|
|
42
39
|
end
|
|
43
40
|
|
|
@@ -66,11 +63,11 @@ module Julewire
|
|
|
66
63
|
|
|
67
64
|
def start_execution(...) = @execution_boundary.start_execution(...)
|
|
68
65
|
|
|
69
|
-
def emit(record =
|
|
66
|
+
def emit(record = UNSET, **fields, &)
|
|
70
67
|
emit_with_level_check(record, true, fields, &)
|
|
71
68
|
end
|
|
72
69
|
|
|
73
|
-
def emit_without_level(record =
|
|
70
|
+
def emit_without_level(record = UNSET, **fields, &)
|
|
74
71
|
emit_with_level_check(record, false, fields, &)
|
|
75
72
|
end
|
|
76
73
|
|
|
@@ -80,8 +77,7 @@ module Julewire
|
|
|
80
77
|
end
|
|
81
78
|
end
|
|
82
79
|
|
|
83
|
-
def emit_envelope(input:, context:, scope:, carry
|
|
84
|
-
owned: false)
|
|
80
|
+
def emit_envelope(input:, context:, scope:, carry:, attributes:, neutral:, enforce_level: true, owned: false)
|
|
85
81
|
reject_runtime_call_during_configure!(:emit_envelope)
|
|
86
82
|
state = runtime_state
|
|
87
83
|
return record_post_close_emit(state) if state.pipeline_closed
|
|
@@ -102,6 +98,7 @@ module Julewire
|
|
|
102
98
|
|
|
103
99
|
begin
|
|
104
100
|
input = scope.owned_summary_record_input
|
|
101
|
+
Serialization::DeepFreeze.validate_symbol_keys(input)
|
|
105
102
|
state.pipeline.emit_isolated_input(input)
|
|
106
103
|
rescue StandardError => e
|
|
107
104
|
notify_failure(e, state, action: :emit_summary_record)
|
|
@@ -127,20 +124,18 @@ module Julewire
|
|
|
127
124
|
config
|
|
128
125
|
end
|
|
129
126
|
|
|
130
|
-
def flush(timeout:
|
|
127
|
+
def flush(timeout: UNSET)
|
|
131
128
|
call_validated_lifecycle(:flush, timeout)
|
|
132
129
|
end
|
|
133
130
|
|
|
134
|
-
def close(timeout:
|
|
131
|
+
def close(timeout: UNSET)
|
|
135
132
|
close_state_resources(close_state(timeout))
|
|
136
133
|
end
|
|
137
134
|
|
|
138
135
|
def reset!
|
|
139
136
|
increment_runtime_count(:reset_attempts)
|
|
140
137
|
reset_result = reject_runtime_call_during_configure!(:reset!) do
|
|
141
|
-
@configure_mutex.synchronize
|
|
142
|
-
@state_mutex.synchronize { reset_under_lock }
|
|
143
|
-
end
|
|
138
|
+
@configure_mutex.synchronize { reset_under_lock }
|
|
144
139
|
end
|
|
145
140
|
deadline = Scheduling::Deadline.for(reset_result.close_timeout)
|
|
146
141
|
return unless reset_result.close_pipeline
|
|
@@ -153,6 +148,8 @@ module Julewire
|
|
|
153
148
|
)
|
|
154
149
|
end
|
|
155
150
|
|
|
151
|
+
def reset_facade! = RuntimeRegistry.reset(primary: self)
|
|
152
|
+
|
|
156
153
|
def after_fork!
|
|
157
154
|
reject_runtime_call_during_configure!(:after_fork!)
|
|
158
155
|
RuntimeRegistry.reset_after_fork(primary: self)
|
|
@@ -161,7 +158,6 @@ module Julewire
|
|
|
161
158
|
def reset_after_fork_runtime!
|
|
162
159
|
reset_after_fork_state!
|
|
163
160
|
runtime_state.pipeline.after_fork!
|
|
164
|
-
self
|
|
165
161
|
end
|
|
166
162
|
|
|
167
163
|
def record_integration_failure(integration, error, **metadata)
|
|
@@ -196,10 +192,10 @@ module Julewire
|
|
|
196
192
|
if owned
|
|
197
193
|
Records::Draft.build_pipeline_owned(
|
|
198
194
|
input,
|
|
199
|
-
context: envelope_hash(context),
|
|
200
|
-
attributes: envelope_hash(attributes),
|
|
201
|
-
neutral: envelope_hash(neutral),
|
|
202
|
-
carry: envelope_hash(carry),
|
|
195
|
+
context: envelope_hash(context, owned: true),
|
|
196
|
+
attributes: envelope_hash(attributes, owned: true),
|
|
197
|
+
neutral: envelope_hash(neutral, owned: true),
|
|
198
|
+
carry: envelope_hash(carry, owned: true),
|
|
203
199
|
scope: scope,
|
|
204
200
|
error_backtrace_lines: state.configuration.error_backtrace_lines,
|
|
205
201
|
input_owned: true,
|
|
@@ -208,10 +204,10 @@ module Julewire
|
|
|
208
204
|
else
|
|
209
205
|
Records::Draft.build(
|
|
210
206
|
input,
|
|
211
|
-
context: envelope_hash(context),
|
|
212
|
-
attributes: envelope_hash(attributes),
|
|
213
|
-
neutral: envelope_hash(neutral),
|
|
214
|
-
carry: envelope_hash(carry),
|
|
207
|
+
context: envelope_hash(context, owned: false),
|
|
208
|
+
attributes: envelope_hash(attributes, owned: false),
|
|
209
|
+
neutral: envelope_hash(neutral, owned: false),
|
|
210
|
+
carry: envelope_hash(carry, owned: false),
|
|
215
211
|
scope: scope,
|
|
216
212
|
error_backtrace_lines: state.configuration.error_backtrace_lines,
|
|
217
213
|
invalid_severity_reporter: @invalid_severity_reporter
|
|
@@ -244,11 +240,7 @@ module Julewire
|
|
|
244
240
|
end
|
|
245
241
|
|
|
246
242
|
def install_and_replace_pipeline(state, next_configuration, next_pipeline)
|
|
247
|
-
replaced_pipeline =
|
|
248
|
-
raise Error, "Julewire.configure state changed before install completed" unless runtime_state.equal?(state)
|
|
249
|
-
|
|
250
|
-
replace_pipeline(next_configuration, next_pipeline)
|
|
251
|
-
end
|
|
243
|
+
replaced_pipeline = replace_pipeline(next_configuration, next_pipeline, state: state)
|
|
252
244
|
PipelineReplacement.new(
|
|
253
245
|
replaced_pipeline,
|
|
254
246
|
state.configuration.pipeline_close_timeout,
|
|
@@ -270,11 +262,14 @@ module Julewire
|
|
|
270
262
|
Fiber[CONFIGURE_GUARD_KEY] = previous
|
|
271
263
|
end
|
|
272
264
|
|
|
273
|
-
def replace_pipeline(configuration, pipeline)
|
|
274
|
-
|
|
265
|
+
def replace_pipeline(configuration, pipeline, state:)
|
|
266
|
+
next_state = state.next_generation(configuration: configuration, pipeline: pipeline)
|
|
267
|
+
unless @state_ref.compare_and_set(state, next_state)
|
|
268
|
+
raise Error, "Julewire.configure state changed before install completed"
|
|
269
|
+
end
|
|
270
|
+
|
|
275
271
|
@post_close_emit_count.value = 0
|
|
276
|
-
@runtime_health.
|
|
277
|
-
@state_ref.set(state.next_generation(configuration: configuration, pipeline: pipeline))
|
|
272
|
+
@runtime_health.record_success
|
|
278
273
|
state.pipeline
|
|
279
274
|
end
|
|
280
275
|
|
|
@@ -293,7 +288,7 @@ module Julewire
|
|
|
293
288
|
end
|
|
294
289
|
|
|
295
290
|
def normalize_lifecycle_timeout(timeout, state)
|
|
296
|
-
timeout.equal?(
|
|
291
|
+
timeout.equal?(UNSET) ? state.configuration.pipeline_close_timeout : timeout
|
|
297
292
|
end
|
|
298
293
|
|
|
299
294
|
def validate_lifecycle_timeout!(timeout, name:)
|
|
@@ -301,7 +296,7 @@ module Julewire
|
|
|
301
296
|
end
|
|
302
297
|
|
|
303
298
|
def close_state_resources(transition)
|
|
304
|
-
return true unless transition
|
|
299
|
+
return true unless transition
|
|
305
300
|
|
|
306
301
|
deadline = Scheduling::Deadline.for(transition.timeout)
|
|
307
302
|
call_pipeline_lifecycle_on(
|
|
@@ -314,17 +309,22 @@ module Julewire
|
|
|
314
309
|
|
|
315
310
|
def close_state(timeout)
|
|
316
311
|
reject_runtime_call_during_configure!(:close)
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
312
|
+
state = runtime_state
|
|
313
|
+
resolved_timeout = normalize_lifecycle_timeout(timeout, state)
|
|
314
|
+
validate_lifecycle_timeout!(resolved_timeout, name: :timeout)
|
|
315
|
+
increment_runtime_count(:close_attempts)
|
|
316
|
+
transition = nil
|
|
317
|
+
|
|
318
|
+
@state_ref.update do |current_state|
|
|
319
|
+
transition = unless current_state.pipeline_closed
|
|
320
|
+
CloseTransition.new(
|
|
321
|
+
current_state,
|
|
322
|
+
normalize_lifecycle_timeout(timeout, current_state)
|
|
323
|
+
)
|
|
324
|
+
end
|
|
325
|
+
current_state.closed
|
|
327
326
|
end
|
|
327
|
+
transition
|
|
328
328
|
end
|
|
329
329
|
|
|
330
330
|
def call_pipeline_lifecycle_on(pipeline, method_name, timeout:, state:)
|
|
@@ -360,8 +360,8 @@ module Julewire
|
|
|
360
360
|
end
|
|
361
361
|
end
|
|
362
362
|
|
|
363
|
-
def envelope_hash(value)
|
|
364
|
-
value.is_a?(Hash)
|
|
363
|
+
def envelope_hash(value, owned:)
|
|
364
|
+
value if owned || value.is_a?(Hash)
|
|
365
365
|
end
|
|
366
366
|
|
|
367
367
|
def record_post_close_emit(state)
|
|
@@ -389,48 +389,48 @@ module Julewire
|
|
|
389
389
|
end
|
|
390
390
|
|
|
391
391
|
def pipeline_degraded?(pipeline_health)
|
|
392
|
-
return true if pipeline_health
|
|
392
|
+
return true if degraded_health?(pipeline_health)
|
|
393
393
|
|
|
394
394
|
pipeline_health.fetch(:destinations).values.any? do |destination_health|
|
|
395
|
-
destination_health
|
|
395
|
+
degraded_health?(destination_health)
|
|
396
396
|
end
|
|
397
397
|
end
|
|
398
398
|
|
|
399
399
|
def integrations_degraded?(integrations)
|
|
400
400
|
integrations.values.any? do |integration_health|
|
|
401
|
-
integration_health
|
|
401
|
+
degraded_health?(integration_health)
|
|
402
402
|
end
|
|
403
403
|
end
|
|
404
404
|
|
|
405
|
+
def degraded_health?(health)
|
|
406
|
+
status = health[:status]
|
|
407
|
+
status && !status.equal?(:ok)
|
|
408
|
+
end
|
|
409
|
+
|
|
405
410
|
def clear_runtime_degradation_if_unchanged(marker)
|
|
406
411
|
@runtime_health.clear_degradation_if_unchanged(marker)
|
|
407
412
|
end
|
|
408
413
|
|
|
409
414
|
def summary_finalizer_failure
|
|
410
|
-
@summary_finalizer_failure ||= ->(error) { handle_summary_finalizer_failure(error) }
|
|
415
|
+
@summary_finalizer_failure ||= ->(error, **metadata) { handle_summary_finalizer_failure(error, **metadata) }
|
|
411
416
|
end
|
|
412
417
|
|
|
413
418
|
def reset_after_fork_state!
|
|
414
|
-
|
|
415
|
-
@configure_mutex = Mutex.new
|
|
416
|
-
@configure_generation = Concurrent::AtomicFixnum.new(0)
|
|
417
|
-
@state_mutex = Mutex.new
|
|
418
|
-
@post_close_emit_count = Concurrent::AtomicFixnum.new(0)
|
|
419
|
+
@post_close_emit_count.value = 0
|
|
419
420
|
@runtime_health = build_runtime_health
|
|
420
|
-
@integration_health.
|
|
421
|
-
@invalid_severity_reporter.
|
|
422
|
-
@state_ref = Concurrent::AtomicReference.new(state)
|
|
423
|
-
nil
|
|
421
|
+
@integration_health.reset!
|
|
422
|
+
@invalid_severity_reporter.reset!
|
|
424
423
|
end
|
|
425
424
|
|
|
426
425
|
def reset_under_lock
|
|
427
426
|
state = runtime_state
|
|
428
427
|
configuration = Configuration.new.snapshot
|
|
429
428
|
@invalid_severity_reporter.reset!
|
|
430
|
-
next_pipeline = configuration.build_pipeline
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
next_pipeline = configuration.build_pipeline
|
|
430
|
+
next_state = state.next_generation(configuration: configuration, pipeline: next_pipeline)
|
|
431
|
+
state = @state_ref.get_and_set(next_state)
|
|
433
432
|
@post_close_emit_count.value = 0
|
|
433
|
+
ContextStore.reset_current!
|
|
434
434
|
@runtime_health.clear_failures!
|
|
435
435
|
@integration_health.reset!
|
|
436
436
|
Diagnostics::ProcessIntegrationHealth.reset!
|
|
@@ -448,12 +448,12 @@ module Julewire
|
|
|
448
448
|
raise Error, "Julewire.#{method_name} cannot be called from inside Julewire.configure"
|
|
449
449
|
end
|
|
450
450
|
|
|
451
|
-
|
|
451
|
+
yield if block_given?
|
|
452
452
|
end
|
|
453
453
|
|
|
454
454
|
def configure_guard_active?
|
|
455
455
|
token = Fiber[CONFIGURE_GUARD_KEY]
|
|
456
|
-
token.
|
|
456
|
+
token.instance_of?(Array) && token.fetch(0) == object_id && token.fetch(1) == @configure_generation.value
|
|
457
457
|
end
|
|
458
458
|
|
|
459
459
|
def increment_lifecycle_attempt(method_name)
|
|
@@ -476,9 +476,8 @@ module Julewire
|
|
|
476
476
|
@runtime_health.record_failure(error, callback: state.configuration.on_failure, **metadata)
|
|
477
477
|
end
|
|
478
478
|
|
|
479
|
-
def handle_summary_finalizer_failure(error)
|
|
479
|
+
def handle_summary_finalizer_failure(error, **metadata)
|
|
480
480
|
state = runtime_state
|
|
481
|
-
metadata = { phase: :summary_finalizer }
|
|
482
481
|
@runtime_health.record_failure(error, callback: state.configuration.on_failure, **metadata)
|
|
483
482
|
end
|
|
484
483
|
|
|
@@ -503,8 +502,6 @@ module Julewire
|
|
|
503
502
|
end
|
|
504
503
|
|
|
505
504
|
def notify_lifecycle_warning(warning)
|
|
506
|
-
return unless warning
|
|
507
|
-
|
|
508
505
|
result = Diagnostics::CallbackNotifier.call(warning.fetch(:on_failure), warning.fetch(:error),
|
|
509
506
|
warning.fetch(:metadata))
|
|
510
507
|
@runtime_health.record_callback_failure(result) if Diagnostics::CallbackNotifier.failure?(result)
|
|
@@ -513,7 +510,6 @@ module Julewire
|
|
|
513
510
|
def build_runtime_health
|
|
514
511
|
Diagnostics::Health.new(
|
|
515
512
|
counter_keys: RUNTIME_COUNTER_KEYS,
|
|
516
|
-
callback_metadata: {},
|
|
517
513
|
callback_failure_counter: :runtime_callback_failures,
|
|
518
514
|
failure_counter: :runtime_failures
|
|
519
515
|
)
|
|
@@ -1,13 +1,15 @@
|
|
|
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 RuntimeRegistry
|
|
6
8
|
DEFAULT_NAME = :default
|
|
7
|
-
|
|
9
|
+
EMPTY_RUNTIMES = {}.freeze
|
|
10
|
+
private_constant :DEFAULT_NAME, :EMPTY_RUNTIMES
|
|
8
11
|
|
|
9
|
-
@
|
|
10
|
-
@runtimes = {}
|
|
12
|
+
@runtimes = Concurrent::AtomicReference.new(EMPTY_RUNTIMES)
|
|
11
13
|
|
|
12
14
|
class << self
|
|
13
15
|
def fetch(name, current: RuntimeLocator.current)
|
|
@@ -18,29 +20,32 @@ module Julewire
|
|
|
18
20
|
raise Error, "named Julewire runtimes are not available from the current runtime"
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
@
|
|
23
|
+
@runtimes.update do |runtimes|
|
|
24
|
+
if runtimes.key?(name)
|
|
25
|
+
runtimes
|
|
26
|
+
else
|
|
27
|
+
runtimes.merge(name => Runtime.new)
|
|
28
|
+
end
|
|
29
|
+
end.fetch(name)
|
|
22
30
|
end
|
|
23
31
|
|
|
24
|
-
def
|
|
25
|
-
|
|
32
|
+
def reset(primary:)
|
|
33
|
+
runtimes = @runtimes.get_and_set(EMPTY_RUNTIMES).values
|
|
34
|
+
|
|
35
|
+
primary.reset!
|
|
36
|
+
runtimes.each(&:reset!)
|
|
26
37
|
nil
|
|
27
38
|
end
|
|
28
39
|
|
|
29
40
|
def reset_after_fork(primary:)
|
|
30
|
-
|
|
31
|
-
# before rebuilding them.
|
|
32
|
-
runtimes = ([primary] + @runtimes.values).uniq
|
|
33
|
-
@mutex = Mutex.new
|
|
41
|
+
runtimes = [primary] + @runtimes.get.values
|
|
34
42
|
|
|
35
|
-
LocalStorage.after_fork!
|
|
36
43
|
ContextStore.reset_current!
|
|
37
44
|
Scheduling::SharedScheduler.after_fork!
|
|
38
|
-
Diagnostics::ProcessIntegrationHealth.
|
|
39
|
-
|
|
40
|
-
Diagnostics::InvalidSeverityReporter.reset_after_fork!
|
|
45
|
+
Diagnostics::ProcessIntegrationHealth.reset!
|
|
46
|
+
Diagnostics::InvalidSeverityReporter.reset!
|
|
41
47
|
runtimes.each(&:reset_after_fork_runtime!)
|
|
42
48
|
Integration::ForkHooks.run
|
|
43
|
-
nil
|
|
44
49
|
end
|
|
45
50
|
end
|
|
46
51
|
end
|
|
@@ -7,11 +7,13 @@ module Julewire
|
|
|
7
7
|
:pipeline,
|
|
8
8
|
:pipeline_closed,
|
|
9
9
|
:pipeline_generation
|
|
10
|
-
)
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
class RuntimeState
|
|
11
13
|
class << self
|
|
12
|
-
def default
|
|
14
|
+
def default
|
|
13
15
|
configuration = Configuration.new.snapshot
|
|
14
|
-
pipeline = configuration.build_pipeline
|
|
16
|
+
pipeline = configuration.build_pipeline
|
|
15
17
|
|
|
16
18
|
new(
|
|
17
19
|
configuration: configuration,
|
|
@@ -1,47 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "concurrent/executor/thread_pool_executor"
|
|
4
|
+
require "concurrent/executor/timer_set"
|
|
5
|
+
|
|
3
6
|
module Julewire
|
|
4
7
|
module Core
|
|
5
8
|
module Scheduling
|
|
6
9
|
module SharedScheduler
|
|
7
10
|
THREAD_NAME = "julewire-deadline-scheduler"
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
EXECUTOR_OPTIONS = { max_threads: 1, idletime: 0 }.freeze
|
|
12
|
+
private_constant :EXECUTOR_OPTIONS
|
|
10
13
|
|
|
11
14
|
class << self
|
|
12
|
-
def schedule(timeout, &)
|
|
13
|
-
|
|
14
|
-
current.schedule(timeout, &)
|
|
15
|
-
end
|
|
15
|
+
def schedule(timeout, &block)
|
|
16
|
+
raise ArgumentError, "block required" unless block
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
timeout = Float(timeout)
|
|
19
|
+
if timeout <= 0
|
|
20
|
+
yield
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@scheduler.post(timeout) do
|
|
25
|
+
Thread.current.name = THREAD_NAME
|
|
26
|
+
yield
|
|
27
|
+
end
|
|
20
28
|
end
|
|
21
29
|
|
|
22
|
-
def
|
|
23
|
-
|
|
24
|
-
@mutex = Mutex.new
|
|
25
|
-
current&.after_fork!
|
|
30
|
+
def cancel(task)
|
|
31
|
+
task&.cancel
|
|
26
32
|
nil
|
|
27
33
|
end
|
|
28
34
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@
|
|
35
|
+
def after_fork!
|
|
36
|
+
previous = @scheduler
|
|
37
|
+
@scheduler = build_scheduler
|
|
38
|
+
previous.kill
|
|
32
39
|
nil
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
private
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@mutex.synchronize do
|
|
41
|
-
@scheduler ||= DeadlineScheduler.new(thread_name: THREAD_NAME)
|
|
42
|
-
end
|
|
44
|
+
def build_scheduler
|
|
45
|
+
executor = Concurrent::ThreadPoolExecutor.new(**EXECUTOR_OPTIONS)
|
|
46
|
+
Concurrent::TimerSet.new(executor: executor)
|
|
43
47
|
end
|
|
44
48
|
end
|
|
49
|
+
|
|
50
|
+
@scheduler = build_scheduler
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
end
|
|
@@ -4,6 +4,9 @@ module Julewire
|
|
|
4
4
|
module Core
|
|
5
5
|
module Serialization
|
|
6
6
|
class BacktraceLimiter
|
|
7
|
+
MAX_CAUSE_DEPTH = Core::NORMALIZATION_MAX_DEPTH
|
|
8
|
+
private_constant :MAX_CAUSE_DEPTH
|
|
9
|
+
|
|
7
10
|
class << self
|
|
8
11
|
def call(value, max_backtrace_lines:)
|
|
9
12
|
new(max_backtrace_lines: max_backtrace_lines).call(value)
|
|
@@ -18,18 +21,18 @@ module Julewire
|
|
|
18
21
|
end
|
|
19
22
|
|
|
20
23
|
def call(value)
|
|
21
|
-
|
|
22
|
-
limit_backtraces(value)
|
|
24
|
+
limit_backtraces(value, Set.new.compare_by_identity)
|
|
23
25
|
value
|
|
24
|
-
ensure
|
|
25
|
-
@seen = nil
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
private
|
|
29
29
|
|
|
30
|
-
def limit_backtraces(value)
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
def limit_backtraces(value, seen)
|
|
31
|
+
MAX_CAUSE_DEPTH.times do
|
|
32
|
+
break unless value.is_a?(Hash)
|
|
33
|
+
break if seen.include?(value)
|
|
34
|
+
|
|
35
|
+
seen.add(value)
|
|
33
36
|
limit_backtrace_field!(value)
|
|
34
37
|
value = value[:cause]
|
|
35
38
|
end
|
|
@@ -40,8 +43,9 @@ module Julewire
|
|
|
40
43
|
|
|
41
44
|
if @max_backtrace_lines.zero?
|
|
42
45
|
error.delete(:backtrace)
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
else
|
|
47
|
+
backtrace = error.fetch(:backtrace)
|
|
48
|
+
error[:backtrace] = backtrace.first(@max_backtrace_lines) if backtrace.is_a?(Array)
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
end
|