All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.picnic.errorprone.bugpatterns.FluxFlatMapUsage Maven / Gradle / Ivy

There is a newer version: 0.19.1
Show newest version
package tech.picnic.errorprone.bugpatterns;

import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.utils.MoreTypes.generic;
import static tech.picnic.errorprone.utils.MoreTypes.subOf;
import static tech.picnic.errorprone.utils.MoreTypes.type;
import static tech.picnic.errorprone.utils.MoreTypes.unbound;

import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MemberReferenceTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import java.util.function.Function;
import reactor.core.publisher.Flux;

/**
 * A {@link BugChecker} that flags usages of {@link Flux#flatMap(Function)} and {@link
 * Flux#flatMapSequential(Function)}.
 *
 * 

{@link Flux#flatMap(Function)} and {@link Flux#flatMapSequential(Function)} eagerly perform up * to {@link reactor.util.concurrent.Queues#SMALL_BUFFER_SIZE} subscriptions. Additionally, the * former interleaves values as they are emitted, yielding nondeterministic results. In most cases * {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and * avoids potentially saturating the thread pool on which subscription happens. If {@code * concatMap}'s sequential-subscription semantics are undesirable one should invoke a {@code * flatMap} or {@code flatMapSequential} overload with an explicit concurrency level. * *

NB: The rarely-used overload {@link Flux#flatMap(Function, Function, * java.util.function.Supplier)} is not flagged by this check because there is no clear alternative * to point to. */ @AutoService(BugChecker.class) @BugPattern( summary = """ `Flux#flatMap` and `Flux#flatMapSequential` have subtle semantics; please use \ `Flux#concatMap` or explicitly specify the desired amount of concurrency""", link = BUG_PATTERNS_BASE_URL + "FluxFlatMapUsage", linkType = CUSTOM, severity = ERROR, tags = LIKELY_ERROR) public final class FluxFlatMapUsage extends BugChecker implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher { private static final long serialVersionUID = 1L; private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY"; private static final Supplier FLUX = Suppliers.typeFromString("reactor.core.publisher.Flux"); private static final Matcher FLUX_FLATMAP = instanceMethod() .onDescendantOf(FLUX) .namedAnyOf("flatMap", "flatMapSequential") .withParameters(Function.class.getCanonicalName()); private static final Supplier FLUX_OF_PUBLISHERS = VisitorState.memoize( generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound())))); /** Instantiates a new {@link FluxFlatMapUsage} instance. */ public FluxFlatMapUsage() {} @Override public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { if (!FLUX_FLATMAP.matches(tree, state)) { return Description.NO_MATCH; } SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state); SuggestedFix concurrencyCapFix = SuggestedFix.postfixWith( Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME); Description.Builder description = buildDescription(tree); if (state.getTypes().isSubtype(ASTHelpers.getType(tree), FLUX_OF_PUBLISHERS.get(state))) { /* * Nested publishers may need to be subscribed to eagerly in order to avoid a deadlock, e.g. * if they are produced by `Flux#groupBy`. In this case we suggest specifying an explicit * concurrently bound, in favour of sequential subscriptions using `Flux#concatMap`. */ description.addFix(concurrencyCapFix).addFix(serializationFix); } else { description.addFix(serializationFix).addFix(concurrencyCapFix); } return description.build(); } @Override public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) { if (!FLUX_FLATMAP.matches(tree, state)) { return Description.NO_MATCH; } // Method references are expected to occur very infrequently; generating both variants of // suggested fixes is not worth the trouble. return describeMatch(tree); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy