Timer Job Scheduling on DataSheet Entry in SharePoint - sharepoint

I have a list on which I have an ItemUpdated handler.
When I edit using the datasheet view and modify every item, the ItemUpdated event will obviously run for every single item.
In my ItemUpdated event, I want it to check if there is a Timer Job scheduled to run. If there is, then extend the SPOneTimeSchedule schedule of this job to delay it by 5 seconds. If there isn't, then create the Timer Job and schedule it for 5 seconds from now.
I've tried looking to see if the job definition exists in the handler and if it does exist, then extend the schedule by 5 seconds. If it doesn't exist, then create the job definition to run in a minutes time.
MyTimerJob rollupJob = null;
foreach (SPJobDefinition job in web.Site.WebApplication.JobDefinitions)
{
if (job.Name == Constants.JOB_ROLLUP_NAME)
{
rollupJob = (MyTimerJob)job;
}
}
if (rollupJob == null)
{
rollupJob = new MyTimerJob(Constants.JOB_ROLLUP_NAME, web.Site.WebApplication);
}
SPOneTimeSchedule schedule = new SPOneTimeSchedule(DateTime.Now.AddSeconds(5));
rollupJob.Schedule = schedule;
rollupJob.Update();
When I try this out on the server, I get a lot of errors
"An update conflict has occurred, and you must re-try this action. The object MyTimerJob Name=MyTimerJobName Parent=SPWebApplication Name=SharePoint -80 is being updated by NT AUTHORITY\NETWORK SERVICE in the w3wp process
I think the job is probably running for the first time and once running, the other ItemUpdated events are coming in and finding the existing Job definition. It then tries to Update this definition even though it is currently being used. Should I make a new Job Definition name so that it doesn't step on top of the first? Or raise the time to a minute?

I solved this myself by just setting the delay to a minutes time from now regardless of whether a definition is found. This way, while it is busy, it will keep pushing back the scheduling of the job until it is done processing

This is because the event is asynchronous. You'll need to rethink exactly what you're trying to solve with this code and potentially re-factor it.

Maybe you should try using "lock" on the timer job object?

Related

Bukkit/Spigot - How to wait until BukkitRunnable is finished

In my onDisbale() method in my Main class I have a loop which creates and starts new BukkitRunnables.
I'm getting a error in console: org.bukkit.plugin.IllegalPluginAccessException: Plugin attempted to register task while disabled I need to somehow wait in my onDisable() method until all the BukkitRunnables I create in the loop are finished. How to do that?
My code looks like this:
#Override
public void onDisable() {
for (Player p : Bukkit.getOnlinePlayers()) {
new PlayerDataSaverRunnable().runTaskAsynchronously(this);
}
}
The onDisable method is the very last thing that gets called before your plugin is disabled and the server shuts down. So, as the error message says, you unfortunately can't schedule any new tasks in the onDisable function.
You mentioned in a comment that you were trying to write to a file in the plugins folder, and under normal circumstances you'd want to do that asynchronously. But, because onDisable only ever gets called when the server is shut down (or when the entire server is reloaded with /reload), it's perfectly fine to run code here that blocks the main thread and could potentially take a few seconds to run — in the case of a shutdown, by the time this method gets called, all the players will have already been kicked off the server, and so there's no "lag" to complain about. If your plugin is super advanced and has to save a bunch of stuff, I don't think any server owners would complain even if it took 10 or so seconds to disable.
Of course, you would have to be saving something crazy for it to take a whole 10 seconds to save. More than likely, most files will save in just a few milliseconds.
If you're really dead-set on disabling the plugin as fast as possible, you might consider having some async task that runs every 5 minutes or so and auto-saves the files. Then, in onDisable, you could only save files that changed since the auto-saver was last run. That's a good practice anyways, just incase the server crashes or the power goes out and the onDisable method doesn't get a chance to run. But, then again, I would still recommend that you save everything in the onDisable method (that's what I do for all of my plugins, as well), even if it will take a few seconds and block the main thread, just so you can be 100% sure that everything gets saved correctly.

Twilio Taskrouter: How to prevent last worker in queue from being reassigned rejected task?

I'm using NodeJS to manage a Twilio Taskrouter workflow. My goal is to have a task assigned to an Idle worker in the main queue identified with queueSid, unless one of the following is true:
No workers in the queue are set to Idle
Reservations for the task have already been rejected by every worker in the queue
In these cases, the task should fall through to the next queue identified with automaticQueueSid. Here is how I construct the JSON for the workflow (it includes a filter such that an inbound call from an agent should not generate an outbound call to that same agent):
configurationJSON(){
var config={
"task_routing":{
"filters":[
{
"filter_friendly_name":"don't call self",
"expression":"1==1",
"targets":[
{
"queue":queueSid,
"expression":"(task.caller!=worker.contact_uri) and (worker.sid NOT IN task.rejectedWorkers)",
"skip_if": "workers.available == 0"
},
{
"queue":automaticQueueSid
}
]
}
],
"default_filter":{
"queue":queueSid
}
}
}
return config;
}
This results in no reservation being created after the task reaches the queue. My event logger shows that the following events have occurred:
workflow.target-matched
workflow.entered
task.created
That's as far as it gets and just hangs there. When I replace the line
"expression":"(task.caller!=worker.contact_uri) and (worker.sid NOT IN task.rejectedWorkers)"
with
"expression":"(task.caller!=worker.contact_uri)
Then the reservation is correctly created for the next available worker, or sent to automaticQueueSid if no workers are available when the call comes in, so I guess the skip_if is working correctly. So maybe there is something wrong with how I wrote the target expression?
I tried working around this by setting a worker to unavailable once they reject a reservation, as follows:
clientWorkspace
.workers(parameters.workerSid)
.reservations(parameters.reservationSid)
.update({
reservationStatus:'rejected'
})
.then(reservation=>{
//this function sets the worker's Activity to Offline
var updateResult=worker.updateWorkerFromSid(parameters.workerSid,process.env.TWILIO_OFFLINE_SID);
})
.catch(err=>console.log("/agent_rejects: error rejecting reservation: "+err));
But what seems to be happening is that as soon as the reservation is rejected, before worker.updateWorkerFromSid() is called, Taskrouter has already generated a new reservation and assigned it to that same worker, and my Activity update fails with the following error:
Error: Worker [workerSid] cannot have its activity updated while it has 1 pending reservations.
Eventually, it seems that the worker is naturally set to Offline and the task does time out and get moved into the next queue, as shown by the following events/descriptions:
worker.activity.update
Worker [friendly name] updated to Offline Activity
reservation.timeout
Reservation [sid] timed out
task-queue.moved
Task [sid] moved out of TaskQueue [friendly name]
task-queue.timeout
Task [sid] timed out of TaskQueue [friendly name]
After this point the task is moved into the next queue automaticQueueSid to be handled by available workers registered with that queue. I'm not sure why a timeout is being used, as I haven't included one in my workflow configuration.
I'm stumped--how can I get the task to successfully move to the next queue upon the last worker's reservation rejection?
UPDATE: although #philnash's answer helped me correctly handle the worker.sid NOT IN task.rejectedWorkers issue, I ultimately ended up implementing this feature using the RejectPendingReservations parameter when updating the worker's availability.
Twilio developer evangelist here.
rejectedWorkers is not an attribute that is automatically handled by TaskRouter. You reference this answer by my colleague Megan in which she says:
For example, you could update TaskAttributes to have a rejected worker SID list, and then in the workflow say that worker.sid NOT IN task.rejectedWorkerSids.
So, in order to filter by a rejectedWorkers attribute you need to maintain one yourself, by updating the task before you reject the reservation.
Let me know if that helps at all.

node-schedule undoing cancellation

I've been working with node for the first time in a while again and stumbled upon node-schedule, which for the most part has been a breeze, however, I've found resuming a scheduled task after canceling it via job.cancel() pretty difficult.
For the record, I'm using schedule to perform specific actions at a specific date (non-recurring) and under some circumstances cancel the task at a specific date but would later like to resume it.
I tried using job.cancel(true) after cancelling it via plain job.cancel() first as the documentation states that that would reschedule the task, but this has not worked for me. Using job.reschedule() after having cancelled job first yields the same result.
I could probably come up with an unelegant solution, but I thought I'd ask if anyone knows of an elegant one first.
It took me a while to understand node-schedule documentation ^^
To un-cancel a job, You have to give to reschedule some options.
If you don't pass anything to reschedule, this function returns false (Error occured)
For exemple, you can declare options, and pass this variable like this :
const schedule = require('node-schedule');
let options = {rule: '*/1 * * * * *'}; // Declare schedule rules
let job = schedule.scheduleJob(options, () => {
console.log('Job processing !');
});
job.cancel(); // Cancel Job
job.reschedule(options); // Reschedule Job
Hope it helps.

Azure Scheduled WebJob Never Finished Status

I have a scheduled web job that runs a function every minute:
[TimerTrigger("00:01:00", RunOnStartup = true)]
Sometimes it hangs and has a "Never Finish" status for a few days. This prevents new schedules for the job to be triggered. The Azure log also didn't record any entries - log was empty for that run.
I wonder if there is a way to tell Azure scheduler to continue with the scheduling if the job has a "Never Finish" status / state? Does setting "UseMonitor = true" do this?
As far as I know, if the scheduled web job processing is taking a long time periodically (or not finishing at all), it must be that every now and then the operations in your job function take a long time or fail. All depends on what your job is actually doing internally. If it is going async in places, the SDK will continue to wait for it to return.
According to this, I suggest you could try to use webjob's TimeoutAttribute.
It easy for functions to be cancelled based on timeout if they're hung.It will show a exception.
If you find the error is too much and you want to alter, I suggest you could use ErrorTrigger, you could refer to this article.
More details, you could refer to below codes.
I used the queue to test it, the result is as same as TimerTrigger webjob.
//Change the function timeout value
[Timeout("00:00:03")]
public static void TimeoutJob(
[QueueTrigger("queue")] string message,
CancellationToken token,
TextWriter log)
{
Console.WriteLine("From function: Received a message: " + message);
Task task = new Task(new Action(() => /*here is your code*/ Thread.Sleep(5000)), token);
// this will cancel the task is the token is CancellationRequested and show the exception the task is cancel
task.Wait();
Console.WriteLine("From function: Cancelled: Yes");
}

Testing Workflow History Autocleanup

I am facing a rather peculiar problem here. We have an OOTB approval workflow, which logs all the workflow history into a workflow history list. This list gets purged every 60 days. To increase this time period for which the workflow history is retained, I googled around and found that I have run this code:
using (SPSite wfSite = new SPSite(siteUrl))
{
using (SPWeb wfWeb = wfSite.OpenWeb(webName))
{
SPList wfList = wfWeb.Lists[listName];
SPWorkflowAssociation _wfAssociation = null;
foreach (SPWorkflowAssociation a in wfList.WorkflowAssociations)
{
if("approval 1" == wfAssociationName.ToLowerInvariant())
{
a.AutoCleanupDays = newCleanupDays;
_wfAssociation = a;
assoCounter++;
}
else
{
_wfAssociation = a;
}
}
wfList.UpdateWorkflowAssociation(_wfAssociation);
}
}
The code works fine, in the sense that it does not throw any exceptions. So far so good. But my problem now is that I need to test whether my code works. So i set the newCleanupDays variable to 0. But i see that new workflow activities are still getting logged in the workflow history list. I can set the variable to 1, but that would mean waiting an entire day to see if the code works..
Is there any way in which I can test my scenario, so that I can set the autocleanup days to 1, and I dont't have to wait an entire day to see if my code works? Is there any way I can "Fool" the system into thinking that 1 day has elapsed? I tried changing the system time and restarting the server and everything, but it didn't work for me.
Changing the system time should work, but you are going to have to kick off the timer job that initiates the workflows. Don't restart the server after the time change.
One warning is that SharePoint really really does not like travelling back in time. Any documents that are created in the "future" are going to have issues. So remember to test in a new web site that can be deleted when you roll back to "now".

Resources