4444import com .google .devtools .build .lib .actions .FileValue ;
4545import com .google .devtools .build .lib .analysis .ConfiguredRuleClassProvider .BundledFileSystem ;
4646import com .google .devtools .build .lib .compress .CompressionService ;
47+ import com .google .devtools .build .lib .concurrent .QuiescingFutureTask ;
4748import com .google .devtools .build .lib .concurrent .safeexecutor .SafeExecutor ;
4849import com .google .devtools .build .lib .concurrent .safeexecutor .SafeFutures ;
4950import com .google .devtools .build .lib .profiler .CounterSeriesCollector ;
8889import com .google .devtools .build .lib .vfs .RootedPath ;
8990import com .google .devtools .build .skyframe .InMemoryGraph ;
9091import com .google .devtools .build .skyframe .InMemoryNodeEntry ;
92+ import com .google .errorprone .annotations .DoNotCall ;
9193import com .google .protobuf .CodedOutputStream ;
9294import java .io .ByteArrayOutputStream ;
9395import java .io .IOException ;
@@ -793,50 +795,9 @@ public ListenableFuture<ListingDataInfo> apply(FileDataInfo info) {
793795 NodeDataInfoOrFuture populateFutureNodeDataInfo (FutureNodeDataInfo future ) {
794796 counters .nodesWaitingForDeps .incrementAndGet ();
795797
796- AbstractNestedFileOpNodes node = future .key ();
797- var dependencyHandler = new NodeDependencyHandler ();
798-
799- // Loops through all node dependencies, registering them with the dependencyHandler. The
800- // dependencyHandler triggers recursive registration, keeping track of immediate results and
801- // any futures.
802- for (int i = 0 ; i < node .analysisDependenciesCount (); i ++) {
803- switch (node .getAnalysisDependency (i )) {
804- case FileKey fileKey :
805- dependencyHandler .addFileKey (fileKey );
806- break ;
807- case DirectoryListingKey listingKey :
808- dependencyHandler .addListingKey (listingKey );
809- break ;
810- case AbstractNestedFileOpNodes nestedKeys :
811- dependencyHandler .addNodeKey (nestedKeys );
812- break ;
813- case RemoteFileOpNode remoteNode :
814- dependencyHandler .addRemoteNode (remoteNode );
815- break ;
816- }
817- }
818-
819- switch (node ) {
820- case NestedFileOpNodes plainNodes :
821- break ;
822- case NestedFileOpNodesWithSource withSource :
823- dependencyHandler .setSourceFile (withSource .source ());
824- break ;
825- }
826-
827- var allFutures = dependencyHandler .getCombinedFutures ();
828- if (allFutures .isEmpty ()) {
829- NodeDataInfo result ;
830- try {
831- result = dependencyHandler .call ();
832- } catch (ExecutionException | IOException e ) {
833- // Only thrown when calling Future.get, but none should be present if this is reached.
834- throw new IllegalStateException ("unexpected failure" , e );
835- }
836- return future .completeWith (result );
837- }
838- return future .completeWith (
839- SafeFutures .call (Futures .whenAllSucceed (allFutures ), dependencyHandler , executor ));
798+ var task = new NodeDependencyHandler (future .key ());
799+ task .run ();
800+ return future .completeWith (task );
840801 }
841802
842803 OutputStream getCompressedOutputStream (OutputStream outputStream ) throws IOException {
@@ -849,12 +810,15 @@ OutputStream getCompressedOutputStream(OutputStream outputStream) throws IOExcep
849810
850811 /**
851812 * Accepts all the dependencies associated with a node, registers their serialization and waits
852- * for processing to complete, signalled through the {@link #call} callback .
813+ * for processing to complete.
853814 *
854815 * <p>Once processing is complete and all keys are known, uploads the node value. {@link
855816 * #computeNodeBytes} defines the wire format of nodes.
856817 */
857- class NodeDependencyHandler implements Callable <NodeDataInfo > {
818+ private final class NodeDependencyHandler extends QuiescingFutureTask <NodeDataInfo >
819+ implements FutureCallback <Object > {
820+ private final AbstractNestedFileOpNodes node ;
821+
858822 private final ArrayList <String > fileKeys = new ArrayList <>();
859823 private final ArrayList <String > listingKeys = new ArrayList <>();
860824 private final ArrayList <NodeInvalidationDataInfo > nodeDependencies = new ArrayList <>();
@@ -866,21 +830,54 @@ class NodeDependencyHandler implements Callable<NodeDataInfo> {
866830 private final ArrayList <FutureListingDataInfo > futureListingDataInfo = new ArrayList <>();
867831 private final ArrayList <FutureNodeDataInfo > futureNodeDataInfo = new ArrayList <>();
868832
833+ private NodeDependencyHandler (AbstractNestedFileOpNodes node ) {
834+ super (executor );
835+ this .node = node ;
836+ }
837+
869838 @ Override
870- public NodeDataInfo call () throws ExecutionException , IOException {
871- for (FutureFileDataInfo futureInfo : futureFileDataInfo ) {
872- addFileInfo (Futures .getDone (futureInfo ));
839+ protected void arrangeSubtasks () {
840+ // Loops through all node dependencies, registering them with this handler. This triggers
841+ // recursive registration, keeping track of immediate results and any futures.
842+ for (int i = 0 ; i < node .analysisDependenciesCount (); i ++) {
843+ switch (node .getAnalysisDependency (i )) {
844+ case FileKey fileKey -> addFileKey (fileKey );
845+ case DirectoryListingKey listingKey -> addListingKey (listingKey );
846+ case AbstractNestedFileOpNodes nestedKeys -> addNodeKey (nestedKeys );
847+ case RemoteFileOpNode remoteNode -> addRemoteNode (remoteNode );
848+ }
873849 }
874- for (FutureListingDataInfo futureInfo : futureListingDataInfo ) {
875- addListingInfo (Futures .getDone (futureInfo ));
850+
851+ switch (node ) {
852+ case NestedFileOpNodes plainNodes -> {}
853+ case NestedFileOpNodesWithSource withSource -> setSourceFile (withSource .source ());
876854 }
877- for (FutureNodeDataInfo futureInfo : futureNodeDataInfo ) {
878- addNodeInfo (Futures .getDone (futureInfo ));
855+ }
856+
857+ @ Override
858+ protected NodeDataInfo getValue () {
859+ @ Nullable String sourceFileKey ;
860+ try {
861+ for (FutureFileDataInfo futureInfo : futureFileDataInfo ) {
862+ addFileInfo (Futures .getDone (futureInfo ));
863+ }
864+ for (FutureListingDataInfo futureInfo : futureListingDataInfo ) {
865+ addListingInfo (Futures .getDone (futureInfo ));
866+ }
867+ for (FutureNodeDataInfo futureInfo : futureNodeDataInfo ) {
868+ addNodeInfo (Futures .getDone (futureInfo ));
869+ }
870+ sourceFileKey = getSourceFileKey ();
871+ } catch (ExecutionException e ) {
872+ // The QuiescingFutureTask setup guarantees that ExecutionException will not be thrown if
873+ // getValue is called.
874+ throw new AssertionError ("unexpected failure" , e );
879875 }
880- @ Nullable String sourceFileKey = getSourceFileKey ();
881876
882877 if (fileKeys .isEmpty () && listingKeys .isEmpty () && sourceFileKey == null ) {
878+ // TODO(b/558805781): investigate whether this is always dead code in practice
883879 if (nodeDependencies .isEmpty ()) {
880+ counters .nodesWaitingForDeps .decrementAndGet ();
884881 return CONSTANT_NODE ; // None of the dependencies are relevant to invalidation.
885882 }
886883 // There are multiple ways that result could become unary here, even if `node` always has at
@@ -893,6 +890,7 @@ public NodeDataInfo call() throws ExecutionException, IOException {
893890 //
894891 // TODO: b/364831651 - consider additional special casing for unary file or listing
895892 // dependencies.
893+ counters .nodesWaitingForDeps .decrementAndGet ();
896894 return nodeDependencies .get (0 );
897895 }
898896 }
@@ -956,6 +954,13 @@ public NodeDataInfo call() throws ExecutionException, IOException {
956954 return new NodeInvalidationDataInfo (key , writeStatusBuilder .build ());
957955 }
958956
957+ @ Override
958+ protected void doneWithError (
959+ @ Nullable Throwable primaryCause , ImmutableList <Throwable > secondaryCauses ) {
960+ counters .nodesWaitingForDeps .decrementAndGet ();
961+ counters .nodesWithProcessingErrors .incrementAndGet ();
962+ }
963+
959964 private void addRemoteNode (RemoteFileOpNode remoteNode ) {
960965 addNodeInfo (registerDependency (remoteNode ));
961966 }
@@ -967,6 +972,7 @@ private void addFileKey(FileKey fileKey) {
967972 break ;
968973 case FutureFileDataInfo futureInfo :
969974 futureFileDataInfo .add (futureInfo );
975+ trackFuture (futureInfo );
970976 break ;
971977 }
972978 }
@@ -989,6 +995,7 @@ private void addListingKey(DirectoryListingKey listingKey) {
989995 break ;
990996 case FutureListingDataInfo futureInfo :
991997 futureListingDataInfo .add (futureInfo );
998+ trackFuture (futureInfo );
992999 break ;
9931000 }
9941001 }
@@ -1011,6 +1018,7 @@ private void addNodeKey(AbstractNestedFileOpNodes nestedKeys) {
10111018 break ;
10121019 case FutureNodeDataInfo futureInfo :
10131020 futureNodeDataInfo .add (futureInfo );
1021+ trackFuture (futureInfo );
10141022 break ;
10151023 }
10161024 }
@@ -1033,28 +1041,39 @@ private void setSourceFile(FileKey sourceFile) {
10331041 sourceFile ,
10341042 sourceFileOrFuture );
10351043 this .sourceFileOrFuture = registerDependency (sourceFile );
1036- }
1037-
1038- private ImmutableList <ListenableFuture <?>> getCombinedFutures () {
1039- var combined =
1040- ImmutableList .<ListenableFuture <?>>builder ()
1041- .addAll (futureFileDataInfo )
1042- .addAll (futureListingDataInfo )
1043- .addAll (futureNodeDataInfo );
10441044 switch (sourceFileOrFuture ) {
1045- case null -> {}
1046- case FileDataInfo unusedSource -> {}
1047- case FutureFileDataInfo futureSource -> combined .add (futureSource );
1045+ case FileDataInfo immediateSource -> {}
1046+ case FutureFileDataInfo futureSource -> trackFuture (futureSource );
10481047 }
1049- return combined .build ();
10501048 }
10511049
1052- private byte [] compressBytes (byte [] nodeBytes ) throws IOException {
1050+ private void trackFuture (ListenableFuture <?> future ) {
1051+ increment ();
1052+ Futures .addCallback (future , this , directExecutor ());
1053+ }
1054+
1055+ @ Override
1056+ @ DoNotCall ("Only called via trackFuture" )
1057+ public void onSuccess (Object ignored ) {
1058+ decrement ();
1059+ }
1060+
1061+ @ Override
1062+ @ DoNotCall ("Only called via trackFuture" )
1063+ public void onFailure (Throwable t ) {
1064+ notifyException (t );
1065+ }
1066+
1067+ private byte [] compressBytes (byte [] nodeBytes ) {
10531068 ByteArrayOutputStream outputStream = new ByteArrayOutputStream ();
1054- MagicBytes .writeMagicBytes (outputStream );
1055- try (OutputStream compressedBytesStream =
1056- compressionService .newZstdOutputStream (outputStream )) {
1057- compressedBytesStream .write (nodeBytes );
1069+ try {
1070+ MagicBytes .writeMagicBytes (outputStream );
1071+ try (OutputStream compressedBytesStream =
1072+ compressionService .newZstdOutputStream (outputStream )) {
1073+ compressedBytesStream .write (nodeBytes );
1074+ }
1075+ } catch (IOException e ) {
1076+ throw new AssertionError ("Unexpected IOException during in-memory compression" , e );
10581077 }
10591078 return outputStream .toByteArray ();
10601079 }
0 commit comments