julewire-core 1.0.0 → 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 +29 -0
- data/docs/advanced-configuration.md +2 -1
- data/docs/configuration.md +14 -7
- data/docs/context-and-propagation.md +26 -4
- data/docs/contracts.md +25 -50
- data/docs/extensions-and-api.md +87 -88
- data/docs/health-schema.md +12 -2
- data/docs/internals.md +24 -23
- data/docs/outputs-and-lifecycle.md +15 -8
- data/docs/records-and-data-policy.md +38 -7
- 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/record_decoder.rb +1 -1
- 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 +56 -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 +16 -66
- 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 +54 -17
- 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 +22 -15
- data/lib/julewire/core/diagnostics/failure_snapshot.rb +9 -3
- 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 +14 -17
- data/lib/julewire/core/execution/lineage.rb +26 -62
- data/lib/julewire/core/execution/measurement_handle.rb +6 -10
- data/lib/julewire/core/execution/scope.rb +17 -22
- data/lib/julewire/core/execution/scope_fields.rb +4 -13
- 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 +52 -16
- data/lib/julewire/core/fields/field_stack.rb +69 -109
- data/lib/julewire/core/fields/internal/deletion.rb +6 -9
- data/lib/julewire/core/fields/internal.rb +35 -26
- 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 +9 -14
- 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 +28 -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 +73 -19
- data/lib/julewire/core/propagation.rb +18 -4
- 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 +127 -258
- 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 +30 -18
- data/lib/julewire/core/records/severity.rb +7 -15
- data/lib/julewire/core/runtime.rb +87 -72
- 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 +3 -2
- data/lib/julewire/core/serialization/bounded_traversal.rb +72 -83
- 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 +22 -49
- 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 +157 -0
- data/lib/julewire/core/serialization/value_copy.rb +183 -109
- 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 +7 -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
|
|
@@ -19,22 +19,24 @@ module Julewire
|
|
|
19
19
|
|
|
20
20
|
class << self
|
|
21
21
|
def from_normalized_hash(record, lineage: nil)
|
|
22
|
-
if record.is_a?(Hash)
|
|
23
|
-
lineage ||= Execution::Lineage.from_execution_hash(record[:execution])
|
|
24
|
-
record = record.merge(execution: Execution::Lineage.clean_lazy_relationship_hash(record[:execution]))
|
|
25
|
-
end
|
|
26
22
|
validate_normalized_hash!(record)
|
|
23
|
+
execution = record.fetch(:execution)
|
|
24
|
+
lineage ||= Execution::Lineage.from_execution_hash(execution)
|
|
25
|
+
record = record.merge(
|
|
26
|
+
execution: Execution::Lineage.clean_normalized_lazy_relationship_hash(execution)
|
|
27
|
+
)
|
|
27
28
|
new(snapshot_hash(record), lineage: lineage)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
def from_owned_hash(record, lineage: nil, trust_frozen: false)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
validate_normalized_hash_structure!(record)
|
|
33
|
+
lineage ||= Execution::Lineage.from_execution_hash(record.fetch(:execution))
|
|
34
|
+
execution = Execution::Lineage.clean_normalized_lazy_relationship_hash(record.fetch(:execution))
|
|
35
|
+
record = record.frozen? ? record.merge(execution: execution) : replace_execution(record, execution)
|
|
36
|
+
new(
|
|
37
|
+
Serialization::DeepFreeze.call(record, trust_frozen: trust_frozen, validate_symbol_keys: true),
|
|
38
|
+
lineage: lineage
|
|
39
|
+
)
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def validate_normalized!(record)
|
|
@@ -55,11 +57,22 @@ module Julewire
|
|
|
55
57
|
record
|
|
56
58
|
end
|
|
57
59
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
+
def validate_normalized_hash_structure!(record)
|
|
61
|
+
validate_record_hash!(record)
|
|
62
|
+
validate_structure!(record)
|
|
63
|
+
end
|
|
60
64
|
|
|
65
|
+
def validate_hash!(record)
|
|
66
|
+
validate_record_hash!(record)
|
|
61
67
|
validate_symbol_keys!(record)
|
|
68
|
+
validate_structure!(record)
|
|
69
|
+
end
|
|
62
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)
|
|
63
76
|
validate_required_keys!(record)
|
|
64
77
|
validate_known_keys!(record)
|
|
65
78
|
|
|
@@ -70,9 +83,7 @@ module Julewire
|
|
|
70
83
|
end
|
|
71
84
|
|
|
72
85
|
def validate_symbol_keys!(record)
|
|
73
|
-
record
|
|
74
|
-
raise TypeError, "record must not use string keys" if key.is_a?(String)
|
|
75
|
-
end
|
|
86
|
+
Serialization::DeepFreeze.validate_symbol_keys(record)
|
|
76
87
|
end
|
|
77
88
|
|
|
78
89
|
def validate_required_keys!(record)
|
|
@@ -126,7 +137,8 @@ module Julewire
|
|
|
126
137
|
def snapshot_hash(record)
|
|
127
138
|
Serialization::ValueCopy.call(
|
|
128
139
|
record,
|
|
129
|
-
freeze_values: true
|
|
140
|
+
freeze_values: true,
|
|
141
|
+
preserve_truncation_metadata: true
|
|
130
142
|
)
|
|
131
143
|
end
|
|
132
144
|
end
|
|
@@ -149,7 +161,7 @@ module Julewire
|
|
|
149
161
|
|
|
150
162
|
def each(&) = @data.each(&)
|
|
151
163
|
|
|
152
|
-
def to_h = Fields::FieldSet.
|
|
164
|
+
def to_h = Fields::FieldSet.deep_dup_owned(@data)
|
|
153
165
|
|
|
154
166
|
# @api internal
|
|
155
167
|
def serializable_data = @data
|
|
@@ -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,22 +77,13 @@ module Julewire
|
|
|
80
77
|
end
|
|
81
78
|
end
|
|
82
79
|
|
|
83
|
-
def emit_envelope(input:, context:, scope:, carry
|
|
80
|
+
def emit_envelope(input:, context:, scope:, carry:, attributes:, neutral:, enforce_level: true, owned: false)
|
|
84
81
|
reject_runtime_call_during_configure!(:emit_envelope)
|
|
85
82
|
state = runtime_state
|
|
86
83
|
return record_post_close_emit(state) if state.pipeline_closed
|
|
87
84
|
|
|
88
85
|
begin
|
|
89
|
-
record =
|
|
90
|
-
input,
|
|
91
|
-
context: envelope_hash(context),
|
|
92
|
-
attributes: envelope_hash(attributes),
|
|
93
|
-
neutral: envelope_hash(neutral),
|
|
94
|
-
carry: envelope_hash(carry),
|
|
95
|
-
scope: scope,
|
|
96
|
-
error_backtrace_lines: state.configuration.error_backtrace_lines,
|
|
97
|
-
invalid_severity_reporter: @invalid_severity_reporter
|
|
98
|
-
).to_record
|
|
86
|
+
record = envelope_draft(input, context, attributes, neutral, carry, scope, state, owned: owned).to_record
|
|
99
87
|
state.pipeline.emit_record(record, enforce_level: enforce_level)
|
|
100
88
|
rescue StandardError => e
|
|
101
89
|
notify_failure(e, state, action: :emit_envelope)
|
|
@@ -110,6 +98,7 @@ module Julewire
|
|
|
110
98
|
|
|
111
99
|
begin
|
|
112
100
|
input = scope.owned_summary_record_input
|
|
101
|
+
Serialization::DeepFreeze.validate_symbol_keys(input)
|
|
113
102
|
state.pipeline.emit_isolated_input(input)
|
|
114
103
|
rescue StandardError => e
|
|
115
104
|
notify_failure(e, state, action: :emit_summary_record)
|
|
@@ -135,20 +124,18 @@ module Julewire
|
|
|
135
124
|
config
|
|
136
125
|
end
|
|
137
126
|
|
|
138
|
-
def flush(timeout:
|
|
127
|
+
def flush(timeout: UNSET)
|
|
139
128
|
call_validated_lifecycle(:flush, timeout)
|
|
140
129
|
end
|
|
141
130
|
|
|
142
|
-
def close(timeout:
|
|
131
|
+
def close(timeout: UNSET)
|
|
143
132
|
close_state_resources(close_state(timeout))
|
|
144
133
|
end
|
|
145
134
|
|
|
146
135
|
def reset!
|
|
147
136
|
increment_runtime_count(:reset_attempts)
|
|
148
137
|
reset_result = reject_runtime_call_during_configure!(:reset!) do
|
|
149
|
-
@configure_mutex.synchronize
|
|
150
|
-
@state_mutex.synchronize { reset_under_lock }
|
|
151
|
-
end
|
|
138
|
+
@configure_mutex.synchronize { reset_under_lock }
|
|
152
139
|
end
|
|
153
140
|
deadline = Scheduling::Deadline.for(reset_result.close_timeout)
|
|
154
141
|
return unless reset_result.close_pipeline
|
|
@@ -161,6 +148,8 @@ module Julewire
|
|
|
161
148
|
)
|
|
162
149
|
end
|
|
163
150
|
|
|
151
|
+
def reset_facade! = RuntimeRegistry.reset(primary: self)
|
|
152
|
+
|
|
164
153
|
def after_fork!
|
|
165
154
|
reject_runtime_call_during_configure!(:after_fork!)
|
|
166
155
|
RuntimeRegistry.reset_after_fork(primary: self)
|
|
@@ -169,7 +158,6 @@ module Julewire
|
|
|
169
158
|
def reset_after_fork_runtime!
|
|
170
159
|
reset_after_fork_state!
|
|
171
160
|
runtime_state.pipeline.after_fork!
|
|
172
|
-
self
|
|
173
161
|
end
|
|
174
162
|
|
|
175
163
|
def record_integration_failure(integration, error, **metadata)
|
|
@@ -200,6 +188,33 @@ module Julewire
|
|
|
200
188
|
|
|
201
189
|
private
|
|
202
190
|
|
|
191
|
+
def envelope_draft(input, context, attributes, neutral, carry, scope, state, owned:)
|
|
192
|
+
if owned
|
|
193
|
+
Records::Draft.build_pipeline_owned(
|
|
194
|
+
input,
|
|
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),
|
|
199
|
+
scope: scope,
|
|
200
|
+
error_backtrace_lines: state.configuration.error_backtrace_lines,
|
|
201
|
+
input_owned: true,
|
|
202
|
+
invalid_severity_reporter: @invalid_severity_reporter
|
|
203
|
+
)
|
|
204
|
+
else
|
|
205
|
+
Records::Draft.build(
|
|
206
|
+
input,
|
|
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),
|
|
211
|
+
scope: scope,
|
|
212
|
+
error_backtrace_lines: state.configuration.error_backtrace_lines,
|
|
213
|
+
invalid_severity_reporter: @invalid_severity_reporter
|
|
214
|
+
)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
203
218
|
def before_execution_boundary_call!(action)
|
|
204
219
|
reject_runtime_call_during_configure!(action)
|
|
205
220
|
end
|
|
@@ -225,11 +240,7 @@ module Julewire
|
|
|
225
240
|
end
|
|
226
241
|
|
|
227
242
|
def install_and_replace_pipeline(state, next_configuration, next_pipeline)
|
|
228
|
-
replaced_pipeline =
|
|
229
|
-
raise Error, "Julewire.configure state changed before install completed" unless runtime_state.equal?(state)
|
|
230
|
-
|
|
231
|
-
replace_pipeline(next_configuration, next_pipeline)
|
|
232
|
-
end
|
|
243
|
+
replaced_pipeline = replace_pipeline(next_configuration, next_pipeline, state: state)
|
|
233
244
|
PipelineReplacement.new(
|
|
234
245
|
replaced_pipeline,
|
|
235
246
|
state.configuration.pipeline_close_timeout,
|
|
@@ -251,11 +262,14 @@ module Julewire
|
|
|
251
262
|
Fiber[CONFIGURE_GUARD_KEY] = previous
|
|
252
263
|
end
|
|
253
264
|
|
|
254
|
-
def replace_pipeline(configuration, pipeline)
|
|
255
|
-
|
|
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
|
+
|
|
256
271
|
@post_close_emit_count.value = 0
|
|
257
|
-
@runtime_health.
|
|
258
|
-
@state_ref.set(state.next_generation(configuration: configuration, pipeline: pipeline))
|
|
272
|
+
@runtime_health.record_success
|
|
259
273
|
state.pipeline
|
|
260
274
|
end
|
|
261
275
|
|
|
@@ -274,7 +288,7 @@ module Julewire
|
|
|
274
288
|
end
|
|
275
289
|
|
|
276
290
|
def normalize_lifecycle_timeout(timeout, state)
|
|
277
|
-
timeout.equal?(
|
|
291
|
+
timeout.equal?(UNSET) ? state.configuration.pipeline_close_timeout : timeout
|
|
278
292
|
end
|
|
279
293
|
|
|
280
294
|
def validate_lifecycle_timeout!(timeout, name:)
|
|
@@ -282,7 +296,7 @@ module Julewire
|
|
|
282
296
|
end
|
|
283
297
|
|
|
284
298
|
def close_state_resources(transition)
|
|
285
|
-
return true unless transition
|
|
299
|
+
return true unless transition
|
|
286
300
|
|
|
287
301
|
deadline = Scheduling::Deadline.for(transition.timeout)
|
|
288
302
|
call_pipeline_lifecycle_on(
|
|
@@ -295,17 +309,22 @@ module Julewire
|
|
|
295
309
|
|
|
296
310
|
def close_state(timeout)
|
|
297
311
|
reject_runtime_call_during_configure!(:close)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
308
326
|
end
|
|
327
|
+
transition
|
|
309
328
|
end
|
|
310
329
|
|
|
311
330
|
def call_pipeline_lifecycle_on(pipeline, method_name, timeout:, state:)
|
|
@@ -341,8 +360,8 @@ module Julewire
|
|
|
341
360
|
end
|
|
342
361
|
end
|
|
343
362
|
|
|
344
|
-
def envelope_hash(value)
|
|
345
|
-
value.is_a?(Hash)
|
|
363
|
+
def envelope_hash(value, owned:)
|
|
364
|
+
value if owned || value.is_a?(Hash)
|
|
346
365
|
end
|
|
347
366
|
|
|
348
367
|
def record_post_close_emit(state)
|
|
@@ -370,48 +389,48 @@ module Julewire
|
|
|
370
389
|
end
|
|
371
390
|
|
|
372
391
|
def pipeline_degraded?(pipeline_health)
|
|
373
|
-
return true if pipeline_health
|
|
392
|
+
return true if degraded_health?(pipeline_health)
|
|
374
393
|
|
|
375
394
|
pipeline_health.fetch(:destinations).values.any? do |destination_health|
|
|
376
|
-
destination_health
|
|
395
|
+
degraded_health?(destination_health)
|
|
377
396
|
end
|
|
378
397
|
end
|
|
379
398
|
|
|
380
399
|
def integrations_degraded?(integrations)
|
|
381
400
|
integrations.values.any? do |integration_health|
|
|
382
|
-
integration_health
|
|
401
|
+
degraded_health?(integration_health)
|
|
383
402
|
end
|
|
384
403
|
end
|
|
385
404
|
|
|
405
|
+
def degraded_health?(health)
|
|
406
|
+
status = health[:status]
|
|
407
|
+
status && !status.equal?(:ok)
|
|
408
|
+
end
|
|
409
|
+
|
|
386
410
|
def clear_runtime_degradation_if_unchanged(marker)
|
|
387
411
|
@runtime_health.clear_degradation_if_unchanged(marker)
|
|
388
412
|
end
|
|
389
413
|
|
|
390
414
|
def summary_finalizer_failure
|
|
391
|
-
@summary_finalizer_failure ||= ->(error) { handle_summary_finalizer_failure(error) }
|
|
415
|
+
@summary_finalizer_failure ||= ->(error, **metadata) { handle_summary_finalizer_failure(error, **metadata) }
|
|
392
416
|
end
|
|
393
417
|
|
|
394
418
|
def reset_after_fork_state!
|
|
395
|
-
|
|
396
|
-
@configure_mutex = Mutex.new
|
|
397
|
-
@configure_generation = Concurrent::AtomicFixnum.new(0)
|
|
398
|
-
@state_mutex = Mutex.new
|
|
399
|
-
@post_close_emit_count = Concurrent::AtomicFixnum.new(0)
|
|
419
|
+
@post_close_emit_count.value = 0
|
|
400
420
|
@runtime_health = build_runtime_health
|
|
401
|
-
@integration_health.
|
|
402
|
-
@invalid_severity_reporter.
|
|
403
|
-
@state_ref = Concurrent::AtomicReference.new(state)
|
|
404
|
-
nil
|
|
421
|
+
@integration_health.reset!
|
|
422
|
+
@invalid_severity_reporter.reset!
|
|
405
423
|
end
|
|
406
424
|
|
|
407
425
|
def reset_under_lock
|
|
408
426
|
state = runtime_state
|
|
409
427
|
configuration = Configuration.new.snapshot
|
|
410
428
|
@invalid_severity_reporter.reset!
|
|
411
|
-
next_pipeline = configuration.build_pipeline
|
|
412
|
-
|
|
413
|
-
|
|
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)
|
|
414
432
|
@post_close_emit_count.value = 0
|
|
433
|
+
ContextStore.reset_current!
|
|
415
434
|
@runtime_health.clear_failures!
|
|
416
435
|
@integration_health.reset!
|
|
417
436
|
Diagnostics::ProcessIntegrationHealth.reset!
|
|
@@ -429,12 +448,12 @@ module Julewire
|
|
|
429
448
|
raise Error, "Julewire.#{method_name} cannot be called from inside Julewire.configure"
|
|
430
449
|
end
|
|
431
450
|
|
|
432
|
-
|
|
451
|
+
yield if block_given?
|
|
433
452
|
end
|
|
434
453
|
|
|
435
454
|
def configure_guard_active?
|
|
436
455
|
token = Fiber[CONFIGURE_GUARD_KEY]
|
|
437
|
-
token.
|
|
456
|
+
token.instance_of?(Array) && token.fetch(0) == object_id && token.fetch(1) == @configure_generation.value
|
|
438
457
|
end
|
|
439
458
|
|
|
440
459
|
def increment_lifecycle_attempt(method_name)
|
|
@@ -457,9 +476,8 @@ module Julewire
|
|
|
457
476
|
@runtime_health.record_failure(error, callback: state.configuration.on_failure, **metadata)
|
|
458
477
|
end
|
|
459
478
|
|
|
460
|
-
def handle_summary_finalizer_failure(error)
|
|
479
|
+
def handle_summary_finalizer_failure(error, **metadata)
|
|
461
480
|
state = runtime_state
|
|
462
|
-
metadata = { phase: :summary_finalizer }
|
|
463
481
|
@runtime_health.record_failure(error, callback: state.configuration.on_failure, **metadata)
|
|
464
482
|
end
|
|
465
483
|
|
|
@@ -484,8 +502,6 @@ module Julewire
|
|
|
484
502
|
end
|
|
485
503
|
|
|
486
504
|
def notify_lifecycle_warning(warning)
|
|
487
|
-
return unless warning
|
|
488
|
-
|
|
489
505
|
result = Diagnostics::CallbackNotifier.call(warning.fetch(:on_failure), warning.fetch(:error),
|
|
490
506
|
warning.fetch(:metadata))
|
|
491
507
|
@runtime_health.record_callback_failure(result) if Diagnostics::CallbackNotifier.failure?(result)
|
|
@@ -494,7 +510,6 @@ module Julewire
|
|
|
494
510
|
def build_runtime_health
|
|
495
511
|
Diagnostics::Health.new(
|
|
496
512
|
counter_keys: RUNTIME_COUNTER_KEYS,
|
|
497
|
-
callback_metadata: {},
|
|
498
513
|
callback_failure_counter: :runtime_callback_failures,
|
|
499
514
|
failure_counter: :runtime_failures
|
|
500
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
|