Skip to content

Commit bab179d

Browse files
authored
Merge pull request #23 from tikurahul/main
Bring back retries when downloading blobs from the Storage cache.
2 parents ade8c66 + 00d58e5 commit bab179d

File tree

6 files changed

+95
-20
lines changed

6 files changed

+95
-20
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ In your `settings.gradle.kts` file add the following
88

99
```kotlin
1010
plugins {
11-
id("androidx.build.gradle.gcpbuildcache") version "1.0.0-alpha04"
11+
id("androidx.build.gradle.gcpbuildcache") version "1.0.0-alpha05"
1212
}
1313

1414
import androidx.build.gradle.gcpbuildcache.GcpBuildCache
@@ -36,7 +36,7 @@ If you are using Groovy, then you should do the following:
3636

3737
```groovy
3838
plugins {
39-
id("androidx.build.gradle.gcpbuildcache") version "1.0.0-alpha04"
39+
id("androidx.build.gradle.gcpbuildcache") version "1.0.0-alpha05"
4040
}
4141
4242
import androidx.build.gradle.gcpbuildcache.GcpBuildCache

RELEASE-NOTES.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Release notes for GCP backed Gradle Remote Cache
22

3+
## 1.0.0-alpha05
4+
5+
- Downloads `Blob`s to an intermediate `Buffer` or a `File` depending on the size of the blob.
6+
- The underlying `FileHandleInputStream` gets cleaned up automatically after the `InputStream` is closed.
7+
- This way we can avoid flakes from the build cache, that is caused by intermittent `StorageException`s
8+
- Retry on fetches given they are being fetched to an intermediate location.
9+
310
## 1.0.0-alpha04
411

512
- Handles exceptions when fetching `BlobInfo`s and `ReadChannel`s from the storage service.

gcpbuildcache/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ gradlePlugin {
5454
}
5555

5656
group = "androidx.build.gradle.gcpbuildcache"
57-
version = "1.0.0-alpha04"
57+
version = "1.0.0-alpha05"
5858

5959
testing {
6060
suites {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2022 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package androidx.build.gradle.gcpbuildcache
19+
20+
import java.io.File
21+
import java.io.InputStream
22+
import java.nio.file.Files
23+
import java.nio.file.Path
24+
25+
/**
26+
* An input stream that keeps track of the file handles, so they can be cleaned up when the
27+
* [FileHandleInputStream] is closed.
28+
*/
29+
internal class FileHandleInputStream(private val file: File) : InputStream() {
30+
private val inputStream by lazy {
31+
file.inputStream()
32+
}
33+
34+
override fun read(): Int {
35+
return inputStream.read()
36+
}
37+
38+
override fun close() {
39+
super.close()
40+
try {
41+
inputStream.close()
42+
} finally {
43+
check(file.delete()) {
44+
"Unable to delete $file"
45+
}
46+
}
47+
}
48+
49+
companion object {
50+
fun Path.handleInputStream(): InputStream {
51+
return FileHandleInputStream(this.toFile())
52+
}
53+
54+
fun create(): Path {
55+
val timestamp = System.nanoTime()
56+
return Files.createTempFile("gradleCache", "$timestamp")
57+
}
58+
}
59+
}

gcpbuildcache/src/main/kotlin/androidx/build/gradle/gcpbuildcache/GcpStorageService.kt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717

1818
package androidx.build.gradle.gcpbuildcache
1919

20+
import androidx.build.gradle.gcpbuildcache.FileHandleInputStream.Companion.handleInputStream
2021
import com.google.api.gax.retrying.RetrySettings
2122
import com.google.auth.oauth2.GoogleCredentials
22-
import com.google.cloud.ReadChannel
2323
import com.google.cloud.http.HttpTransportOptions
2424
import com.google.cloud.storage.*
2525
import org.gradle.api.GradleException
2626
import org.gradle.api.logging.Logging
2727
import java.io.InputStream
28-
import java.nio.channels.Channels
2928

3029
/**
3130
* An implementation of the [StorageService] that is backed by Google Cloud Storage.
@@ -36,6 +35,7 @@ internal class GcpStorageService(
3635
gcpCredentials: GcpCredentials,
3736
override val isPush: Boolean,
3837
override val isEnabled: Boolean,
38+
private val sizeThreshold: Long = BLOB_SIZE_THRESHOLD
3939
) : StorageService {
4040

4141
private val storageOptions by lazy { storageOptions(projectId, gcpCredentials, isPush) }
@@ -48,8 +48,7 @@ internal class GcpStorageService(
4848

4949
val blobId = BlobId.of(bucketName, cacheKey)
5050
logger.info("Loading $cacheKey from ${blobId.name}")
51-
val readChannel = load(storageOptions, blobId) ?: return null
52-
return Channels.newInputStream(readChannel)
51+
return load(storageOptions, blobId, sizeThreshold)
5352
}
5453

5554
override fun store(cacheKey: String, contents: ByteArray): Boolean {
@@ -103,14 +102,19 @@ internal class GcpStorageService(
103102
// Need full control for updating metadata
104103
private const val STORAGE_FULL_CONTROL = "https://www.googleapis.com/auth/devstorage.full_control"
105104

106-
private fun load(storage: StorageOptions?, blobId: BlobId): ReadChannel? {
105+
private const val BLOB_SIZE_THRESHOLD = 50 * 1024 * 1024L
106+
107+
private fun load(storage: StorageOptions?, blobId: BlobId, sizeThreshold: Long): InputStream? {
107108
if (storage == null) return null
108109
return try {
109110
val blob = storage.service.get(blobId) ?: return null
110-
val reader = blob.reader()
111-
// We don't expect to store objects larger than Int.MAX_VALUE
112-
reader.setChunkSize(blob.size.toInt())
113-
reader
111+
return if (blob.size > sizeThreshold) {
112+
val path = FileHandleInputStream.create()
113+
blob.downloadTo(path)
114+
path.handleInputStream()
115+
} else {
116+
blob.getContent().inputStream()
117+
}
114118
} catch (storageException: StorageException) {
115119
logger.debug("Unable to load Blob ($blobId)", storageException)
116120
null
@@ -141,8 +145,7 @@ internal class GcpStorageService(
141145
): StorageOptions? {
142146
val credentials = credentials(gcpCredentials, isPushSupported) ?: return null
143147
val retrySettings = RetrySettings.newBuilder()
144-
// We don't want retries.
145-
retrySettings.maxAttempts = 0
148+
retrySettings.maxAttempts = 3
146149
return StorageOptions.newBuilder().setCredentials(credentials)
147150
.setStorageRetryStrategy(StorageRetryStrategy.getUniformStorageRetryStrategy()).setProjectId(projectId)
148151
.setRetrySettings(retrySettings.build())

gcpbuildcache/src/test/kotlin/androidx/build/gradle/gcpbuildcache/GcpStorageServiceTest.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class GcpStorageServiceTest {
3535
bucketName = BUCKET_NAME,
3636
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
3737
isPush = true,
38-
isEnabled = true
38+
isEnabled = true,
39+
sizeThreshold = 0L
3940
)
4041
storageService.use {
4142
val cacheKey = "test-store.txt"
@@ -54,7 +55,8 @@ class GcpStorageServiceTest {
5455
bucketName = BUCKET_NAME,
5556
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
5657
isPush = true,
57-
isEnabled = true
58+
isEnabled = true,
59+
sizeThreshold = 0L
5860
)
5961
storageService.use {
6062
val cacheKey = "test-load.txt"
@@ -76,7 +78,8 @@ class GcpStorageServiceTest {
7678
bucketName = BUCKET_NAME,
7779
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
7880
isPush = false,
79-
isEnabled = true
81+
isEnabled = true,
82+
sizeThreshold = 0L
8083
)
8184
storageService.use {
8285
val cacheKey = "test-store-no-push.txt"
@@ -94,14 +97,16 @@ class GcpStorageServiceTest {
9497
bucketName = BUCKET_NAME,
9598
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
9699
isPush = true,
97-
isEnabled = true
100+
isEnabled = true,
101+
sizeThreshold = 0L
98102
)
99103
val readOnlyStorageService = GcpStorageService(
100104
projectId = PROJECT_ID,
101105
bucketName = BUCKET_NAME,
102106
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath)),
103107
isPush = false,
104-
isEnabled = true
108+
isEnabled = true,
109+
sizeThreshold = 0L
105110
)
106111
storageService.use {
107112
readOnlyStorageService.use {
@@ -125,7 +130,8 @@ class GcpStorageServiceTest {
125130
bucketName = BUCKET_NAME,
126131
gcpCredentials = ExportedKeyGcpCredentials(File(serviceAccountPath!!)),
127132
isPush = true,
128-
isEnabled = false
133+
isEnabled = false,
134+
sizeThreshold = 0L
129135
)
130136
storageService.use {
131137
val cacheKey = "test-store-disabled.txt"

0 commit comments

Comments
 (0)