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  }