github.com/secure-build/gitlab-runner@v12.5.0+incompatible/network/trace.go (about) 1 package network 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "gitlab.com/gitlab-org/gitlab-runner/common" 9 "gitlab.com/gitlab-org/gitlab-runner/helpers/trace" 10 ) 11 12 type clientJobTrace struct { 13 client common.Network 14 config common.RunnerConfig 15 jobCredentials *common.JobCredentials 16 id int 17 cancelFunc context.CancelFunc 18 19 buffer *trace.Buffer 20 21 lock sync.RWMutex 22 state common.JobState 23 failureReason common.JobFailureReason 24 finished chan bool 25 26 sentTrace int 27 sentTime time.Time 28 29 updateInterval time.Duration 30 forceSendInterval time.Duration 31 finishRetryInterval time.Duration 32 maxTracePatchSize int 33 34 failuresCollector common.FailuresCollector 35 } 36 37 func (c *clientJobTrace) Success() { 38 c.Fail(nil, common.NoneFailure) 39 } 40 41 func (c *clientJobTrace) Fail(err error, failureReason common.JobFailureReason) { 42 c.lock.Lock() 43 44 if c.state != common.Running { 45 c.lock.Unlock() 46 return 47 } 48 49 if err == nil { 50 c.state = common.Success 51 } else { 52 c.setFailure(failureReason) 53 } 54 55 c.lock.Unlock() 56 c.finish() 57 } 58 59 func (c *clientJobTrace) Write(data []byte) (n int, err error) { 60 return c.buffer.Write(data) 61 } 62 63 func (c *clientJobTrace) SetMasked(masked []string) { 64 c.buffer.SetMasked(masked) 65 } 66 67 func (c *clientJobTrace) SetCancelFunc(cancelFunc context.CancelFunc) { 68 c.cancelFunc = cancelFunc 69 } 70 71 func (c *clientJobTrace) SetFailuresCollector(fc common.FailuresCollector) { 72 c.failuresCollector = fc 73 } 74 75 func (c *clientJobTrace) IsStdout() bool { 76 return false 77 } 78 79 func (c *clientJobTrace) setFailure(reason common.JobFailureReason) { 80 c.state = common.Failed 81 c.failureReason = reason 82 if c.failuresCollector != nil { 83 c.failuresCollector.RecordFailure(reason, c.config.ShortDescription()) 84 } 85 } 86 87 func (c *clientJobTrace) start() { 88 c.finished = make(chan bool) 89 c.state = common.Running 90 c.setupLogLimit() 91 go c.watch() 92 } 93 94 func (c *clientJobTrace) finalTraceUpdate() { 95 for c.anyTraceToSend() { 96 switch c.sendPatch() { 97 case common.UpdateSucceeded: 98 // we continue sending till we succeed 99 continue 100 case common.UpdateAbort: 101 return 102 case common.UpdateNotFound: 103 return 104 case common.UpdateRangeMismatch: 105 time.Sleep(c.finishRetryInterval) 106 case common.UpdateFailed: 107 time.Sleep(c.finishRetryInterval) 108 } 109 } 110 } 111 112 func (c *clientJobTrace) finalStatusUpdate() { 113 for { 114 switch c.sendUpdate() { 115 case common.UpdateSucceeded: 116 return 117 case common.UpdateAbort: 118 return 119 case common.UpdateNotFound: 120 return 121 case common.UpdateRangeMismatch: 122 return 123 case common.UpdateFailed: 124 time.Sleep(c.finishRetryInterval) 125 } 126 } 127 } 128 129 func (c *clientJobTrace) finish() { 130 c.buffer.Finish() 131 c.finished <- true 132 c.finalTraceUpdate() 133 c.finalStatusUpdate() 134 c.buffer.Close() 135 } 136 137 func (c *clientJobTrace) incrementalUpdate() common.UpdateState { 138 state := c.sendPatch() 139 if state != common.UpdateSucceeded { 140 return state 141 } 142 143 return c.touchJob() 144 } 145 146 func (c *clientJobTrace) anyTraceToSend() bool { 147 c.lock.RLock() 148 defer c.lock.RUnlock() 149 150 return c.buffer.Size() != c.sentTrace 151 } 152 153 func (c *clientJobTrace) sendPatch() common.UpdateState { 154 c.lock.RLock() 155 content, err := c.buffer.Bytes(c.sentTrace, c.maxTracePatchSize) 156 sentTrace := c.sentTrace 157 c.lock.RUnlock() 158 159 if err != nil { 160 return common.UpdateFailed 161 } 162 163 if len(content) == 0 { 164 return common.UpdateSucceeded 165 } 166 167 sentOffset, state := c.client.PatchTrace( 168 c.config, c.jobCredentials, content, sentTrace) 169 170 if state == common.UpdateSucceeded || state == common.UpdateRangeMismatch { 171 c.lock.Lock() 172 c.sentTime = time.Now() 173 c.sentTrace = sentOffset 174 c.lock.Unlock() 175 } 176 177 return state 178 } 179 180 // Update Coordinator that the job is still running. 181 func (c *clientJobTrace) touchJob() common.UpdateState { 182 c.lock.RLock() 183 shouldRefresh := time.Since(c.sentTime) > c.forceSendInterval 184 c.lock.RUnlock() 185 186 if !shouldRefresh { 187 return common.UpdateSucceeded 188 } 189 190 jobInfo := common.UpdateJobInfo{ 191 ID: c.id, 192 State: common.Running, 193 } 194 195 status := c.client.UpdateJob(c.config, c.jobCredentials, jobInfo) 196 197 if status == common.UpdateSucceeded { 198 c.lock.Lock() 199 c.sentTime = time.Now() 200 c.lock.Unlock() 201 } 202 203 return status 204 } 205 206 func (c *clientJobTrace) sendUpdate() common.UpdateState { 207 c.lock.RLock() 208 state := c.state 209 c.lock.RUnlock() 210 211 jobInfo := common.UpdateJobInfo{ 212 ID: c.id, 213 State: state, 214 FailureReason: c.failureReason, 215 } 216 217 status := c.client.UpdateJob(c.config, c.jobCredentials, jobInfo) 218 219 if status == common.UpdateSucceeded { 220 c.lock.Lock() 221 c.sentTime = time.Now() 222 c.lock.Unlock() 223 } 224 225 return status 226 } 227 228 func (c *clientJobTrace) abort() bool { 229 if c.cancelFunc != nil { 230 c.cancelFunc() 231 c.cancelFunc = nil 232 return true 233 } 234 return false 235 } 236 237 func (c *clientJobTrace) watch() { 238 for { 239 select { 240 case <-time.After(c.updateInterval): 241 state := c.incrementalUpdate() 242 if state == common.UpdateAbort && c.abort() { 243 <-c.finished 244 return 245 } 246 break 247 248 case <-c.finished: 249 return 250 } 251 } 252 } 253 254 func (c *clientJobTrace) setupLogLimit() { 255 bytesLimit := c.config.OutputLimit * 1024 // convert to bytes 256 if bytesLimit == 0 { 257 bytesLimit = common.DefaultOutputLimit 258 } 259 260 c.buffer.SetLimit(bytesLimit) 261 } 262 263 func newJobTrace(client common.Network, config common.RunnerConfig, jobCredentials *common.JobCredentials) (*clientJobTrace, error) { 264 buffer, err := trace.New() 265 if err != nil { 266 return nil, err 267 } 268 269 return &clientJobTrace{ 270 client: client, 271 config: config, 272 buffer: buffer, 273 jobCredentials: jobCredentials, 274 id: jobCredentials.ID, 275 maxTracePatchSize: common.DefaultTracePatchLimit, 276 updateInterval: common.UpdateInterval, 277 forceSendInterval: common.ForceTraceSentInterval, 278 finishRetryInterval: common.UpdateRetryInterval, 279 }, nil 280 }