@@ -34,14 +34,25 @@ async function build(sdk, params) {
3434 return waitForBuildEndTime ( sdk , start . build ) ;
3535}
3636
37- async function waitForBuildEndTime ( sdk , { id, logs } , nextToken ) {
37+ async function waitForBuildEndTime (
38+ sdk ,
39+ { id, logs } ,
40+ seqEmptyLogs ,
41+ totalEvents ,
42+ throttleCount ,
43+ nextToken
44+ ) {
3845 const {
3946 codeBuild,
4047 cloudWatchLogs,
4148 wait = 1000 * 30 ,
4249 backOff = 1000 * 15 ,
4350 } = sdk ;
4451
52+ totalEvents = totalEvents || 0 ;
53+ seqEmptyLogs = seqEmptyLogs || 0 ;
54+ throttleCount = throttleCount || 0 ;
55+
4556 // Get the CloudWatchLog info
4657 const startFromHead = true ;
4758 const { cloudWatchLogsArn } = logs ;
@@ -55,7 +66,12 @@ async function waitForBuildEndTime(sdk, { id, logs }, nextToken) {
5566 // The CloudWatchLog _may_ not be set up, only make the call if we have a logGroupName
5667 logGroupName &&
5768 cloudWatchLogs
58- . getLogEvents ( { logGroupName, logStreamName, startFromHead, nextToken } )
69+ . getLogEvents ( {
70+ logGroupName,
71+ logStreamName,
72+ startFromHead,
73+ nextToken,
74+ } )
5975 . promise ( ) ,
6076 ] ) . catch ( ( err ) => {
6177 errObject = err ;
@@ -72,6 +88,7 @@ async function waitForBuildEndTime(sdk, { id, logs }, nextToken) {
7288 if ( errObject . message && errObject . message . search ( "Rate exceeded" ) !== - 1 ) {
7389 //We were rate-limited, so add `backOff` seconds to the wait time
7490 let newWait = wait + backOff ;
91+ throttleCount ++ ;
7592
7693 //Sleep before trying again
7794 await new Promise ( ( resolve ) => setTimeout ( resolve , newWait ) ) ;
@@ -80,6 +97,9 @@ async function waitForBuildEndTime(sdk, { id, logs }, nextToken) {
8097 return waitForBuildEndTime (
8198 { ...sdk , wait : newWait } ,
8299 { id, logs } ,
100+ seqEmptyLogs ,
101+ totalEvents ,
102+ throttleCount ,
83103 nextToken
84104 ) ;
85105 } else {
@@ -92,20 +112,42 @@ async function waitForBuildEndTime(sdk, { id, logs }, nextToken) {
92112 const [ current ] = batch . builds ;
93113 const { nextForwardToken, events = [ ] } = cloudWatch ;
94114
115+ // GetLogEvents can return partial/empty responses even when there is data.
116+ // We wait for two consecutive empty log responses to minimize false positive on EOF.
117+ // Empty response counter starts after any logs have been received, or when the build completes.
118+ if ( events . length == 0 && ( totalEvents > 0 || current . endTime ) ) {
119+ seqEmptyLogs ++ ;
120+ } else {
121+ seqEmptyLogs = 0 ;
122+ }
123+ totalEvents += events . length ;
124+
95125 // stdout the CloudWatchLog (everyone likes progress...)
96126 // CloudWatchLogs have line endings.
97127 // I trim and then log each line
98128 // to ensure that the line ending is OS specific.
99129 events . forEach ( ( { message } ) => console . log ( message . trimEnd ( ) ) ) ;
100130
101- // We did it! We can stop looking!
102- if ( current . endTime && ! events . length ) return current ;
131+ // Stop after the build is ended and we've received two consecutive empty log responses
132+ if ( current . endTime && seqEmptyLogs >= 2 ) {
133+ return current ;
134+ }
103135
104136 // More to do: Sleep for a few seconds to avoid rate limiting
105- await new Promise ( ( resolve ) => setTimeout ( resolve , wait ) ) ;
137+ // If never throttled and build is complete, halve CWL polling delay to minimize latency
138+ await new Promise ( ( resolve ) =>
139+ setTimeout ( resolve , current . endTime && throttleCount == 0 ? wait / 2 : wait )
140+ ) ;
106141
107142 // Try again
108- return waitForBuildEndTime ( sdk , current , nextForwardToken ) ;
143+ return waitForBuildEndTime (
144+ sdk ,
145+ current ,
146+ seqEmptyLogs ,
147+ totalEvents ,
148+ throttleCount ,
149+ nextForwardToken
150+ ) ;
109151}
110152
111153function githubInputs ( ) {
0 commit comments