how does chained scheduled tasks work in coldfusio...
# cfml-general
s
how does chained scheduled tasks work in coldfusion, how can i setup a parent and the chained scheduled tasks in cf through programming
t
specifically, it looks like you need the
onComplete
attribute to set up chained tasks.
s
i looked at it, but i am looking for how chaining wors
ok, so i have 6 scheduled tasks to run, one being as parent and others need to triggered one after the another, in that case what will i be doing, trying to see if i can use the cfschedule to create tasks in one go, i will add the names in the list and loop over the list and it will create the schedule task, i can create a code which i have in mind so we can try
t
At a guess,
Copy code
cfschedule(action="create", task="child5", url="path/to/a/cfm");
cfschedule(action="create", task="child4", url="path/to/a/cfm" onComplete="child5");
cfschedule(action="create", task="child3", url="path/to/a/cfm" onComplete="child4");
cfschedule(action="create", task="child2", url="path/to/a/cfm" onComplete="child3");
cfschedule(action="create", task="child1", url="path/to/a/cfm" onComplete="child2");
cfschedule(action="create", task="parent", url="path/to/a/cfm" onComplete="child1" cron="0 0 0 * * ?");
not sure if a task needs to be defined before it's used in
onComplete
, but just in case, that's why I defined them backwards.
s
this is my try
Copy code
<cfset scheduledFiles = ["file1.cfm", "file2.cfm", "file3.cfm", "file4.cfm", "file5.cfm", "file6.cfm", "file7.cfm", "file8.cfm", "file9.cfm", "file10.cfm"]>

<cfset startDate = now()>

<cfloop index="i" from="1" to="10">
    <cfset taskName = "Task" & i>
    <cfset scheduledFile = scheduledFiles[i]>
    
    <cfscript>
        taskService = createObject("java", "coldfusion.server.ServiceFactory").getTaskService();
        taskService.createTask(taskName, scheduledFile, "0 0 */1 * * ?", startDate);
    </cfscript>
</cfloop>
now 1 want file1.cfm should run on oncomplete and run others one after another
t
I'm not familiar with ServiceFactory, so I don't know what options that has... but using cfschedule instead...
Copy code
<cfscript>
    scheduledFiles = ["file1.cfm", "file2.cfm", "file3.cfm", "file4.cfm", "file5.cfm", "file6.cfm", "file7.cfm", "file8.cfm", "file9.cfm", "file10.cfm"];

startDate = now();

for(i = 1; i <= scheduledFiles.len(); i++) {
    attributeColletion = {
        action: "create",
        task: "Task#i#,
        url: scheduledFiles[i],
        startDate: startDate
    };
    if( i == 1 ) {
        attributeCollection.cronTime = "0 0 */1 * * ?";
    }
    if( i != scheduledFiles.len() ) {
        attributeCollection.onComplete = "Task#i+1#";
    }
     cfschedule(attributeCollection=attributeCollection);
}
</cfscript>
this assumes that you only need the cron time on the first task, because all the others are triggered from there. I don't know if that's right or not.
in order to pass different attributes to the various cfschedule calls, I'm using attributeCollection. That's a struct of all the attributes for the tag. That lets us have
oncomplete
for all but the last one, and to have the schedule info on only the first one.
s
it does not create a cfschedule task this way, cfschedule(attributeCollection=attributeCollection);
it keep throwing error action is required nd then task is required
seems the code needs to be written in tags
👎 1
how th for loop in written in tags
t
<cfloop index="i" from="1" to="#ArrayLen(scheduledFiles)#">
I think.
b
@Simone, what do you need a loop and a serviceFactory for? You can create the scheduled tasks and chain them without writing a single line of code. Do it in the ColdFusion Administrator. For example, as follows: (1) Go to the Scheduled Tasks page in the Administrator. Create each of the scheduled tasks, entering just the required values in the form. Give each task a unique name. (They may share the same group name.) Beware which start-times and end-times you enter for each task. If task T2 is to start after task T1, then T2's start-date/start-time should occur after those of T1. (2) For each task except the very first, select Chained Task. (3) For each task, Tsk_i, say, click on Show Additional Settings. In the onComplete field. enter the task Tsk_j that follows Tsk_i. Enter it in the following manner: groupName:Tsk_j. Use a comma-separated list where more than one task is to start after Tsk_i completes. That's about it.
s
Copy code
cfschedule sucks, it oes not work with attributecollection
i am getting error Advance Scheduling support is not available in this edition of ColdFusion server.
Copy code
<cfset scheduledFiles = ["File1.cfm", "File2.cfm", "File3.cfm", "File4.cfm", "File5.cfm", "File6.cfm"]>
<cfset startDate = DateFormat(DateAdd('d',-1,now()),'mm/dd/yyyy')>
<cfloop index="i" from="1" to="#ArrayLen(scheduledFiles)#">
    <cfschedule action="create" task= "#scheduledFiles[i]#"
        url= "#application.servername##scheduledFiles[i]#"
        startDate= "#startDate#" 
        starttime='#timeFormat(dateAdd("s", 30, now()), "HH:mm:ss")#' 
        group="iO"
        username="admin"
        password="admin"> 
    <cfif i eq 1>
        <cfschedule action="update" task= "#scheduledFiles[i]#"
        url= "#application.servername##scheduledFiles[i]#"
        startDate= "#startDate#" 
        starttime='#timeFormat(dateAdd("s", 30, now()), "HH:mm:ss")#'
        group="iO"
        username="admin"
        password="admin" 
        > 
       
    </cfif>
    <cfif i neq ArrayLen(scheduledFiles)>
        <cfschedule action="update" task= "#scheduledFiles[i]#"
        url= "#application.servername##scheduledFiles[i]#"
        startDate= "#startDate#" 
        starttime='#timeFormat(dateAdd("s", 30, now()), "HH:mm:ss")#'
        group="iO"
        username="admin"
        password="admin" 
        
        >
    </cfif>
</cfloop>
dont'see anything wrong in the code, i removed cron and oncomplete
s
but what is wrong in code, i am not using anything special
i am trying to schedule for previous day
m
what version of ColdFusion are you using? It is not so much that your code is wrong per se; it is more like what you are doing is not available in your ColdFusion server edition. Please keep in mind that ColdFusion server has a standard edition and an enterprise edition. Some of the features are only available in the enterprise edition but not in the standard edition.
s
i am using CF2021
m
CF2021 Standard?
s
yes
but i can create using admin
why its behaving badly here
i can test in the developer edition
let me check
t
Is chaining an enterprise-only feature maybe? If it works in developer, then that's probably what's going on.
s
on local, yes it works, but is there a way i can write some other way so the tasks should run after the one has been completed, looking for alternatives now, can i use windows schedular, if yes how any clue and docs ?
t
I haven't played much with the windows scheduler so that might be an option, but I don't know. But if you control the code for the tasks, you could write a task that calls all of the others in order.
m
@Simone I have done similar thing before. What I did was, I kept a database table to keep track what has been run and what is the next task. After the task is done, the cfm page would update the database table to mark the current process as completed. And then look up the next task and mark this next task as "In Progress". So, when some other tasks are completed, those cfm pages would read the database table to see what is completed, what is in progress, and what should be run next
@Simone In my case, the implementation was actually for an import process. There were a series of import files which needed to be processed in a certain order. The import files were in a central location. Instead of having one server processing ALL the files, we had three servers grabbing the import files and performed the import process. So, I implemented a database table to keep track of the statuses of the import files. So, after one server has finished processing one import file, it would update the database to mark it complete for the import file. It would then look up the next import file to be processed and then set up a scheduled task for it (one minute in the future). Basically, the same routine goes on for the other web servers as well until all the import files have been run.
s
@Monte Chan seems interesting, but when you store all the entries in database and mak the first one as running and after the scheduled task is run and completed, it updates the database to completed, how it will fetch second one and then execurttes it, what interval it will keeping on bugging to know when to trigger the next one, or its just programatically, because i have a code where i have run all the tasks after 1 hour of internal one after the other
m
There is a database table that defines the names of the files and the running order. There is another table that keeps track of the statuses of marks the files (i.e. completed, in progress, ...etc.). After a file is done is marked completed, it checks the table to see what the next file needs to be processed. If the next file is already in progress or scheduled, it would then schedule the file after that.
In my case, the schedule task would be set to either run immediately or 1 minute in the future from the current time.
b
@Tim is spot-on. Chaining of scheduled tasks is an Enterprise-only feature. So you cannot do it on CF2021 Standard. See the table in https://www.adobe.com/products/coldfusion-family/buying-guide.html
Copy code
@Simone: "is there a way i can write some other way so the tasks should run after the one has been completed,"
Yes. Suppose you know that the maximum time each task will run for is 1 hour. Then you could just manually create tasks whose start-date/start-times are 1 hour and 5 minutes apart. And how to create the tasks manually without writing a single line of code? See my earlier suggestion.
l
If you want some sanity with scheduled tasks, look at our implementation: https://coldbox.ortusbooks.com/digging-deeper/scheduled-tasks
j
@Simone you might also look into CBQ. You can break jobs into tasks then selectively call follow-up tasks if/when necessary. We are moving everything in this direction. https://cbq.ortusbooks.com/
s
this is somewhat i found from internet <cfscript> for (i = 1; i <= 5; i++) { taskName = "Task " & i; status = "Incomplete"; lastRunTime = ""; nextRunTime = ""; queryExecute(" INSERT INTO ScheduledTasks (TaskName, Status, LastRunTime, NextRunTime) VALUES (:taskName, :status, :lastRunTime, :nextRunTime) ", { taskName: { value: taskName, cfsqltype: "cf_sql_varchar" }, status: { value: status, cfsqltype: "cf_sql_varchar" }, lastRunTime: { value: lastRunTime, cfsqltype: "cf_sql_timestamp" }, nextRunTime: { value: nextRunTime, cfsqltype: "cf_sql_timestamp" } }, { datasource: "formdata" }); } allTasksComplete = queryExecute(" SELECT COUNT(*) AS TotalTasks FROM ScheduledTasks WHERE Status = 'Complete' ", {}, { datasource: "formdata" }).TotalTasks; if (allTasksComplete EQ 5) { queryExecute(" UPDATE ScheduledTasks SET NextRunTime = DATE_ADD(NOW(), INTERVAL 1 HOUR) ", {}, { datasource: "formdata" }); } </cfscript> in this case, how should i call my last schedule task which is finalcode to send to thepage.cfm to record everything,
can't use coldbox because the project is vey old with no framework
j
I don't know what that is, but it looks homegrown. You can use this kind of thing to chain scheduled tasks though.
Copy code
Create/update task ( run this once on startup for every task you want to define ) - 

      cfschedule(
        action = "update",
        task = "GenerateAlerts",
        operation = "HTTPRequest",
        startDate = "5/12/2016",
        startTime="6:00 AM",
        endTime="6:00 PM",
        interval="300",
        url = "<http://localhost:5000/generate/alerts>",
        path = expandPath("/logs"),
        file = "generateAlerts.htm",
        requesttimeout = "600"
      );

Then in the GenerateAlerts task, if you wanted to run generateAlerts2, you would:

cfschedule( action = "run", task = "GenerateAlerts2" );
https://cfdocs.org/cfschedule
s
i have the docs so many times and everytime its nothing new, i have been writing scripts back and forth to make sure i should have it handy and working
but till now no luck
m
@Simone by any chance, would you be able to put your codebase in github? If so, please post the link to your github repo. Without looking at your codes, it is hard to tell why you are stuck... The stuff that you posted about what you found on the internet is just writing things to a database table to keep track when the tasks are run; but it does not contain anything to actually run the scheduled tasks.
s
ok sure
i can share the code here
what i have
i do not have a github here is my start
Copy code
<!--- Query the database to fetch the pending tasks --->
<cfquery name="getPendingTasks" datasource="yourDataSource">
    SELECT * FROM tasks WHERE status = 'pending'
</cfquery>

<!--- Loop through the pending tasks --->
<cfloop query="getPendingTasks">
    <!--- Perform the task and check if it succeeds --->
    <cfset taskSucceeded = performTask(currentRow.taskId)>

    <!--- Update the task status and schedule time based on success --->
    <cfif taskSucceeded>
        <cfset newStatus = 'complete'>
        <cfset newScheduleTime = DateAdd('h', 1, currentRow.scheduleTime)>
    <cfelse>
        <cfset newStatus = 'pending'>
        <cfset newScheduleTime = currentRow.scheduleTime>
    </cfif>

    <!--- Update the task in the database with the new status and schedule time --->
    <cfquery name="updateTask" datasource="yourDataSource">
        UPDATE tasks
        SET status = <cfqueryparam value="#newStatus#" cfsqltype="cf_sql_varchar">,
            scheduleTime = <cfqueryparam value="#newScheduleTime#" cfsqltype="cf_sql_timestamp">
        WHERE taskId = <cfqueryparam value="#currentRow.taskId#" cfsqltype="cf_sql_integer">
    </cfquery>
</cfloop>
performTask is a function which will perform task one after the other like cfschedule run because i have created those tasks in the cfschedular and every task runs and give me status of true/false, just wondering i have to wait for the results to return adn move to next step, should i use some threads or how, this is freajking very confusing now
b
I can see how much effort you have put into this. However, I can spot some flaws, sorry. (1) "PerformTask" is apparently a synchronous, hence blocking, call. Let's say the task is performed in 30 minutes, Then the loop will come to a standstill while waiting for the result of the call. Not optimal. You mention threads. That is probably a better idea. (2) The division of tasks into just 2 categories (Complete, Pending) is coarse-grained. Off the top of my head, I can imagine a task being Complete, Running, Pending (YetToStart), Misfired, or Failed. By Misfired I am thinking of a task that, for whatever reason, ends up in the catch section of a try-catch. By Failed I am thinking of a task that exceeds the request-timeout, for example.
s
right, but can you help me here writing that piece of code, for threads i am not sure, i had read about it but its implementation i had never actually practically implemented, and also the tasks i have have to run one after the other like a chain effect so not sure how thread will help, when you onmisfire and failed, for failed how can i track that because i think for failed, i will get the result as false which the page will output from the try catch block.
and if has to be synchronous, i want to output the results every 5 mins to see its on which task, i ran the tasks individually and i think they do not take 30 mins to execute as all of them
and also another question, there are two more tasks which i plan to do after the cfloop so once i have the full data in all tables i needed, i will run the other 2 remaining tasks to do thier JOB
this what my function does and i have no way of knowing what it did in the background. every taskName displays true/false on screen so if i run cfschedule action run, how can i get that status and it says its running and it sends me a status
Copy code
<cffunction name="performTask">
        <cfargument name="taskID" required="true" default="">
        <cfargument name="taskName" required="true" default="">
        <cfschedule action="run" task="#arguments.taskName#">
    </cffunction>
thought of using cfhttp now instead of cfschedule run, at least i will have the results but the main page which has the above code as cfloop, has to be scheduledTask
and once is status is complete, how do i back it back to pending again
i did it like this this, but i am not using a join
Copy code
<cfthread action="run" name="performTaskThread#taskId#">
        <cfset taskSucceeded = performTask(taskURL,taskName)>
    </cfthread>