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
Database.Batchable
when it also implements another intermediate interface. Hopefully this is helpful beyond grasping at straws :)Test.StartTest()
and StopTest()?