Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin should not throw exceptions when it gets a 404 result from GCP Storage #19

Closed
liutikas opened this issue May 2, 2022 · 3 comments
Assignees

Comments

@liutikas
Copy link
Member

liutikas commented May 2, 2022

https://ci.android.com/builds/submitted/8521547/androidx_device_tests/latest/view/logs/build_error.log

Could not load entry af34a49eb2b818c9dbcd5fc68bab898f from remote build cache
org.gradle.internal.operations.BuildOperationInvocationException: com.google.cloud.RetryHelper$RetryHelperException: com.google.cloud.storage.StorageException: 404 Not Found
GET https://storage.googleapis.com/download/storage/v1/b/androidx-gradle-remote-cache/o/af34a49eb2b818c9dbcd5fc68bab898f?alt=media&generation=1651284912454778
No such object: androidx-gradle-remote-cache/af34a49eb2b818c9dbcd5fc68bab898f
	at org.gradle.internal.operations.DefaultBuildOperationRunner.throwAsBuildOperationInvocationException(DefaultBuildOperationRunner.java:192)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.access$100(DefaultBuildOperationRunner.java:24)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
	at org.gradle.caching.internal.controller.service.OpFiringRemoteBuildCacheServiceHandle.loadInner(OpFiringRemoteBuildCacheServiceHandle.java:46)
	at org.gradle.caching.internal.controller.service.BaseRemoteBuildCacheServiceHandle.maybeLoad(BaseRemoteBuildCacheServiceHandle.java:77)
	at org.gradle.caching.internal.controller.DefaultBuildCacheController.lambda$loadRemoteAndStoreResultLocally$2(DefaultBuildCacheController.java:146)
	at org.gradle.caching.local.internal.DefaultBuildCacheTempFileStore.withTempFile(DefaultBuildCacheTempFileStore.java:40)
	at org.gradle.caching.local.internal.DirectoryBuildCacheService$3.run(DirectoryBuildCacheService.java:175)
	at org.gradle.internal.Factories$1.create(Factories.java:31)
	at org.gradle.cache.internal.LockOnDemandCrossProcessCacheAccess.withFileLock(LockOnDemandCrossProcessCacheAccess.java:90)
	at org.gradle.cache.internal.DefaultCacheAccess.withFileLock(DefaultCacheAccess.java:196)
	at org.gradle.cache.internal.DefaultPersistentDirectoryStore.withFileLock(DefaultPersistentDirectoryStore.java:193)
	at org.gradle.cache.internal.DefaultCacheFactory$ReferenceTrackingCache.withFileLock(DefaultCacheFactory.java:214)
	at org.gradle.caching.local.internal.DirectoryBuildCacheService.withTempFile(DirectoryBuildCacheService.java:172)
	at org.gradle.caching.internal.controller.DefaultBuildCacheController.loadRemoteAndStoreResultLocally(DefaultBuildCacheController.java:143)
	at org.gradle.caching.internal.controller.DefaultBuildCacheController.load(DefaultBuildCacheController.java:127)
	at org.gradle.internal.execution.steps.BuildCacheStep.lambda$executeWithCache$2(BuildCacheStep.java:78)
	at org.gradle.internal.Try.ofFailable(Try.java:41)
	at org.gradle.internal.execution.steps.BuildCacheStep.executeWithCache(BuildCacheStep.java:77)
	at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$0(BuildCacheStep.java:70)
	at org.gradle.internal.Either$Left.fold(Either.java:115)
	at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59)
	at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:69)
	at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:47)
	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:36)
	at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:25)
	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
	at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:110)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:56)
	at java.base/java.util.Optional.orElseGet(Optional.java:369)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:56)
	at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:38)
	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:73)
	at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:89)
	at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:50)
	at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:114)
	at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:57)
	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:76)
	at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)
	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNoEmptySources(SkipEmptyWorkStep.java:254)
	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:91)
	at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:56)
	at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32)
	at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21)
	at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
	at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:43)
	at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)
	at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
	at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:281)
	at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
	at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
	at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
	at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
	at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
	at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)
	at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:139)
	at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:128)
	at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
	at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
	at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
	at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
	at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
	at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
	at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
	at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:69)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:327)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:314)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:307)
	at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:293)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:415)
	at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:337)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.io.IOException: com.google.cloud.RetryHelper$RetryHelperException: com.google.cloud.storage.StorageException: 404 Not Found
GET https://storage.googleapis.com/download/storage/v1/b/androidx-gradle-remote-cache/o/af34a49eb2b818c9dbcd5fc68bab898f?alt=media&generation=1651284912454778
No such object: androidx-gradle-remote-cache/af34a49eb2b818c9dbcd5fc68bab898f
	at com.google.cloud.storage.BlobReadChannel.read(BlobReadChannel.java:149)
	at java.base/sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:65)
	at java.base/sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:107)
	at java.base/sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:101)
	at java.base/java.io.InputStream.read(InputStream.java:205)
	at com.google.common.io.ByteStreams.copy(ByteStreams.java:109)
	at com.google.common.io.ByteSink.writeFrom(ByteSink.java:125)
	at org.gradle.caching.internal.controller.service.LoadTarget.readFrom(LoadTarget.java:44)
	at org.gradle.caching.internal.controller.service.OpFiringRemoteBuildCacheServiceHandle$OpFiringEntryReader$1.run(OpFiringRemoteBuildCacheServiceHandle.java:99)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
	at org.gradle.caching.internal.controller.service.OpFiringRemoteBuildCacheServiceHandle$OpFiringEntryReader.readFrom(OpFiringRemoteBuildCacheServiceHandle.java:95)
	at androidx.build.gradle.gcpbuildcache.GcpBuildCacheService.load(GcpBuildCacheService.kt:59)
	at org.gradle.caching.internal.controller.service.BaseRemoteBuildCacheServiceHandle.loadInner(BaseRemoteBuildCacheServiceHandle.java:89)
	at org.gradle.caching.internal.controller.service.OpFiringRemoteBuildCacheServiceHandle$1.run(OpFiringRemoteBuildCacheServiceHandle.java:49)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	... 94 more
Caused by: com.google.cloud.RetryHelper$RetryHelperException: com.google.cloud.storage.StorageException: 404 Not Found
GET https://storage.googleapis.com/download/storage/v1/b/androidx-gradle-remote-cache/o/af34a49eb2b818c9dbcd5fc68bab898f?alt=media&generation=1651284912454778
No such object: androidx-gradle-remote-cache/af34a49eb2b818c9dbcd5fc68bab898f
	at com.google.cloud.RetryHelper.runWithRetries(RetryHelper.java:54)
	at com.google.cloud.storage.BlobReadChannel.read(BlobReadChannel.java:136)
	... 117 more
Caused by: com.google.cloud.storage.StorageException: 404 Not Found
GET https://storage.googleapis.com/download/storage/v1/b/androidx-gradle-remote-cache/o/af34a49eb2b818c9dbcd5fc68bab898f?alt=media&generation=1651284912454778
No such object: androidx-gradle-remote-cache/af34a49eb2b818c9dbcd5fc68bab898f
	at com.google.cloud.storage.StorageException.translate(StorageException.java:116)
	at com.google.cloud.storage.spi.v1.HttpStorageRpc.read(HttpStorageRpc.java:741)
	at com.google.cloud.storage.BlobReadChannel.lambda$read$0(BlobReadChannel.java:137)
	at com.google.api.gax.retrying.DirectRetryingExecutor.submit(DirectRetryingExecutor.java:103)
	at com.google.cloud.RetryHelper.run(RetryHelper.java:76)
	at com.google.cloud.RetryHelper.runWithRetries(RetryHelper.java:50)
	... 118 more
Caused by: com.google.api.client.googleapis.json.GoogleJsonResponseException: 404 Not Found
GET https://storage.googleapis.com/download/storage/v1/b/androidx-gradle-remote-cache/o/af34a49eb2b818c9dbcd5fc68bab898f?alt=media&generation=1651284912454778
No such object: androidx-gradle-remote-cache/af34a49eb2b818c9dbcd5fc68bab898f
	at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
	at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:118)
	at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:37)
	at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:428)
	at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1111)
	at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:514)
	at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:455)
	at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeMedia(AbstractGoogleClientRequest.java:479)
	at com.google.api.services.storage.Storage$Objects$Get.executeMedia(Storage.java:7222)
	at com.google.cloud.storage.spi.v1.HttpStorageRpc.read(HttpStorageRpc.java:736)
	... 122 more
@liutikas
Copy link
Member Author

liutikas commented May 2, 2022

The only plugin code in the stack is GcpBuildCacheService.kt:59.

Can we handle these better? We should just return false.

@liutikas
Copy link
Member Author

liutikas commented May 3, 2022

Going backwards from the stack, it seem that in GcpBuildCacheService#load line 59, we try to read a non-null input stream returned by GcpStorageService#load. We know it is non null because we have a null check on that same line.

So that means com.google.cloud.storage.Storage#get on line 111 of GcpStorageService returns non-null, but when we then later try to load that stream we enter BlobReadChannel#read retry logic as buffer inside of this class is null, which makes me feel that maybe we closed on this object before gradle read it or maybe gradle already somehow read this blob already.

@tikurahul
Copy link
Collaborator

I think the underlying problem we might be running into is due to the way BlobReadChannel.read(...) works. Here is a snippet:

@Override
  public int read(ByteBuffer byteBuffer) throws IOException {
    validateOpen();
    if (buffer == null) {
      if (endOfStream) {
        return -1;
      }
      final int toRead =
          Math.toIntExact(Math.min(limit - position, Math.max(byteBuffer.remaining(), chunkSize)));
      if (toRead <= 0) {
        endOfStream = true;
        return -1;
      }
      try {
        ResultRetryAlgorithm<?> algorithm =
            retryAlgorithmManager.getForObjectsGet(storageObject, requestOptions);
        Tuple<String, byte[]> result =
            runWithRetries(
                () -> storageRpc.read(storageObject, requestOptions, position, toRead),
                serviceOptions.getRetrySettings(),
                algorithm,
                serviceOptions.getClock());
        String etag = result.x();
        byte[] bytes = result.y();
        if (bytes.length > 0 && lastEtag != null && !Objects.equals(etag, lastEtag)) {
          throw new IOException("Blob " + blob + " was updated while reading");
        }
        lastEtag = etag;
        buffer = bytes;
      } catch (RetryHelper.RetryHelperException e) {
        throw new IOException(e);
      }
      if (toRead > buffer.length) {
        endOfStream = true;
        if (buffer.length == 0) {
          buffer = null;
          return -1;
        }
      }
    }
    int toWrite = Math.min(buffer.length - bufferPos, byteBuffer.remaining());
    byteBuffer.put(buffer, bufferPos, toWrite);
    bufferPos += toWrite;
    if (bufferPos >= buffer.length) {
      position += buffer.length;
      buffer = null;
      bufferPos = 0;
    }
    return toWrite;
  }

Specifically:

Tuple<String, byte[]> result =
            runWithRetries(
                () -> storageRpc.read(storageObject, requestOptions, position, toRead),
                serviceOptions.getRetrySettings(),
                algorithm,
                serviceOptions.getClock());

The underlying implementation is making multiple RPCs to do range-gets on a blob.
We might need to use an intermediate buffer to stop IOExceptions here from crashing the build.

tikurahul added a commit to tikurahul/gcp-gradle-build-cache that referenced this issue May 10, 2022
- Remove the need for retries.
- When writes fail, we fail silently and treat subsequent reads as a cache-miss.
- Set the `chunkSize` for reads to equal the size of the `Blob`.
  This way, we only make 1 RPC per blob input stream. This is safe because
  the size of the objects in cache are not very large.
liutikas added a commit that referenced this issue May 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants