0

I have the following class, none of the dependencies invoke Queueable:

public class TEST_SandboxDataFactory {
    public static void create() {
        List<SObjectType> sObjectTypeList
                = new List<SObjectType>(XFTY_DummySObjectFactoryOutletLookup.OUTLET_TYPE_BY_SOBJECT_TYPE.keySet());
        System.enqueueJob(new SandboxDataFactoryQueueable(sObjectTypeList));
    }

    private class SandboxDataFactoryQueueable implements Queueable {
        List<SObjectType> sObjectTypeList;

        private SandboxDataFactoryQueueable(List<SObjectType> sObjectTypeList) {
            this.sObjectTypeList = sObjectTypeList;
        }

        public void execute(QueueableContext param1) {
            SObjectType sObjectType = this.sObjectTypeList.remove(0);
            System.debug(LoggingLevel.INFO, 'Creating data for' + sObjectType);

            try {
                new XFTY_DummySObjectSupplier(sObjectType)
                        .setQuantityPerTemplate(10)
                        .setInclusivity(XFTY_InsertInclusivityEnum.ALL)
                        .setInsertMode(XFTY_InsertModeEnum.NOW_BEST_EFFORT)
                        .supplyBundle();
            }
            catch (Exception ex) {
                System.debug(LoggingLevel.ERROR, '!!! Failed to create data for ' + sObjectType);
            }

            if (!this.sObjectTypeList.isEmpty()) {
                System.debug(LoggingLevel.INFO, 'Next creation applied to' + this.sObjectTypeList[0]);
                System.enqueueJob(new SandboxDataFactoryQueueable(this.sObjectTypeList));
            }
            else {
                System.debug(LoggingLevel.INFO, '!!! Creation finished.');
            }
        }
    }
}

My expectation is that when I anonymously execute

TEST_SandboxDataFactory.create();

The system will:

  1. Enqueue an instance of SandboxDataFactoryQueueable.
  2. SandboxDataFactoryQueueable will then:
  3. Pop the first SObjectType out of List sObjectTypeList
  4. Create data for SObjectType
  5. If sObjectTypeList has remaining items, enqueue a new instance of SandboxDataFactoryQueueable
  6. Repeat 2 through 5 until the list is exhausted.

In actually, this will only create data for the first item (Account), and then fail with Too many queuable jobs added to the queue: 2

Further testing revealed that the following code works:

public with sharing class foo {
    public static void create() {
        List<SObjectType> sObjectTypeList
                = new List<SObjectType>(XFTY_DummySObjectFactoryOutletLookup.OUTLET_TYPE_BY_SOBJECT_TYPE.keySet());
        System.enqueueJob(new SandboxDataFactoryQueueable(sObjectTypeList));
    }

    private class SandboxDataFactoryQueueable implements Queueable {
        List<SObjectType> sObjectTypeList;

        private SandboxDataFactoryQueueable(List<SObjectType> sObjectTypeList) {
            this.sObjectTypeList = sObjectTypeList;
        }

        public void execute(QueueableContext param1) {
            SObjectType sObjectType = this.sObjectTypeList.remove(0);
            System.debug(LoggingLevel.INFO, 'Creating data for' + sObjectType);

            if (!this.sObjectTypeList.isEmpty()) {
                System.debug(LoggingLevel.INFO, 'Next creation applied to' + this.sObjectTypeList[0]);
                System.enqueueJob(new SandboxDataFactoryQueueable(this.sObjectTypeList));
            }
            else {
                System.debug(LoggingLevel.INFO, '!!! Creation finished.');
            }
        }
    }
}

... whereas the significant difference is in this bit:

    new XFTY_DummySObjectSupplier(sObjectType)
                .setQuantityPerTemplate(10)
                .setInclusivity(XFTY_InsertInclusivityEnum.ALL)
                .setInsertMode(XFTY_InsertModeEnum.NOW_BEST_EFFORT)
                .supplyBundle();

While this code doesn't directly enqueue anything, it will be firing many triggers, some of which could be enqueuing things.

Is there any way I can make this work?

Would it be possible to iterate over XFTY_DummySObjectFactoryOutletLookup.OUTLET_TYPE_BY_SOBJECT_TYPE.keySet() using a Batchable, with 1 item per batch?

  • 1
    Does XFTY_DummySObjectSupplier enqueue any jobs? Also, empty catch blocks are a horrid antipattern. – Adrian Larson Jul 26 '19 at 17:42
  • No, it doesn't and in this context, for the moment I'm after "best effort" so I don't care if about what it doesn't create, but I agree in general that would be a bad practice. – Brian Kessler Jul 26 '19 at 17:46
  • 1
    The only other explanation is that you have triggers on the objects being inserted, which also fire off Queueable jobs. That is a bit of an antipattern as well, but if you are creating a tool for distribution you have to account for it. Please note that sandbox chaining is limited to 5 jobs, rather than allowing indefinite chaining. You might be better off looking to Platform Events as the basis for your async framework here. – Adrian Larson Jul 26 '19 at 17:49
  • Sandbox chaining is limited to 5 jobs? That will definitely be a problem. :-( We'll have to check out platform events.... :-( – Brian Kessler Jul 26 '19 at 17:54
4

You can only enqueue one job from a job (be it Batchable or Queueable).

There are pretty much exactly two reasons you would run into this error. Either XFTY_DummySObjectSupplier kicks off a Queueable, or the object you are creating data for has triggers which do the same.

Regardless of which is the cause, you are going to hit a roadblock in that you can only chain to a depth of 5 in sandbox organizations. You will likely need to build this tool on top of Platform Events if you want it to be robust enough to handle these types of constraints.

Your approach would likely be quite similar, simply popping the first element out of the list for each execution. You could use JSON.serialize or simply newline join.

public class MyFactory
{
    public static void create()
    {
        List<String> objects = new List<String>(myKeySet());
        EventBus.publish(new MyFactoryEvent__e(
            Objects__c=String.join(objects, '\n');
        ));
    }
}

trigger on MyFactoryEvent__e(after insert)
{
    List<String> objects = trigger.new[0].Objects__c.split('\n');
    String object = objects.remove(0);

    // call builder

    if (!objects.isEmpty())
        EventBus.publish(new MyFactoryEvent__e(
            Objects__c=String.join(objects, '\n');
        ));
}
|improve this answer|||||
  • Can you please either post code or link to relevant information how I might do this with Platform events? – Brian Kessler Jul 26 '19 at 18:00
  • 2
    Your approach would barely change. I added a simple sketch of what it should look like. – Adrian Larson Jul 26 '19 at 18:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.