The reported scenario has been identified as an issue with how limits are enforced for future recursion. Our product development team(all thanks to @DanielBallinger ) has filed a bug #W-11247010 for this behaviour.
According to the filed bug, Limits.getLimitFutureCalls()
is incorrectly returning 50 when called in a future method. Ideally, it should return 0 when called from a future method.
It indicates that a future method could be invoked. However, attempting to do so results in an System.AsyncException with the message:
Future method cannot be called from a future or batch method
The exception is expected here, but the Limits class is returning a misleading value.
In contrast, the Limits.getLimitQueueableJobs() method returns the expected value of 1.
One plus side of this is that the future method itself can't be used to fan out the volume of async jobs. Only the Queueable to Future step permits that.
The below example future method that shows Limits.getLimitFutureCalls() returns 50 when System.isFuture() is true. The value should actually be 0 (zero) in this case.
// FutureTest.future1('base', 0);
public class FutureTest {
@future
public static void future1(string baseName, integer depth) {
System.debug('Future 1 [' + baseName + '] Depth: ' + depth);
System.debug('FutureCalls: ' + Limits.getFutureCalls() + '/' + Limits.getLimitFutureCalls() );
System.debug('System.isFuture: ' + System.isFuture() );
if(Limits.getLimitFutureCalls() > 0 && Limits.getFutureCalls() < Limits.getLimitFutureCalls()) {
// This should be impossible from a Future method!
// Throws Future method cannot be called from a future or batch method:...
FutureTest.future1(baseName + ' > Future!1_' + depth, depth + 1);
}
}
}
We have also filed a bug #W-11261710 that Queueable execution can start 50 child @Future jobs.
Known issue link: trailblazer.salesforce.com/issues_view?id=a1p4V00000214kJQAQ