From a4cebe1c0d59e3e770b532348a4140f591b1a49f Mon Sep 17 00:00:00 2001 From: James Simone Date: Mon, 5 Jun 2023 17:09:05 -0400 Subject: [PATCH 1/4] Added guard check for daily org AsyncApexJob execution amount prior to going async --- extra-tests/classes/RollupTests.cls | 16 ++++++++++++++++ rollup/core/classes/RollupAsyncProcessor.cls | 11 +++++++++-- rollup/core/classes/RollupLimits.cls | 18 ++++++++++++++++++ sfdx-project.json | 4 ++-- 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/extra-tests/classes/RollupTests.cls b/extra-tests/classes/RollupTests.cls index 2acef87a..9f207cb0 100644 --- a/extra-tests/classes/RollupTests.cls +++ b/extra-tests/classes/RollupTests.cls @@ -2847,4 +2847,20 @@ private class RollupTests { mock.Records[0].get('Description') ); } + + @IsTest + static void fallsBackToRunningSyncWhenOutOfAsyncJobs() { + RollupTestUtils.DMLMock mock = RollupTestUtils.loadAccountIdMock( + new List{ new ContactPointAddress(Name = 'One', PreferenceRank = 50) } + ); + Rollup.apexContext = TriggerOperation.AFTER_INSERT; + RollupLimits.orgAsyncJobsUsed = OrgLimits.getMap().get('DailyAsyncApexExecutions').getLimit() + 1; + + // specifically no Test.startTest()/Test.stopTest() to prove it's been run sync + Rollup.sumFromApex(ContactPointAddress.PreferenceRank, ContactPointAddress.ParentId, Account.Id, Account.AnnualRevenue, Account.SObjectType) + .runCalc(); + + System.assertEquals(1, mock.Records.size()); + System.assertEquals(50, mock.Records[0].get(Account.AnnualRevenue)); + } } diff --git a/rollup/core/classes/RollupAsyncProcessor.cls b/rollup/core/classes/RollupAsyncProcessor.cls index bfb3dbb2..a33e288b 100644 --- a/rollup/core/classes/RollupAsyncProcessor.cls +++ b/rollup/core/classes/RollupAsyncProcessor.cls @@ -1058,8 +1058,9 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme } private Boolean ingestRollupControlData(List syncRollups) { - Boolean isRunningAsnyc = this.getIsRunningAsync(); + Boolean isRunningAsync = this.getIsRunningAsync(); Boolean shouldRollupsRunWithoutCustomSetting = false; + RollupLimits.Tester limitTester = new RollupLimits.Tester(this.rollupControl, isRunningAsync); for (Integer index = this.rollups.size() - 1; index >= 0; index--) { RollupAsyncProcessor rollup = this.rollups[index]; shouldRollupsRunWithoutCustomSetting = rollup.getCanRollupWithoutCustomSetting(); @@ -1068,8 +1069,14 @@ global virtual without sharing class RollupAsyncProcessor extends Rollup impleme Boolean shouldRunSyncDeferred = this.getShouldRunSyncDeferred(rollup); Boolean couldRunSync = rollup.rollupControl.ShouldRunAs__c == RollupMetaPicklists.ShouldRunAs.Synchronous || - (isRunningAsnyc && this.getIsTimingOut(rollup.rollupControl, rollup) == false) || + (isRunningAsync && this.getIsTimingOut(rollup.rollupControl, rollup) == false) || this.isSingleRecordSyncUpdate(rollup); + + if (limitTester.hasExceededOrgAsyncLimit()) { + couldRunSync = true; + shouldRunSyncDeferred = false; + } + String hashedRollupKey = rollup.getHashedContents() + rollup.calcItems?.hashCode() + rollup.oldCalcItems?.hashCode(); if (hashedRollups.contains(hashedRollupKey)) { this.rollups.remove(index); diff --git a/rollup/core/classes/RollupLimits.cls b/rollup/core/classes/RollupLimits.cls index a5bdc9af..d68c59a4 100644 --- a/rollup/core/classes/RollupLimits.cls +++ b/rollup/core/classes/RollupLimits.cls @@ -1,6 +1,8 @@ public without sharing class RollupLimits { @TestVisible private static Integer stubbedQueryRows; + @TestVisible + private static Integer orgAsyncJobsUsed; private static final Integer SYNC_TIMEOUT_INTERVAL_MS = 3000; private static final Integer ASYNC_TIMEOUT_INTERVAL_MS = 13000; @@ -8,6 +10,18 @@ public without sharing class RollupLimits { private static final Integer LIMIT_HEAP_SIZE = Limits.getLimitHeapSize(); private static final Integer LIMIT_QUERY_ROWS = 50000; + private static final Boolean HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT { + get { + if (HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT == null) { + System.OrgLimit asyncJobLimit = OrgLimits.getMap().get('DailyAsyncApexExecutions'); + Integer countOfJobsUsed = orgAsyncJobsUsed != null ? orgAsyncJobsUsed : asyncJobLimit.getValue(); + HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT = countOfJobsUsed - asyncJobLimit.getLimit() > 0; + } + return HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT; + } + set; + } + public class Tester { private final transient RollupControl__mdt control; private final transient Boolean isRunningAsync; @@ -61,5 +75,9 @@ public without sharing class RollupLimits { Integer remainingQueryRows = this.control.MaxQueryRows__c?.intValue() - queryRowsUsed; return remainingQueryRows > 0 ? remainingQueryRows : 0; } + + public Boolean hasExceededOrgAsyncLimit() { + return HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT; + } } } diff --git a/sfdx-project.json b/sfdx-project.json index 72887cd2..934dfffb 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -4,8 +4,8 @@ "default": true, "package": "apex-rollup", "path": "rollup", - "versionName": "Tech debt cleanup", - "versionNumber": "1.5.69.0", + "versionName": "Check org limits for AsyncApexJob execution prior to enqueueing/batching", + "versionNumber": "1.5.70.0", "versionDescription": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.", "releaseNotesUrl": "https://github.com/jamessimone/apex-rollup/releases/latest", "unpackagedMetadata": { From 25794ae8a031f5c41b3ec1ccef24aec9acdaea75 Mon Sep 17 00:00:00 2001 From: James Simone Date: Tue, 6 Jun 2023 10:36:38 -0400 Subject: [PATCH 2/4] Implementing workaround for packaging gack due to System.OrgLimits.getMap() call --- extra-tests/classes/RollupLimitsTest.cls | 7 +++++++ extra-tests/classes/RollupLimitsTest.cls-meta.xml | 5 +++++ rollup/core/classes/RollupLimits.cls | 15 +++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 extra-tests/classes/RollupLimitsTest.cls create mode 100644 extra-tests/classes/RollupLimitsTest.cls-meta.xml diff --git a/extra-tests/classes/RollupLimitsTest.cls b/extra-tests/classes/RollupLimitsTest.cls new file mode 100644 index 00000000..bd2cfcb3 --- /dev/null +++ b/extra-tests/classes/RollupLimitsTest.cls @@ -0,0 +1,7 @@ +@IsTest +private class RollupLimitsTest { + @IsTest + static void correctlyReferencesOrgLimits() { + System.assertEquals(false, new RollupLimits.Tester(RollupControl__mdt.getInstance('Org_Default'), false).hasExceededOrgAsyncLimit()); + } +} diff --git a/extra-tests/classes/RollupLimitsTest.cls-meta.xml b/extra-tests/classes/RollupLimitsTest.cls-meta.xml new file mode 100644 index 00000000..45cccbd4 --- /dev/null +++ b/extra-tests/classes/RollupLimitsTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 57.0 + Active + \ No newline at end of file diff --git a/rollup/core/classes/RollupLimits.cls b/rollup/core/classes/RollupLimits.cls index d68c59a4..1fe3ab9f 100644 --- a/rollup/core/classes/RollupLimits.cls +++ b/rollup/core/classes/RollupLimits.cls @@ -12,11 +12,18 @@ public without sharing class RollupLimits { private static final Boolean HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT { get { - if (HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT == null) { - System.OrgLimit asyncJobLimit = OrgLimits.getMap().get('DailyAsyncApexExecutions'); - Integer countOfJobsUsed = orgAsyncJobsUsed != null ? orgAsyncJobsUsed : asyncJobLimit.getValue(); - HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT = countOfJobsUsed - asyncJobLimit.getLimit() > 0; + Integer countOfJobsUsed = 0; + Integer asyncJobLimit = 250000; + // at the moment, packaging orgs run tests synchronously, which produces an error when fetching System.OrgLimits.getMap() + // this is a workaround until it's safe to access OrgLimits from a synchronously running test + if (HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT == null && Test.isRunningTest() == false) { + System.OrgLimit asyncLimit = System.OrgLimits.getMap().get('DailyAsyncApexExecutions'); + asyncJobLimit = asyncLimit.getLimit(); + countOfJobsUsed = asyncLimit.getValue(); + } else if (HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT == null && orgAsyncJobsUsed != null) { + countOfJobsUsed = orgAsyncJobsUsed; } + HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT = countOfJobsUsed - asyncJobLimit > 0; return HAS_EXCEEDED_ORG_ASYNC_JOB_LIMIT; } set; From faa7dc297c1172e932104c34d13b33b11e81e4b3 Mon Sep 17 00:00:00 2001 From: James Simone Date: Tue, 6 Jun 2023 11:08:53 -0400 Subject: [PATCH 3/4] Also removing OrgLimits reference from test class due to packaging... --- extra-tests/classes/RollupTests.cls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extra-tests/classes/RollupTests.cls b/extra-tests/classes/RollupTests.cls index 9f207cb0..7c003244 100644 --- a/extra-tests/classes/RollupTests.cls +++ b/extra-tests/classes/RollupTests.cls @@ -2854,11 +2854,11 @@ private class RollupTests { new List{ new ContactPointAddress(Name = 'One', PreferenceRank = 50) } ); Rollup.apexContext = TriggerOperation.AFTER_INSERT; - RollupLimits.orgAsyncJobsUsed = OrgLimits.getMap().get('DailyAsyncApexExecutions').getLimit() + 1; + // one over the default limit + RollupLimits.orgAsyncJobsUsed = 250001; // specifically no Test.startTest()/Test.stopTest() to prove it's been run sync - Rollup.sumFromApex(ContactPointAddress.PreferenceRank, ContactPointAddress.ParentId, Account.Id, Account.AnnualRevenue, Account.SObjectType) - .runCalc(); + Rollup.sumFromApex(ContactPointAddress.PreferenceRank, ContactPointAddress.ParentId, Account.Id, Account.AnnualRevenue, Account.SObjectType).runCalc(); System.assertEquals(1, mock.Records.size()); System.assertEquals(50, mock.Records[0].get(Account.AnnualRevenue)); From 327f8eb3b546641b860a2d61aac309b81c56c770 Mon Sep 17 00:00:00 2001 From: GitHub Action Bot Date: Tue, 6 Jun 2023 15:40:01 +0000 Subject: [PATCH 4/4] Bumping package version from Github Action --- README.md | 4 ++-- package.json | 2 +- rollup-namespaced/README.md | 4 ++-- rollup-namespaced/sfdx-project.json | 7 ++++--- rollup/core/classes/RollupLogger.cls | 2 +- sfdx-project.json | 3 ++- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 76bebed5..8100b0b4 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ As well, don't miss [the Wiki](../../wiki), which includes even more info for co ## Deployment & Setup - + Deploy to Salesforce - + Deploy to Salesforce Sandbox diff --git a/package.json b/package.json index 9a2b5bab..44714671 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apex-rollup", - "version": "1.5.69", + "version": "1.5.70", "description": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.", "repository": { "type": "git", diff --git a/rollup-namespaced/README.md b/rollup-namespaced/README.md index edf815b0..76ea0dcb 100644 --- a/rollup-namespaced/README.md +++ b/rollup-namespaced/README.md @@ -18,12 +18,12 @@ For more info, see the base `README`. ## Deployment & Setup - + Deploy to Salesforce - + Deploy to Salesforce Sandbox diff --git a/rollup-namespaced/sfdx-project.json b/rollup-namespaced/sfdx-project.json index 0d58a07d..4a6ba4b7 100644 --- a/rollup-namespaced/sfdx-project.json +++ b/rollup-namespaced/sfdx-project.json @@ -4,8 +4,8 @@ "default": true, "package": "apex-rollup-namespaced", "path": "rollup-namespaced/source/rollup", - "versionName": "Tech debt cleanup", - "versionNumber": "1.0.42.0", + "versionName": "Check org limits for AsyncApexJob execution prior to enqueueing/batching", + "versionNumber": "1.0.43.0", "versionDescription": "Fast, configurable, elastically scaling custom rollup solution. Apex Invocable action, one-liner Apex trigger/CMDT-driven logic, and scheduled Apex-ready.", "releaseNotesUrl": "https://github.com/jamessimone/apex-rollup/releases/latest", "unpackagedMetadata": { @@ -36,6 +36,7 @@ "apex-rollup-namespaced@1.0.39-0": "04t6g000008SneMAAS", "apex-rollup-namespaced@1.0.40-0": "04t6g000008SnebAAC", "apex-rollup-namespaced@1.0.41-0": "04t6g000008SnldAAC", - "apex-rollup-namespaced@1.0.42-0": "04t6g000008SnlsAAC" + "apex-rollup-namespaced@1.0.42-0": "04t6g000008SnlsAAC", + "apex-rollup-namespaced@1.0.43-0": "04t6g000008SnrcAAC" } } \ No newline at end of file diff --git a/rollup/core/classes/RollupLogger.cls b/rollup/core/classes/RollupLogger.cls index 26106130..d03d42c3 100644 --- a/rollup/core/classes/RollupLogger.cls +++ b/rollup/core/classes/RollupLogger.cls @@ -1,7 +1,7 @@ global without sharing virtual class RollupLogger implements ILogger { @TestVisible // this gets updated via the pipeline as the version number gets incremented - private static final String CURRENT_VERSION_NUMBER = 'v1.5.69'; + private static final String CURRENT_VERSION_NUMBER = 'v1.5.70'; private static final LoggingLevel FALLBACK_LOGGING_LEVEL = LoggingLevel.DEBUG; private static final RollupPlugin PLUGIN = new RollupPlugin(); diff --git a/sfdx-project.json b/sfdx-project.json index 934dfffb..674bd879 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -106,6 +106,7 @@ "apex-rollup@1.5.66-0": "04t6g000008SneHAAS", "apex-rollup@1.5.67-0": "04t6g000008SneWAAS", "apex-rollup@1.5.68-0": "04t6g000008SnlYAAS", - "apex-rollup@1.5.69-0": "04t6g000008SnlnAAC" + "apex-rollup@1.5.69-0": "04t6g000008SnlnAAC", + "apex-rollup@1.5.70-0": "04t6g000008SnrXAAS" } } \ No newline at end of file