github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/build_event_source.go (about)

     1  package db
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"sync"
     7  
     8  	"github.com/pf-qiu/concourse/v6/atc"
     9  	"github.com/pf-qiu/concourse/v6/atc/event"
    10  )
    11  
    12  var ErrEndOfBuildEventStream = errors.New("end of build event stream")
    13  var ErrBuildEventStreamClosed = errors.New("build event stream closed")
    14  
    15  //go:generate counterfeiter . EventSource
    16  
    17  type EventSource interface {
    18  	Next() (event.Envelope, error)
    19  	Close() error
    20  }
    21  
    22  func newBuildEventSource(
    23  	buildID int,
    24  	table string,
    25  	conn Conn,
    26  	notifier Notifier,
    27  	from uint,
    28  ) *buildEventSource {
    29  	wg := new(sync.WaitGroup)
    30  
    31  	source := &buildEventSource{
    32  		buildID: buildID,
    33  		table:   table,
    34  
    35  		conn: conn,
    36  
    37  		notifier: notifier,
    38  
    39  		events: make(chan event.Envelope, 2000),
    40  		stop:   make(chan struct{}),
    41  		wg:     wg,
    42  	}
    43  
    44  	wg.Add(1)
    45  	go source.collectEvents(from)
    46  
    47  	return source
    48  }
    49  
    50  type buildEventSource struct {
    51  	buildID int
    52  	table   string
    53  
    54  	conn     Conn
    55  	notifier Notifier
    56  
    57  	events chan event.Envelope
    58  	stop   chan struct{}
    59  	err    error
    60  	wg     *sync.WaitGroup
    61  }
    62  
    63  func (source *buildEventSource) Next() (event.Envelope, error) {
    64  	e, ok := <-source.events
    65  	if !ok {
    66  		return event.Envelope{}, source.err
    67  	}
    68  
    69  	return e, nil
    70  }
    71  
    72  func (source *buildEventSource) Close() error {
    73  	select {
    74  	case <-source.stop:
    75  		return nil
    76  	default:
    77  		close(source.stop)
    78  	}
    79  
    80  	source.wg.Wait()
    81  
    82  	return source.notifier.Close()
    83  }
    84  
    85  func (source *buildEventSource) collectEvents(cursor uint) {
    86  	defer source.wg.Done()
    87  
    88  	var batchSize = cap(source.events)
    89  
    90  	for {
    91  		select {
    92  		case <-source.stop:
    93  			source.err = ErrBuildEventStreamClosed
    94  			close(source.events)
    95  			return
    96  		default:
    97  		}
    98  
    99  		completed := false
   100  
   101  		tx, err := source.conn.Begin()
   102  		if err != nil {
   103  			return
   104  		}
   105  
   106  		defer Rollback(tx)
   107  
   108  		err = tx.QueryRow(`
   109  			SELECT builds.completed
   110  			FROM builds
   111  			WHERE builds.id = $1
   112  		`, source.buildID).Scan(&completed)
   113  		if err != nil {
   114  			source.err = err
   115  			close(source.events)
   116  			return
   117  		}
   118  
   119  		rows, err := tx.Query(`
   120  			SELECT type, version, payload
   121  			FROM `+source.table+`
   122  			WHERE build_id = $1 OR build_id_old = $1
   123  			ORDER BY event_id ASC
   124  			OFFSET $2
   125  			LIMIT $3
   126  		`, source.buildID, cursor, batchSize)
   127  		if err != nil {
   128  			source.err = err
   129  			close(source.events)
   130  			return
   131  		}
   132  
   133  		rowsReturned := 0
   134  
   135  		for rows.Next() {
   136  			rowsReturned++
   137  
   138  			cursor++
   139  
   140  			var t, v, p string
   141  			err := rows.Scan(&t, &v, &p)
   142  			if err != nil {
   143  				_ = rows.Close()
   144  
   145  				source.err = err
   146  				close(source.events)
   147  				return
   148  			}
   149  
   150  			data := json.RawMessage(p)
   151  
   152  			ev := event.Envelope{
   153  				Data:    &data,
   154  				Event:   atc.EventType(t),
   155  				Version: atc.EventVersion(v),
   156  			}
   157  
   158  			select {
   159  			case source.events <- ev:
   160  			case <-source.stop:
   161  				_ = rows.Close()
   162  
   163  				source.err = ErrBuildEventStreamClosed
   164  				close(source.events)
   165  				return
   166  			}
   167  		}
   168  
   169  		err = tx.Commit()
   170  		if err != nil {
   171  			close(source.events)
   172  			return
   173  		}
   174  
   175  		if rowsReturned == batchSize {
   176  			// still more events
   177  			continue
   178  		}
   179  
   180  		if completed {
   181  			source.err = ErrEndOfBuildEventStream
   182  			close(source.events)
   183  			return
   184  		}
   185  
   186  		select {
   187  		case <-source.notifier.Notify():
   188  		case <-source.stop:
   189  			source.err = ErrBuildEventStreamClosed
   190  			close(source.events)
   191  			return
   192  		}
   193  	}
   194  }