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
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# frozen_string_literal: true
# This class is not backed by a table in the main database.
# It loads the latest Pipeline for the HEAD of a repository, and caches that
# in Redis.
module Gitlab
module Cache
module Ci
class ProjectPipelineStatus
include Gitlab::Utils::StrongMemoize
attr_accessor :sha, :status, :ref, :project, :loaded
def self.load_for_project(project)
new(project).tap do |status|
status.load_status
end
end
def self.load_in_batch_for_projects(projects)
projects.each do |project|
project.pipeline_status = new(project)
project.pipeline_status.load_status
end
end
def self.update_for_pipeline(pipeline)
pipeline_info = {
sha: pipeline.sha,
status: pipeline.status,
ref: pipeline.ref
}
new(pipeline.project, pipeline_info: pipeline_info)
.store_in_cache_if_needed
end
def initialize(project, pipeline_info: {}, loaded_from_cache: nil)
@project = project
@sha = pipeline_info[:sha]
@ref = pipeline_info[:ref]
@status = pipeline_info[:status]
@loaded = loaded_from_cache
end
def has_status?
loaded? && sha.present? && status.present?
end
def load_status
return if loaded?
if has_cache?
load_from_cache
else
load_from_project
store_in_cache
end
self.loaded = true
rescue GRPC::Unavailable, GRPC::DeadlineExceeded => e
# Handle Gitaly connection issues gracefully
Gitlab::ErrorTracking
.track_exception(e, project_id: project.id)
end
def load_from_project
return unless commit
self.sha = commit.sha
self.status = commit.status
self.ref = project.repository.root_ref
end
# We only cache the status for the HEAD commit of a project
# This status is rendered in project lists
def store_in_cache_if_needed
return delete_from_cache unless commit
return unless sha
return unless ref
if commit.sha == sha && project.repository.root_ref == ref
store_in_cache
end
end
def load_from_cache
with_redis do |redis|
self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref)
self.status = nil if self.status.empty?
end
end
def store_in_cache
with_redis do |redis|
redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref })
end
end
def delete_from_cache
with_redis do |redis|
redis.del(cache_key)
end
end
def has_cache?
return self.loaded unless self.loaded.nil?
with_redis do |redis|
redis.exists?(cache_key) # rubocop:disable CodeReuse/ActiveRecord
end
end
def loaded?
self.loaded
end
def cache_key
"#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:project:#{project.id}:pipeline_status"
end
def commit
strong_memoize(:commit) do
project.commit
end
end
def with_redis(&block)
Gitlab::Redis::Cache.with(&block) # rubocop:disable CodeReuse/ActiveRecord
end
end
end
end
end
|