I have an anonymous Apex scheduled job that runs every three hours on the hour:
String cron = '0 00 07,10,13,16,19,22 * * ? 2015';
MyScheduledDispatcher sd = new MyScheduledDispatcher();
System.schedule('My Job 07-10-13-16-19-22h everyDay 2015',cron,sd);
Above job runs under user: automatedJobUser
, System Admin profile
When I deploy via Changeset new code that is invoked by the schedulable class MyScheduledDispatcher
(in my use case, a batchable class), I observe the following:
- The new code is ignored on all future scheduled runs (!?!).
- The new code is executed if I run the schedule under a different user (that is, launch a new schedule).
- The new code is executed if I cancel the job and reschedule under
automatedJobuser
OBSERVATION: The scheduled job is bound to the last version of the code prior to creation of the schedule. Updating the code via a changeset has no effect
Q1 - Is this because I'm using the Appleman pattern that doesn't have a compile-time reference to the changed-by-deployment batchable class - and SFDC doesn't think a runtime recompile is required?
Q2 - Is there a better pattern to use given the code below so I can deploy at will and know that the next scheduled job instance will use the updated code?
I have Deployment options set as:
Yes, I can read and note Enabling this option may cause Apex jobs to fail
but I interpreted this as "code would be swapped in in the middle of a series of batches and results would be unpredictable"
It makes no sense to me that in one context, SFDC is executing prior versions of the Apex code for running schedules while in all other transactions, the new version of the code is used.
Schedulable class defines an interface and execute()
follows the Appleman pattern for decoupling the batchable from the schedulable:
public MyScheduledDispatcher() {
MyParms__c p = [select id, query__c, scope_Size__c from MyParms__c][0];
}
public Interface IScheduleDispatched {
void execute(SchedulableContext sc);
}
public void execute(SchedulableContext sc) {
Type schedHandlerType = Type.forName('MyBatchable'); // get Type for the desired ScheduleHandler class - this is the decoupling trick
if(schedHandlerType != null) {
IScheduleDispatched obj = (IScheduleDispatched)schedHandlerType.NewInstance(); // create instance of the ScheduleHandler but cast as interface IScheduleDispatched
obj.execute(sc); // invoke the execute() method on the interface object - which will start the batch
}
}
Batchable class (the one that was changed by deployment) looks like:
public with sharing class MyBatchable
implements Database.Batchable<Opportunity>, Database.Stateful, Database.AllowsCallouts,
Iterable<Opportunity>, Iterator<Opportunity>,
MySchedulable.IScheduleDispatched {
public MyBatchable() { // do stuff }
public void execute(SchedulableContext sc) { // starts batch
ID batchProcessId = Database.executeBatch(this,this.scopeSize);
}
public Iterator<Opportunity> iterator() {return this;}
public Boolean hasNext() { // do stuff }
public Opportunity next() { // do stuff }
public Iterable<Opportunity> start(Database.BatchableContext BC){
// do stuff }
public void execute(Database.BatchableContext bc, List<sObject> scope) {
// do useful stuff }
public void finish(Database.BatchableContext bc) { // do stuff }
Update:
This is going to take some more experimentation with alternatives -- otherwise I'm going to have to delete and restart the schedule on every code change (not good, as I'll forget something some day)