| // Copyright 2024 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.skyframe; | |
| import static com.google.common.base.Preconditions.checkNotNull; | |
| import com.google.devtools.build.lib.actions.ActionLookupData; | |
| import com.google.devtools.build.lib.actions.ActionLookupKey; | |
| import com.google.devtools.build.lib.actions.ActionLookupSummaryKey; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.skyframe.serialization.DependOnFutureShim.DefaultDependOnFutureShim; | |
| import com.google.devtools.build.lib.skyframe.serialization.DeserializedSkyValue; | |
| import com.google.devtools.build.lib.skyframe.serialization.SerializationException; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever.NoCachedData; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever.Restart; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever.RetrievalContext; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever.RetrievalResult; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever.RetrievedValue; | |
| import com.google.devtools.build.lib.skyframe.serialization.SkyValueRetriever.SerializableSkyKeyComputeState; | |
| import com.google.devtools.build.lib.skyframe.serialization.analysis.AnalysisValueWithMtsv; | |
| import com.google.devtools.build.lib.skyframe.serialization.analysis.RemoteAnalysisCacheClient; | |
| import com.google.devtools.build.lib.skyframe.serialization.analysis.RemoteAnalysisCacheReaderDepsProvider; | |
| import com.google.devtools.build.lib.skyframe.serialization.analysis.SkycacheUploadClient; | |
| import com.google.devtools.build.lib.skyframe.serialization.analysis.proto.MissReason; | |
| import com.google.devtools.build.skyframe.SkyFunction.Environment; | |
| import com.google.devtools.build.skyframe.SkyKey; | |
| import com.google.devtools.build.skyframe.SkyValue; | |
| import com.google.devtools.build.skyframe.Version; | |
| import java.util.function.Supplier; | |
| /** | |
| * A wrapper around {@link SkyValueRetriever} to handle Bazel-on-Skyframe specific logic, metrics | |
| * gathering, and error handling. | |
| */ | |
| public final class SkyValueRetrieverUtils { | |
| public static RetrievalResult retrieveRemoteSkyValue( | |
| SkyKey key, | |
| Environment env, | |
| RemoteAnalysisCacheReaderDepsProvider analysisCachingDeps, | |
| Supplier<? extends SerializableSkyKeyComputeState> stateSupplier) | |
| throws InterruptedException { | |
| if (env.inErrorBubbling()) { | |
| // Remote retrieval during error bubbling causes incorrect error propagation. See b/449016469. | |
| return new NoCachedData(MissReason.MISS_REASON_NOT_ATTEMPTED); | |
| } | |
| if (analysisCachingDeps.shouldBailOutOnMissingFingerprint()) { | |
| return new NoCachedData(MissReason.MISS_REASON_NOT_ATTEMPTED); | |
| } | |
| Label label = | |
| switch (key) { | |
| case ActionLookupKey alk -> alk.getLabel(); | |
| case ActionLookupData ald -> ald.getLabel(); | |
| case ActionLookupSummaryKey summaryKey -> summaryKey.argument().getLabel(); | |
| case Artifact artifact -> artifact.getOwnerLabel(); | |
| default -> throw new IllegalStateException("unexpected key: " + key.getCanonicalName()); | |
| }; | |
| if (label == null) { | |
| // If there's no label, there's no cached data. | |
| return new NoCachedData(MissReason.MISS_REASON_NOT_ATTEMPTED); | |
| } | |
| SkyValueRetriever retriever = analysisCachingDeps.getSkyValueRetriever(); | |
| RemoteAnalysisCacheClient client = analysisCachingDeps.getAnalysisCacheClient(); | |
| if (retriever == null || client == null) { | |
| return new NoCachedData(MissReason.MISS_REASON_NOT_ATTEMPTED); | |
| } | |
| RetrievalResult retrievalResult = null; | |
| RetrievalContext state = env.getState(stateSupplier).getRetrievalContext(); | |
| if (state.isInitialQuery()) { | |
| state.setStartTimestampNanos(System.nanoTime()); | |
| } | |
| try { | |
| retrievalResult = | |
| retriever.tryRetrieve(env, new DefaultDependOnFutureShim(env), client, key, state); | |
| retrievalResult = | |
| maybeUnwrapVersionedSkyValue(key, retrievalResult, env, analysisCachingDeps); | |
| if (retrievalResult instanceof RetrievedValue || retrievalResult instanceof NoCachedData) { | |
| analysisCachingDeps.recordRetrievalResult( | |
| retrievalResult, key, state.getPhaseDurationMicros()); | |
| } | |
| } catch (SerializationException e) { | |
| // Don't crash the build if deserialization failed. Gracefully fallback to local evaluation. | |
| analysisCachingDeps.recordSerializationException(e, key, state.getPhaseDurationMicros()); | |
| retrievalResult = new NoCachedData(e.getReason()); | |
| } finally { | |
| if (retrievalResult == Restart.RESTART) { | |
| state.addRestart(); | |
| } | |
| } | |
| if (retrievalResult instanceof RetrievedValue(SkyValue v) | |
| && !(v instanceof DeserializedSkyValue)) { | |
| throw new IllegalStateException( | |
| "deserialized SkyValue of type " | |
| + v.getClass().getCanonicalName() | |
| + " does not implement DeserializedSkyValue. Try using" | |
| + " @AutoCodec(deserializedInterface = DeserializedSkyValue.class)"); | |
| } | |
| return retrievalResult; | |
| } | |
| private static RetrievalResult maybeUnwrapVersionedSkyValue( | |
| SkyKey key, | |
| RetrievalResult retrievalResult, | |
| Environment env, | |
| RemoteAnalysisCacheReaderDepsProvider analysisCachingDeps) { | |
| if (!(key instanceof ActionLookupKey) | |
| || !(retrievalResult instanceof RetrievedValue(SkyValue value))) { | |
| return retrievalResult; | |
| } | |
| if (value instanceof AnalysisValueWithMtsv(SkyValue innerValue, Version mtsv)) { | |
| if (analysisCachingDeps.getSkycacheAnalysisOnly() | |
| && env.getMaxTransitiveSourceVersionSoFar() != null) { | |
| env.injectVersion(mtsv); | |
| } | |
| return new RetrievedValue(innerValue); | |
| } | |
| if (analysisCachingDeps.getSkycacheAnalysisOnly()) { | |
| return new NoCachedData(MissReason.MISS_REASON_MISSING_MTSV); | |
| } | |
| return retrievalResult; | |
| } | |
| public static void tryUploadAsync( | |
| RemoteAnalysisCacheReaderDepsProvider cachingDeps, | |
| SkyKey key, | |
| SkyValue value, | |
| Environment env) | |
| throws InterruptedException { | |
| if (!cachingDeps.mode().isAsyncUpload()) { | |
| return; | |
| } | |
| SkycacheUploadClient uploadClient = cachingDeps.getSkycacheUploadClient(); | |
| // TODO(b/527929697): Handle null uploadClient properly (e.g. fail the build cleanly). | |
| checkNotNull(uploadClient).tryUpload(key, value, env); | |
| } | |
| private SkyValueRetrieverUtils() {} | |
| } |