Welcome to mirror list, hosted at ThFree Co, Russian Federation.

cross_database_modification.rb « concerns « models « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: dea62f03f91c11aba4b101d0258dc9ddbfa92fdd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# frozen_string_literal: true

module CrossDatabaseModification
  extend ActiveSupport::Concern

  class TransactionStackTrackRecord
    DEBUG_STACK = Rails.env.test? && ENV['DEBUG_GITLAB_TRANSACTION_STACK']
    LOG_FILENAME = Rails.root.join("log", "gitlab_transaction_stack.log")

    EXCLUDE_DEBUG_TRACE = %w[
      lib/gitlab/database/query_analyzer
      app/models/concerns/cross_database_modification.rb
    ].freeze

    def self.logger
      @logger ||= Logger.new(LOG_FILENAME, formatter: ->(_, _, _, msg) { Gitlab::Json.dump(msg) + "\n" })
    end

    def self.log_gitlab_transactions_stack(action: nil, example: nil)
      return unless DEBUG_STACK

      message = "gitlab_transactions_stack performing #{action}"
      message += " in example #{example}" if example

      cleaned_backtrace = Gitlab::BacktraceCleaner.clean_backtrace(caller)
        .reject { |line| EXCLUDE_DEBUG_TRACE.any? { |exclusion| line.include?(exclusion) } }
        .first(5)

      logger.warn({
        message: message,
        action: action,
        gitlab_transactions_stack: ::ApplicationRecord.gitlab_transactions_stack,
        caller: cleaned_backtrace,
        thread: Thread.current.object_id
      })
    end

    def initialize(subject, gitlab_schema)
      @subject = subject
      @gitlab_schema = gitlab_schema
      @subject.gitlab_transactions_stack.push(gitlab_schema)

      self.class.log_gitlab_transactions_stack(action: :after_push)
    end

    def done!
      unless @done
        @done = true

        self.class.log_gitlab_transactions_stack(action: :before_pop)
        @subject.gitlab_transactions_stack.pop
      end

      true
    end

    def trigger_transactional_callbacks?
      false
    end

    def before_committed!
    end

    def rolledback!(force_restore_state: false, should_run_callbacks: true)
      done!
    end

    def committed!(should_run_callbacks: true)
      done!
    end
  end

  included do
    private_class_method :gitlab_schema
  end

  class_methods do
    def gitlab_transactions_stack
      Thread.current[:gitlab_transactions_stack] ||= []
    end

    def transaction(**options, &block)
      if track_gitlab_schema_in_current_transaction?
        super(**options) do
          # Hook into current transaction to ensure that once
          # the `COMMIT` is executed the `gitlab_transactions_stack`
          # will be allowing to execute `after_commit_queue`
          record = TransactionStackTrackRecord.new(self, gitlab_schema)

          begin
            connection.current_transaction.add_record(record)

            yield
          ensure
            record.done!
          end
        end
      else
        super(**options, &block)
      end
    end

    def track_gitlab_schema_in_current_transaction?
      return false unless Feature::FlipperFeature.table_exists?

      Feature.enabled?(:track_gitlab_schema_in_current_transaction)
    rescue ActiveRecord::NoDatabaseError, PG::ConnectionBad
      false
    end

    def gitlab_schema
      case self.name
      when 'ActiveRecord::Base', 'ApplicationRecord'
        :gitlab_main
      when 'Ci::ApplicationRecord'
        :gitlab_ci
      else
        Gitlab::Database::GitlabSchema.table_schema(table_name) if table_name
      end
    end
  end
end