Commit e7f22619 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Validate build trace in an exclusive trace lock

This makes sure that traces are not being updated while we validate
them. In case of a deadlock being detected we will increase a relevant
metric and ask GitLab Runner to retry the request.
parent 91fb7752
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
module Ci module Ci
class UpdateBuildStateService class UpdateBuildStateService
include Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
include ::Gitlab::ExclusiveLeaseHelpers
Result = Struct.new(:status, :backoff, keyword_init: true) Result = Struct.new(:status, :backoff, keyword_init: true)
...@@ -18,25 +19,45 @@ module Ci ...@@ -18,25 +19,45 @@ module Ci
def execute def execute
overwrite_trace! if has_trace? overwrite_trace! if has_trace?
create_pending_state! if accept_available?
if accept_request? unless accept_available?
accept_build_state! return update_build_state!
else end
validate_build_trace!
update_build_state! ensure_pending_state!
in_build_trace_lock do
process_build_state!
end end
end end
private private
def accept_build_state! def overwrite_trace!
if Time.current - pending_state.created_at > ACCEPT_TIMEOUT metrics.increment_trace_operation(operation: :overwrite)
metrics.increment_trace_operation(operation: :discarded)
return update_build_state! build.trace.set(params[:trace]) if Gitlab::Ci::Features.trace_overwrite?
end
def ensure_pending_state!
pending_state.created_at
end end
def process_build_state!
if live_chunks_pending?
if pending_state_outdated?
discard_build_trace!
update_build_state!
else
accept_build_state!
end
else
validate_build_trace!
update_build_state!
end
end
def accept_build_state!
build.trace_chunks.live.find_each do |chunk| build.trace_chunks.live.find_each do |chunk|
chunk.schedule_to_persist! chunk.schedule_to_persist!
end end
...@@ -48,26 +69,14 @@ module Ci ...@@ -48,26 +69,14 @@ module Ci
end end
end end
def overwrite_trace!
metrics.increment_trace_operation(operation: :overwrite)
build.trace.set(params[:trace]) if Gitlab::Ci::Features.trace_overwrite?
end
def create_pending_state!
pending_state.created_at
end
def validate_build_trace! def validate_build_trace!
return unless accept_available? if chunks_persisted?
metrics.increment_trace_operation(operation: :finalized)
end
unless ::Gitlab::Ci::Trace::Checksum.new(build).valid? unless ::Gitlab::Ci::Trace::Checksum.new(build).valid?
metrics.increment_trace_operation(operation: :invalid) metrics.increment_trace_operation(operation: :invalid)
end end
return unless chunks_persisted?
metrics.increment_trace_operation(operation: :finalized)
end end
def update_build_state! def update_build_state!
...@@ -89,12 +98,24 @@ module Ci ...@@ -89,12 +98,24 @@ module Ci
end end
end end
def discard_build_trace!
metrics.increment_trace_operation(operation: :discarded)
end
def accept_available? def accept_available?
!build_running? && has_checksum? && chunks_migration_enabled? !build_running? && has_checksum? && chunks_migration_enabled?
end end
def accept_request? def live_chunks_pending?
accept_available? && live_chunks_pending? build.trace_chunks.live.any?
end
def chunks_persisted?
build.trace_chunks.any? && !live_chunks_pending?
end
def pending_state_outdated?
Time.current - pending_state.created_at > ACCEPT_TIMEOUT
end end
def build_state def build_state
...@@ -109,14 +130,6 @@ module Ci ...@@ -109,14 +130,6 @@ module Ci
params.dig(:checksum).present? params.dig(:checksum).present?
end end
def live_chunks_pending?
build.trace_chunks.live.any?
end
def chunks_persisted?
build.trace_chunks.any? && !live_chunks_pending?
end
def build_running? def build_running?
build_state == 'running' build_state == 'running'
end end
...@@ -138,6 +151,14 @@ module Ci ...@@ -138,6 +151,14 @@ module Ci
build.pending_state build.pending_state
end end
def in_build_trace_lock(&block)
build.trace.lock(&block) # rubocop:disable CodeReuse/ActiveRecord
rescue FailedToObtainLockError
metrics.increment_trace_operation(operation: :locked)
accept_build_state!
end
def chunks_migration_enabled? def chunks_migration_enabled?
::Gitlab::Ci::Features.accept_trace?(build.project) ::Gitlab::Ci::Features.accept_trace?(build.project)
end end
......
...@@ -130,6 +130,10 @@ module Gitlab ...@@ -130,6 +130,10 @@ module Gitlab
end end
end end
def lock(&block)
in_write_lock(&block)
end
private private
def read_stream def read_stream
......
...@@ -7,7 +7,8 @@ module Gitlab ...@@ -7,7 +7,8 @@ module Gitlab
extend Gitlab::Utils::StrongMemoize extend Gitlab::Utils::StrongMemoize
OPERATIONS = [:appended, :streamed, :chunked, :mutated, :overwrite, OPERATIONS = [:appended, :streamed, :chunked, :mutated, :overwrite,
:accepted, :finalized, :discarded, :conflict, :invalid].freeze :accepted, :finalized, :discarded, :conflict, :locked,
:invalid].freeze
def increment_trace_operation(operation: :unknown) def increment_trace_operation(operation: :unknown)
unless OPERATIONS.include?(operation) unless OPERATIONS.include?(operation)
......
...@@ -111,4 +111,13 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do ...@@ -111,4 +111,13 @@ RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
end end
end end
end end
describe '#lock' do
it 'acquires an exclusive lease on the trace' do
trace.lock do
expect { trace.lock }
.to raise_error ::Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
end
end
end
end end
...@@ -135,6 +135,26 @@ RSpec.describe Ci::UpdateBuildStateService do ...@@ -135,6 +135,26 @@ RSpec.describe Ci::UpdateBuildStateService do
.with(operation: :invalid) .with(operation: :invalid)
end end
end end
context 'when failed to acquire a build trace lock' do
it 'accepts a state update request' do
build.trace.lock do
result = subject.execute
expect(result.status).to eq 202
end
end
it 'increment locked trace metric' do
build.trace.lock do
execute_with_stubbed_metrics!
expect(metrics)
.to have_received(:increment_trace_operation)
.with(operation: :locked)
end
end
end
end end
context 'when build trace has not been migrated yet' do context 'when build trace has not been migrated yet' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment