Monday, August 1, 2016

Sitecore inconsistencies with the reminder feature

In the Sitecore content editor, there is a handy feature which allows content authors to set a reminder on a content item. This feature will then email the user with the reminder message they set at the specified date/time.

The first problem which I encountered was that the same reminder was being sent multiple times (from the master database and the web database). If an item had an older reminder set and you then published to the web database, the reminder would be re-added to the queue and sent again. The fix for these problems has been addressed by Christian Kay Linkhusen and involves overriding the task database to stop reminders from being sent off the web database.

The second problem I encountered was that some reminders were begin sent as expected where others would not. After looking at the tasks table in the core database, I noticed that when I set a reminder on the Content Management server, it would then be added into the table without an instance name. The task database agent is a timed job which is run on both content management and content delivery servers. When this is run (GetPendingTasks - to get pending tasks to process), the first method it calls is AssignTasks, which looks for any tasks without an instance name and assigns them to the current instance. This meant that one of the content delivery servers could then take ownership of a newly created reminder, and the content management server would never see it. Because this item is in the master database, the content delivery server could never access it so the reminder would never send.

The fix for this problem was to take a copy of the GetPendingTasks() method and remove the AssignTasks() method from running. The code to get the pending tasks actually gets all tasks due to run assigned to the current instance, or where the instance is null - so the tasks without the instance names would still send. The fix above actually overrides the method which calls GetPendingTasks() so these two fixes work well together.

using Sitecore;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Sql;
using Sitecore.Data.SqlServer;
using Sitecore.Diagnostics;
using Sitecore.Diagnostics.PerformanceCounters;
using Sitecore.Reflection;
using Sitecore.Tasks;
using System;
using System.Collections;
using System.Data.SqlTypes;
using Sitecore.Common;

namespace MyProject.Sitecore.Override
{
    public class TaskDatabaseAgent : Sitecore.Tasks.TaskDatabaseAgent
    {
        public new void Run()
        {
            Task[] pendingTasks = GetPendingTasks(); // Call our copy of the method here
            this.LogInfo(string.Concat("Processing tasks (count: ", (int)pendingTasks.Length, ")"));
            Task[] taskArray = pendingTasks;
            for (int i = 0; i < (int)taskArray.Length; i++)
            {
                Task logActivity = taskArray[i];
                try
                {
                    logActivity.LogActivity = this.LogActivity;

                    //Test if the Task is the EmailReminderTask and the current database is not master database
                    if (logActivity.GetType() == typeof(EmailReminderTask) && !logActivity.DatabaseName.ToLower().Equals("master"))
                    {
                        //If its not the master database
                        //Only mark the Task as done, don't execute the reminder
                        Globals.TaskDatabase.MarkDone(logActivity);
                    }
                    else
                    {
                        logActivity.Execute();
                    }

                    JobsCount.TasksTasksExecuted.Increment((long)1);
                }
                catch (Exception exception)
                {
                    Log.Error("Exception in task", exception, logActivity.GetType());
                }
            }
        }
        public Task[] GetPendingTasks()
        {
            //this.AssignTasks(); // Removed as CD servers were assigning master tasks to themselves
            ArrayList arrayLists = new ArrayList();
            int num = 8;
            string str = "SELECT [ID], [NextRun], [taskType], [Parameters], [Recurrence], [ItemID], [Database], [InstanceName] \r\n        FROM [Tasks] \r\n        WHERE [NextRun] <= @now AND [Pending] = 1 AND [Disabled] = 0 AND ([InstanceName] IS NULL OR [InstanceName] = @instanceName) \r\n        ORDER BY [NextRun]";
            object[] utcNow = new object[] { "now", DateTime.UtcNow, "instanceName", Settings.InstanceName };
            object[] array = SqlUtil.GetArray(str, utcNow, Settings.GetConnectionString("core"));
            for (int i = 0; i < (int)array.Length - (num - 1); i = i + num)
            {
                ID d = ID.Parse(array[i]);
                DateTime dateTime = ((DateTime)array[i + 1]).SpecifyKind(DateTimeKind.Utc);
                string str1 = array[i + 2] as string;
                string str2 = array[i + 3] as string;
                string str3 = array[i + 4] as string;
                ID d1 = ID.Parse(array[i + 5]);
                string str4 = array[i + 6] as string;
                string str5 = array[i + 7] as string;
                Task task = this.CreateTask(str1, dateTime);
                if (task != null)
                {
                    task.ID = d;
                    task.Parameters = str2;
                    task.RecurrencePattern = str3;
                    task.ItemID = d1;
                    task.DatabaseName = str4;
                    task.InstanceName = str5;
                    arrayLists.Add(task);
                }
            }
            return arrayLists.ToArray(typeof(Task)) as Task[];
        }

        protected Task CreateTask(string taskType, DateTime taskDate)
        {
            if (taskDate == SqlDateTime.MinValue.Value)
            {
                taskDate = DateTime.MinValue;
            }
            taskDate = DateUtil.ToUniversalTime(taskDate);
            object[] objArray = new object[] { taskDate };
            return ReflectionUtil.CreateObject(taskType, objArray) as Task;
        }

        /// <summary>
        /// Logs the info.
        /// </summary>
        /// <param name="message">The message.</param>
        private void LogInfo(string message)
        {
            if (this.LogActivity)
            {
                Log.Info(message, this);
            }
        }
    }
}
You'll also need to replace the task database agent to use the one above:
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <sitecore>
    <scheduling>
      <agent type="Sitecore.Tasks.TaskDatabaseAgent" method="Run" interval="00:10:00">
        <patch:attribute name="type" xdt:Transform="Replace" xdt:Locator="Match(type)">Myproject.Override.TaskDatabaseAgent</patch:attribute>
      </agent>
    </scheduling>
  </sitecore>
</configuration>
As with any overrides or replacement of Sitecore code, you will need to check if the code is updated upon installations of CMS updates and update your code accordingly. This code was taken from version8 update 5.

No comments:

Post a Comment