30

We're trying to implement dynamic batch processes to dispatch different classes. Specifically, we have integrated multiple systems, and depending on certain events, we dispatch callouts to those systems.

We have a simple object that remembers the name of the integration to call and the record to integrate with, and custom settings to determine how many records to process at once, etc. There is a scheduler that polls the database every few minutes and calls the appropriate class, which must implement a particular interface.

Now, we're adding a new interface to accommodate larger data volumes, and here's where the problems start.

The original interface looks like this:

public interface ClassicIntegration {
    boolean initialize();
    void execute(List<List<Queued_Item__c>> items, String method);
    void finish();
}

Traditionally, the classes that use this interface return true if they initialize okay (they never fail at this point), and then dispatch the data inside execute() to a future method, and have empty finish() methods.

Those calls end up executing immediately after the scheduler performs its work, and everything is done within seconds.

However, there's an inherent limit with this system; we could only process a handful of records at a time, because of the 10 future call limit and the 10 callout limit; loading 10,000 records takes over 3 hours. QA once tried to import 450,000 records, only to find out it'd take a week to deliver all the messages. This led to a design for a new interface. The primary benefit of the new interface was to have the class be batchable, so I wrote it like this:

public interface BatchIntegration extends Database.Batchable<Queued_Item__c> {
    void initialize(List<List<Queued_Item__c>> items, String method);
}

The signature was meant to be nearly the same to preserve as much of the original code as possible (because the other classes wouldn't be migrated immediately).

However, at this point, I'm running into a problem. I'm calling the batch like this:

BatchIntegration impl = (BatchIntegration)Type.forName(implClass).newInstance();
impl.initialize(queueItems, actionMethod);
Database.executeBatch(impl, batchSize);

This batch is assigned a Job ID, which I can verify in debug statements, and appears in the queue, which I can verify under Apex Jobs. However, it remains in the Queued status and never even attempts to start, while in the meantime, dozens of other future methods, scheduled classes, and even different batches (I created a test class that simply generates random accounts), and all of them run by just fine.

I can't seem to find any documentation about possible bugs, but surely this can't be right? Are we "allowed" to extend interfaces? Or does the system have some limitation regarding this that's simply not documented? The code compiles, and it even runs, as evidenced by the results, but the queued item itself won't execute.

Does anyone have any further information on this, or should I attempt to log this as a bug?

Edit: Changing the interface to not extend the system interface, and moving it to the class fixes this behavior, but that complicates the code, because it's no longer obvious when writing a new class from the interface that the class must also be batchable, nor can I "force" a particular data type, etc. It's no longer self-describing, and therefore complicates future maintenance. I'm more looking for a "this is a known issue" answer, not a workaround.

Further Testing

Interface

public interface FauxBatch extends Database.Batchable<Account> {
    void initialize(Account[] data);
}

Implementation

public class FauxBatchImpl implements FauxBatch, Iterable<Account>, Iterator<Account> {
    account[] accounts;

    public Iterable<Account> start(Database.BatchableContext bc) {
        return this;
    }
    public boolean hasNext() {
        return !accounts.isEmpty();
    }
    public iterator<account> iterator() {
        return this;
    }
    public account next() {
        return accounts.remove(0);
    }
    public void execute(database.batchablecontext bc, account[] records) {

    }
    public void finish(database.batchablecontext bc) {

    }
    public void initialize(Account[] records) {
        accounts = records;
    }
}

Execute Anonymous

FauxBatch b = new FauxBatchImpl();
b.initialize([SELECT Id, Name FROM Account]);
Database.executeBatch(b);

Logs From Execution

30.0 APEX_CODE,ERROR;APEX_PROFILING,FINEST;CALLOUT,ERROR;DB,ERROR;SYSTEM,ERROR;VALIDATION,ERROR;VISUALFORCE,ERROR;WORKFLOW,ERROR
Execute Anonymous: FauxBatch b = new FauxBatchImpl();
Execute Anonymous: b.initialize([SELECT Id, Name FROM Account]);
Execute Anonymous: Database.executeBatch(b);
20:26:24.140 (140607767)|EXECUTION_STARTED
20:26:24.140 (140617897)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
20:26:24.326 (326375736)|FATAL_ERROR|Internal Salesforce.com Error
20:26:24.220 (326531141)|CUMULATIVE_LIMIT_USAGE
20:26:24.220|LIMIT_USAGE_FOR_NS|(default)|
  Number of SOQL queries: 1 out of 100
  Number of query rows: 23 out of 50000
  Number of SOSL queries: 0 out of 20
  Number of DML statements: 0 out of 150
  Number of DML rows: 0 out of 10000
  Maximum CPU time: 0 out of 10000
  Maximum heap size: 0 out of 6000000
  Number of callouts: 0 out of 10
  Number of Email Invocations: 0 out of 10
  Number of fields describes: 0 out of 100
  Number of record type describes: 0 out of 100
  Number of child relationships describes: 0 out of 100
  Number of picklist describes: 0 out of 100
  Number of future calls: 0 out of 10

20:26:24.220|TOTAL_EMAIL_RECIPIENTS_QUEUED|0
20:26:24.220|STATIC_VARIABLE_LIST|
  FauxBatch:_static_b:0

20:26:24.220|CUMULATIVE_LIMIT_USAGE_END

20:26:24.326 (326675319)|CODE_UNIT_FINISHED|execute_anonymous_apex
20:26:24.326 (326685358)|EXECUTION_FINISHED

If I change executeBatch to scheduleBatch, the job shows up in the queue, but never executes; I have to abort it.

Test Method

Next, I created a test method for the class/interface.

@isTest(SeeAllData=true)
public class FauxBatchImplTest {
    static testMethod void test() {
        FauxBatch b = new FauxBatchImpl();
        b.initialize([select id, name from account]);
        Test.startTest();
        Database.executeBatch(b);
        Test.stopTest();
    }
}

Results:

Time Started    7/16/2014 8:38 PM
Class           FauxBatchImplTest
Method Name     test
Pass/Fail       Fail
Error Message   Internal Salesforce Error: 411786643-886372 (-666072638) (-666072638)
Stack Trace  
  • 1
    The Database.Batchable interface also defines execute and finish methods. Admittedly they have a different signature/parameters, so you wouldn't think there would be an issue there. Might be worth an experiment to see if there is something going wrong. – Daniel Ballinger Jul 17 '14 at 0:26
  • 1
    My other thought would be to define a start method in the BatchIntegration class to see if it is getting started and then failing to execute. Maybe there is something broken in the way the batch process creates instances to run. I.e. a problem with checking if the class is an instance of Database.Batchable when it also implements another intermediate interface. Hopefully this is helpful beyond grasping at straws :) – Daniel Ballinger Jul 17 '14 at 0:32
  • 1
    @DanielBallinger Yes, and the new interface has only one method, initialize, which was intentional to avoid any conflicts. The original purpose of execute and finish was to simulate a batch while not actually being one. The new interface makes no pretense of emulation, as each class must be a proper batchable class in order to execute (it'll cause an exception that will terminate the process gracefully otherwise). – sfdcfox Jul 17 '14 at 0:35
  • 1
    @DanielBallinger The called class fully implements database.batchable, because it wouldn't compile without. The scheduled class actually calls the execute batch method, so the implementing class doesn't need to concern itself about how it's called. The debug logs never indicate that the batch class' start method is ever called. – sfdcfox Jul 17 '14 at 0:40
  • Does the batch job execute in a testing context when using Test.StartTest() and StopTest()? – Daniel Ballinger Jul 17 '14 at 1:07
2

The fact that the failure is described as an Internal Salesforce Error with a code virtually guarantees that some sort of Salesforce bug is involved. If you haven't already done so you should submit a case.

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.