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 }