For developer work is this error surely known. So, what is going on here?
Well, the first thing to note is that the error is absolutely correct. The task is locked, but why is it locked, and was it not unlocked appropriately?
How workflow tasks are locked
The first thing to realize is that when SharePoint workflows alter tasks there needs to be some sort of locking behaviour on tasks so that you will not accidentally create race conditions and update a task simultaneously, the one update overwriting the other. Typically database level locks are used but for SharePoint Workflow tasks however a more simple, business-layer type lock suffices. Since SharePoint workflow is about humans and not about maximum near real-time performance the chance of collisions is low enough not to be worried about this. The workflow runtime in SharePoint locks tasks by setting a field and persisting that to the database. It then checks on the field value to determine whether is locked. You can actually see the code that does this. The SPWinOEItemEventReceiver implements the ItemUpdating and ItemUpdated events. In the ItemUpdating you can find code similar to the following pseudo code:
If WorkflowVersion for item not equal to 1
Throw locked error
Else
Place lock (set WorkflowVersion on item to value not equal to 1)
Running into the locking issue
So, the issue is that the lock is still there even though it should have been released in the ItemUpdated event. Clearly, the ItemUpdated event is where the issue lies, and like all bugs in life, you did it, and not the framework! (I hope you are not shocked) There is only one aspect of the locking that you can control, and that is the persistence and hydration of your workflow to and from the database. This is exactly what is causing the bugs. When the ItemUpdated event fires and tries to de-serialize your workflow there might be an exception during the hydration of your workflow object. This error is difficult to see since it is happening in non-user code based on an asynchronous event. When that error occurs, the task unlocking code doesn’t run!
The general flow of events to create this issue goes something like this:
- Developer designs a workflow which creates a task.
- Developer tests the workflow, and runs it up to the task change activity, meaning that the workflow is now serialized in the database waiting for a task change to occur.
- Developer spots a bug, and updates the workflow in such a way that de-serialization breaks.
- Developer updates the task through the browser to continue the workflow.
- Runtime bumps into the de-serialization error, and cannot continue, hence the task unlocking code does not run, and the task is locked for all eternity.
Preventing the locking issue
Now that we have a clear understanding of the issue, there are many things you can about it. On development I’d go for re-running the entire workflow.
On Production, it is even easier:
DO NOT UPGRADE UNTIL ALL RUNNING WORKFLOWS ARE COMPLETE
You should quiesce a workflow and when all running workflows have completed. Or, when you need to have the business logic available during the quiescing, you can only create a new workflow.
How to fix affected workflow tasks
As first thought which I have had that return WorkflowVersion to value 1, and it’s right. Example code can look like that:
public void UpdateTaskItem(SPList taskList, int identifier)
{
taskItem = taskList.GetItemById(identifier);
Guid workflowInstanceId = new Guid(taskItem[Microsoft.SharePoint.SPBuiltInFieldId.WorkflowInstanceID].ToString());
SPWorkflow workflow = taskItem.Workflows[workflowInstanceId];
if (workflow != null && !workflow.IsLocked)
{
taskItem[Microsoft.SharePoint.SPBuiltInFieldId.WorkflowVersion] = 1;
}
taskItem.Update();
}
Here we can see how to fix this situation. I have created one feature for finding this secret issues. It was really handy for our team.
The application has these parts:
- Found the corrupt task items
- Fix them
The first thought to find all these corrupt task items based on CAML query, but field “WorkflowVersion” is hidden what means it’s not reachable for search. My peace of code:
string siteUrl = SPContext.Current.Site.Url;
List<LockedTask> lockedTasks = new List<LockedTask>();
SPSecurity.RunWithElevatedPrivileges(() =>
{
using (SPSite site = new SPSite(siteUrl))
{
foreach (SPWeb web in site.AllWebs)
{
using (web)
{
foreach (SPList list in web.Lists)
{
//we're looking for task list with workflow features
if (list.BaseTemplate == SPListTemplateType.Tasks && list.Fields.Contains(Microsoft.SharePoint.SPBuiltInFieldId.WorkflowVersion))
{
//we do not expect any subfolders or another type of items
SPQuery spQry = new SPQuery();
spQry.RowLimit = 300;
do
{
SPListItemCollection items = list.GetItems(spQry);
foreach (SPListItem item in items)
{
int version = (int)item[Microsoft.SharePoint.SPBuiltInFieldId.WorkflowVersion];
// tick item which has not WorkflowVersion value 1
if (version == 1) continue;
XDocument xDoc = XDocument.Parse(item.Xml);
lockedTasks.Add(new LockedTask
{
LinkToDocument = xDoc.Root.Attribute("ows_WorkflowLink").Value.ToString(),
WorkflowName = xDoc.Root.Attribute("ows_WorkflowName").Value.ToString(),
AssignedTo = xDoc.Root.Attribute("ows_AssignedTo").Value.ToString(),
Name = item.Title,
TaskID = item.UniqueId,
ListID = list.ID,
WebID = web.ID,
WorkflowVersion = (int)item[Microsoft.SharePoint.SPBuiltInFieldId.WorkflowVersion]
});
}
spQry.ListItemCollectionPosition = items.ListItemCollectionPosition;
} while (spQry.ListItemCollectionPosition != null);
}
}
site.RootWeb.AllowUnsafeUpdates = true;
site.RootWeb.Properties.AddProperty(this.rootSitePropertyBag, lockedTasks);
}
}
}
});
pnTasks.Controls.Clear();
lockedTasks.ForEach(a =>
{
CheckBox ch = new CheckBox();
ch.EnableViewState = true;
ch.Text = a.ToString();
pnTasks.Controls.Add(ch);
pnTasks.Controls.Add(new LiteralControl("<br />"));
});
I display all corrupt task item in list with checkbox where you can pick up them and execute fixing function looks as below:
string siteUrl = SPContext.Current.Site.Url;
SPSecurity.RunWithElevatedPrivileges(() =>
{
using (SPSite site = new SPSite(siteUrl))
{
site.AllowUnsafeUpdates = true;
List<LockedTask> list = site.RootWeb.Properties.ReadProperty<List<LockedTask>>(this.rootSitePropertyBag);
// seek all check boxes
foreach (Control cc in pnTasks.Controls)
if (cc is CheckBox && (cc as CheckBox).Checked)
{
LockedTask task = LockedTask.GetTask((cc as CheckBox).Text, list);
using (SPWeb web = site.OpenWeb(task.WebID))
{
web.AllowUnsafeUpdates = true;
SPList taskList = web.Lists[task.ListID];
SPListItem taskItem = taskList.GetItemByUniqueId(task.TaskID);
//unlocking task by setting 1 on Workflow version
taskItem[Microsoft.SharePoint.SPBuiltInFieldId.WorkflowVersion] = 1;
taskItem.Update();
}
}
}
});
I hope that helps to understand why SharePoint is locking your workflow tasks. Here is attached source code of my solution.
Happy SharePointing!