5858import java .nio .file .StandardCopyOption ;
5959import java .util .Optional ;
6060import java .util .concurrent .TimeUnit ;
61+ import java .util .stream .Stream ;
6162import java .util .zip .ZipEntry ;
6263import java .util .zip .ZipOutputStream ;
6364import java .nio .charset .Charset ;
@@ -67,6 +68,7 @@ public class RemoteEngineBuilder {
6768
6869 private static final Logger LOGGER = LoggerFactory .getLogger (RemoteEngineBuilder .class );
6970 private static final String RECONNECT_METRIC_ID = "extender.service.remoteBuilder.reconnect" ;
71+ private static final int MAX_ERROR_BODY_LENGTH = 200 ;
7072
7173 private GCPInstanceService instanceService ;
7274 private final MeterRegistry meterRegistry ;
@@ -141,6 +143,12 @@ private void countReconnect(HttpHost builderHost, String operation) {
141143 "operation" , operation );
142144 }
143145
146+ // Remote builders can return a large HTML error page as a job_status body; cap what we echo
147+ // into error.txt so a runaway body never bloats the file the client downloads.
148+ private static String truncate (String body ) {
149+ return body .length () <= MAX_ERROR_BODY_LENGTH ? body : body .substring (0 , MAX_ERROR_BODY_LENGTH ) + "..." ;
150+ }
151+
144152 private static String requestOperation (HttpRequest request ) {
145153 try {
146154 // request paths are /build_async/<platform>/<sdk>, /job_status, /job_result
@@ -207,7 +215,26 @@ public void buildAsync(final RemoteInstanceConfig remoteInstanceConfig,
207215 touchInstance (remoteInstanceConfig .getInstanceId ());
208216 HttpGet statusRequest = new HttpGet (String .format ("%s/job_status?jobId=%s" , remoteInstanceConfig .getUrl (), jobId ));
209217 try (CloseableHttpResponse statusResponse = httpClient .execute (statusRequest )) {
210- jobStatus = Integer .valueOf (EntityUtils .toString (statusResponse .getEntity ()));
218+ int statusCode = statusResponse .getStatusLine ().getStatusCode ();
219+ if (statusCode != HttpStatus .SC_OK ) {
220+ LOGGER .error (Markers .SERVER_ERROR , "Remote builder returned HTTP {} for job_status of job {}" , statusCode , jobId );
221+ File errorFile = new File (resultDir , BuilderConstants .BUILD_ERROR_FILENAME );
222+ try (PrintWriter writer = new PrintWriter (errorFile )) {
223+ writer .write (String .format ("Remote builder returned HTTP %d for job_status of job %s" , statusCode , jobId ));
224+ }
225+ return ;
226+ }
227+ String jobStatusBody = EntityUtils .toString (statusResponse .getEntity ());
228+ try {
229+ jobStatus = Integer .valueOf (jobStatusBody .trim ());
230+ } catch (NumberFormatException exc ) {
231+ LOGGER .error (Markers .SERVER_ERROR , "Remote builder returned malformed job_status '{}' for job {}" , truncate (jobStatusBody ), jobId );
232+ File errorFile = new File (resultDir , BuilderConstants .BUILD_ERROR_FILENAME );
233+ try (PrintWriter writer = new PrintWriter (errorFile )) {
234+ writer .write (String .format ("Remote builder returned malformed job_status '%s' for job %s" , truncate (jobStatusBody ), jobId ));
235+ }
236+ return ;
237+ }
211238 }
212239 if (jobStatus != 0 ) {
213240 LOGGER .info (String .format ("Job %s status is %d" , jobId , jobStatus ));
@@ -255,6 +282,17 @@ private void downloadResult(final RemoteInstanceConfig remoteInstanceConfig, Str
255282 for (int attempt = 0 ; ; ++attempt ) {
256283 touchInstance (remoteInstanceConfig .getInstanceId ());
257284 try (CloseableHttpResponse resultResponse = httpClient .execute (new HttpGet (resultUrl ))) {
285+ int resultStatusCode = resultResponse .getStatusLine ().getStatusCode ();
286+ if (resultStatusCode != HttpStatus .SC_OK ) {
287+ // A completed HTTP error (not a mid-download IOException) is a definitive builder
288+ // fault, so don't retry it and never copy its body into build.zip.
289+ LOGGER .error (Markers .SERVER_ERROR , "Remote builder returned HTTP {} for job_result of job {}" , resultStatusCode , jobId );
290+ File errorFile = new File (resultDir , BuilderConstants .BUILD_ERROR_FILENAME );
291+ try (PrintWriter writer = new PrintWriter (errorFile )) {
292+ writer .write (String .format ("Remote builder returned HTTP %d for job_result of job %s" , resultStatusCode , jobId ));
293+ }
294+ return ;
295+ }
258296 if (jobStatus == BuilderConstants .JobStatus .SUCCESS .ordinal ()) {
259297 // Write zip file to result directory
260298 File tmpResult = new File (resultDir , BuilderConstants .BUILD_RESULT_FILENAME + ".tmp" );
@@ -287,9 +325,11 @@ HttpEntity buildHttpEntity(final File projectDirectory, File tmpUploadArchive) t
287325 MultipartEntityBuilder entityBuilder = MultipartEntityBuilder .create ();
288326 entityBuilder .setStrictMode ();
289327
290- try (OutputStream fileOut = Files .newOutputStream (tmpUploadArchive .toPath ()); ZipOutputStream zipStream = new ZipOutputStream (fileOut )) {
291- Path projectDirectoryPath = projectDirectory .toPath ();
292- Files .walk (projectDirectoryPath )
328+ Path projectDirectoryPath = projectDirectory .toPath ();
329+ try (OutputStream fileOut = Files .newOutputStream (tmpUploadArchive .toPath ());
330+ ZipOutputStream zipStream = new ZipOutputStream (fileOut );
331+ Stream <Path > projectFiles = Files .walk (projectDirectoryPath )) {
332+ projectFiles
293333 .filter (Files ::isRegularFile )
294334 .filter (path -> !path .getFileName ().toString ().equals (ExtenderConst .SOURCE_CODE_ARCHIVE_MAGIC_NAME ))
295335 .filter (path -> !path .getFileName ().toString ().equals (DataCacheService .FILE_CACHE_INFO_FILE ))
0 commit comments