WF4: Workflow stay locked - c#

I've an application that host WF4 workflow in IIS using WorkflowApplication
The workflow is defined by user (using a rehosted workflow designer) and the xml is stored in the database. Then, depending on user actions using the application, an xml is selected in database and the workflow are created / resumed.
My problem is: when the workflow reach a bookmarks and go idle, it stay locked for a various amount of time. Then, if the user try to make another action concerning this workflow, I got this exception:
The execution of an InstancePersistenceCommand was interrupted because the instance '52da4562-896e-4959-ae40-5cd016c4ae79' is locked by a different instance owner. This error usually occurs because a different host has the instance loaded. The instance owner ID of the owner or host with a lock on the instance is 'd7339374-2285-45b9-b4ea-97b18c968c19'.
Now it's time for some piece of code
When I workflow goes idle, I specify it should be unloaded:
private PersistableIdleAction handlePersistableIdle(WorkflowApplicationIdleEventArgs arg)
{
this.Logger.DebugFormat("Workflow '{1}' is persistableIdle on review '{0}'", arg.GetReviewId(), arg.InstanceId);
return PersistableIdleAction.Unload;
}
Foreach WorkflowApplication I need, I create a new SqlWorkflowInstanceStore:
var store = new SqlWorkflowInstanceStore(this._connectionString);
store.RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(5);
store.InstanceLockedExceptionAction = InstanceLockedExceptionAction.BasicRetry;
Here is how my WorkflowApplication is created
WorkflowApplication wfApp = new WorkflowApplication(root.RootActivity);
wfApp.Extensions.Add(...);
wfApp.InstanceStore = this.createStore();
wfApp.PersistableIdle = this.handlePersistableIdle;
wfApp.OnUnhandledException = this.handleException;
wfApp.Idle = this.handleIdle;
wfApp.Unloaded = this.handleUnloaded;
wfApp.Aborted = this.handleAborted;
wfApp.SynchronizationContext = new CustomSynchronizationContext();
return wfApp;
Then I call the Run method to start it.
Some explanations:
- root.RootActivity: it's the activity created from the workflow XML stored in database
- CustomSynchronizationContext: a synchronisation context that handle authorisations
- in the handleUnloaded method I log when a workflow is unloaded, and I see that the workflow is correctly unloaded before the next user action, but it seems the workflow stay locked after being unloaded (?)
Then, later, when I need to resume the workflow, I create the workflow the same way then I call:
wfApp.Load(workflowInstanceId);
which throw the "locked" exception specified above.
If I wait a few minutes, and try again, it works fine.
I read a post here that say we need to set an owner.
So I've also tried using a static SqlWorkflowInstanceStore with the owner set using this code:
if (_sqlWorkflowInstanceStore != null)
return _sqlWorkflowInstanceStore;
lock (_mutex)
{
if (_sqlWorkflowInstanceStore != null)
return _sqlWorkflowInstanceStore;
// Configure store
_sqlWorkflowInstanceStore = new SqlWorkflowInstanceStore(this._connectionString);
_sqlWorkflowInstanceStore.RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(5);
_sqlWorkflowInstanceStore.InstanceLockedExceptionAction = InstanceLockedExceptionAction.BasicRetry;
// Set owner - Store will be read-only beyond this point and will not be configurable anymore
var handle = _sqlWorkflowInstanceStore.CreateInstanceHandle();
var view = _sqlWorkflowInstanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(5));
handle.Free();
_sqlWorkflowInstanceStore.DefaultInstanceOwner = view.InstanceOwner;
}
return _sqlWorkflowInstanceStore;
But then I've this kind of exception:
The execution of an InstancePersistenceCommand was interrupted because
the instance owner registration for owner ID
'9efb4434-8560-469f-9d03-098a2d48821e' has become invalid. This error
indicates that the in-memory copy of all instances locked by this
owner have become stale and should be discarded, along with the
InstanceHandles. Typically, this error is best handled by restarting
the host.
Does anyone know how to be sure that the lock on the workflow is released immediately when the workflow is unloaded ?
I've see some post doing this with a WorkflowServiceHost (using WorkflowIdleBehavior) but here I'm not using WorkflowServiceHost, I'm using WorkflowApplication
Thank you for any help!

I suspect the problem is with the InstanceOwner of the SqlWorkflowInstanceStore. It isn't deleted so the workflow needs to wait for the ownership of the previous one to time out.
Creating an instance owner
var instanceStore = new SqlWorkflowInstanceStore(connStr);
var instanceHandle = instanceStore.CreateInstanceHandle();
var createOwnerCmd = new CreateWorkflowOwnerCommand();
var view = instanceStore.Execute(instanceHandle, createOwnerCmd, TimeSpan.FromSeconds(30));
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
Deleting an instance owner
var deleteOwnerCmd = new DeleteWorkflowOwnerCommand();
instanceStore.Execute(instanceHandle, deleteOwnerCmd, TimeSpan.FromSeconds(30));
Another possible issue is that when a workflow aborts the Unloaded callback isn't called.

Related

WF4 - resuming from instance store and InstanceNotReadyException

I am using WF4 and the WorkflowApplication to host a workflow. The workflow is very simple (at testing stage). It boils down to a series of logging activities with a delay activity and then more logging before finishing. I am using SqlWorkflowInstanceStore for saving persisting the workflows.
The workflow runs fine until it reaches the delay activity, here I can see that it gets saved into the persist database and then unloaded. I have looked at code examples and use the current code (at bottom) for resuming the workflow after the delay has expired. The code is run in a loop to ensure new (resumable) workflows are loaded. It all seems to work fine, the workflow gets resumed and I can se the expected logging output - however after the workflow completes it seems to try to resume it again. The call
WaitForEvents(_handle, TimeSpan.MaxValue)
contiunes again just like its going to resume a completed workflow. Next the
hasRunnableWorkflows
gets set to true. When the code reaches
wfApp.LoadRunnableInstance();
The exception InstanceNotReadyException (No runnable workflow instances were found in the InstanceStore for this WorkflowApplication to load.) is thrown.
I dont understand why this is happening and how to prevent the exception getting thrown. If I ignore the exception everything seems to work fine but I want to know why this is happening and if I'm doing someting wrong.
Code for resuming the workflow:
public Task ResumePendingFlows()
{
var tcs = new TaskCompletionSource<Guid>();
var store = _workflowInstanceStore?.Store;
if (store != null)
{
bool hasRunnableWorkflows = false;
//wait until a event has occurred
foreach (var currentEvent in store.WaitForEvents(_handle, TimeSpan.MaxValue))
{
if (currentEvent == HasRunnableWorkflowEvent.Value)
{
hasRunnableWorkflows = true;
break;
}
}
if (hasRunnableWorkflows)
{
//create WorkflowApplication with extensions and instance store
var wfApp = CreateWorkflowApplication();
wfApp.LoadRunnableInstance();
Logger?.Debug("Found runnable workflows");
//register completed, unloaded event passing the task completion source
RegisterWorkflowEvents(tcs, wfApp);
wfApp.Run();
}
else
{
Logger?.Debug("Did not find runnable workflows");
tcs.SetResult(Guid.Empty);
}
}
return tcs.Task;
}

Better Technique: Reading Data in a Thread

I've got a routine called GetEmployeeList that loads when my Windows Application starts.
This routine pulls in basic employee information from our Active Directory server and retains this in a list called m_adEmpList.
We have a few Windows accounts set up as Public Profiles that most of our employees on our manufacturing floor use. This m_adEmpList gives our employees the ability to log in to select features using those Public Profiles.
Once all of the Active Directory data is loaded, I attempt to "auto logon" that employee based on the System.Environment.UserName if that person is logged in under their private profile. (employees love this, by the way)
If I do not thread GetEmployeeList, the Windows Form will appear unresponsive until the routine is complete.
The problem with GetEmployeeList is that we have had times when the Active Directory server was down, the network was down, or a particular computer was not able to connect over our network.
To get around these issues, I have included a ManualResetEvent m_mre with the THREADSEARCH_TIMELIMIT timeout so that the process does not go off forever. I cannot login someone using their Private Profile with System.Environment.UserName until I have the list of employees.
I realize I am not showing ALL of the code, but hopefully it is not necessary.
public static ADUserList GetEmployeeList()
{
if ((m_adEmpList == null) ||
(((m_adEmpList.Count < 10) || !m_gotData) &&
((m_thread == null) || !m_thread.IsAlive))
)
{
m_adEmpList = new ADUserList();
m_thread = new Thread(new ThreadStart(fillThread));
m_mre = new ManualResetEvent(false);
m_thread.IsBackground = true;
m_thread.Name = FILLTHREADNAME;
try {
m_thread.Start();
m_gotData = m_mre.WaitOne(THREADSEARCH_TIMELIMIT * 1000);
} catch (Exception err) {
Global.LogError(_CODEFILE + "GetEmployeeList", err);
} finally {
if ((m_thread != null) && (m_thread.IsAlive)) {
// m_thread.Abort();
m_thread = null;
}
}
}
return m_adEmpList;
}
I would like to just put a basic lock using something like m_adEmpList, but I'm not sure if it is a good idea to lock something that I need to populate, and the actual data population is going to happen in another thread using the routine fillThread.
If the ManualResetEvent's WaitOne timer fails to collect the data I need in the time allotted, there is probably a network issue, and m_mre does not have many records (if any). So, I would need to try to pull this information again the next time.
If anyone understands what I'm trying to explain, I'd like to see a better way of doing this.
It just seems too forced, right now. I keep thinking there is a better way to do it.
I think you're going about the multithreading part the wrong way. I can't really explain it, but threads should cooperate and not compete for resources, but that's exactly what's bothering you here a bit. Another problem is that your timeout is too long (so that it annoys users) and at the same time too short (if the AD server is a bit slow, but still there and serving). Your goal should be to let the thread run in the background and when it is finished, it updates the list. In the meantime, you present some fallbacks to the user and the notification that the user list is still being populated.
A few more notes on your code above:
You have a variable m_thread that is only used locally. Further, your code contains a redundant check whether that variable is null.
If you create a user list with defaults/fallbacks first and then update it through a function (make sure you are checking the InvokeRequired flag of the displaying control!) you won't need a lock. This means that the thread does not access the list stored as member but a separate list it has exclusive access to (not a member variable). The update function then replaces (!) this list, so now it is for exclusive use by the UI.
Lastly, if the AD server is really not there, try to forward the error from the background thread to the UI in some way, so that the user knows what's broken.
If you want, you can add an event to signal the thread to stop, but in most cases that won't even be necessary.

How to decrease the user objects and Handles for console application

I am having the console application which creates the PDF for a set of records. For if the count is 9000 above an error occurred that "Error creating window Handle". In the application level i am using 6 threads.
As i observed in the Task Manager the Handles are increasing and the user objects are also increasing.
I have written the obj.Dispose method where ever i have created the object. So now my question is how to decrease the user objects and Handles.
I am using the console application with 3.5 Framework in C#.
Update:
Below is the code which i have used
Thread FirstTreadPDFs = new Thread(() => objPDFsProcess.DoGeneratePDFsProcess());
FirstTreadPDFs.Start();
//Thread2
Thread SecondTreadPDFs = new Thread(() => objPDFsProcess.DoGeneratePDFsProcess());
SecondTreadPDFs.Start();
//Thread3
Thread ThirdTreadPDFs = new Thread(() => objPDFsProcess.DoGeneratePDFsProcess2());
ThirdTreadPDFs.Start();
//Thread4
Thread FourthTreadPDFs = new Thread(() => objPDFsProcess.DoGeneratePDFsProcess());
FourthTreadPDFs.Start();
//Thread5
Thread FifthTreadPDFs = new Thread(() => objPDFsProcess.DoGeneratePDFsProcess1());
FifthTreadPDFs.Start();
FirstTreadPDFs.Join();
SecondTreadPDFs.Join();
ThirdTreadPDFs.Join();
FourthTreadPDFs.Join();
FifthTreadPDFs.Join();
DataSet dsHeader1 = new DataSet();
//Pending Cusotmers need to get to generate PDFs
dsHeader1 = objCustStatementDAL.GetCustStatementdetailsForPDF(IsEDelivery, 1);
if (dsHeader1 != null && dsHeader1.Tables.Count > 0)
{
if (dsHeader1.Tables[0].Rows.Count > 0)
{
writerLog.WriteLine(DateTime.Now + " Trying to get Pending Records");
objPDFsProcess.DoGeneratePDFsProcess2();
writerLog.WriteLine(DateTime.Now + " Exit Trying to get Pending Records block");
}
}
dsHeader1.Dispose();
After executing 9000+ records the Exit Trying line is executing and stopping the application.
Where ever i use the object i placed Dispose method.
From your question it is not really clear what are you doing, but if I'm guessing right, you are keeping too many open file handlers.
So here it comes. If you open a StreamReader for example, you open a File Handler, what happens to be an unmanaged and limited resource. Unmanaged means the .NET runtime can't keep tabs on its use, and even if you lose reference to the StreamReader object, the handler won't be closed. So for that, you need to call the Dispose function (and if you are creating a class that uses native resources, implement the IDisposable interface, wich contains the Dispose function properly). You can do the calling explicitly, but the best for everyone is to use the using block. That way your handlers will be closed properly everytime you leave the scope of the block, whatever the means.
Of course if you are triing to keep open and use this much handlers, you need to trick your way around it somehow, and that would still involve closing not currently used ones.

Using EventWaitHandle to ensure a single instance across multiple users

Collaborators have built a prototype using Processing that connects to a Sparkfun RFID reader, I think using a serial connection over USB. We've deployed the prototype into a number of trialists' homes and one common usage scenario I foolishly overlooked was user switching. Hence I am writing a wrapper that ensures only one instance of the prototype application is running across all users on the machine.
I’m testing out my first stab at this as a simple console app. Here’s the code:
static void Main(string[] args)
{
// http://stackoverflow.com/a/2590446/575530
var users = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
var rule = new EventWaitHandleAccessRule(users, EventWaitHandleRights.FullControl, AccessControlType.Allow);
var security = new EventWaitHandleSecurity();
security.AddAccessRule(rule);
bool createdStartup;
using (var whStartup = new EventWaitHandle(false, EventResetMode.AutoReset, "Global/AaltoTokensAppStartupEvent", out createdStartup, security))
{
bool createdShutdown;
using (var whShutdown = new EventWaitHandle(false, EventResetMode.AutoReset, "Global/AaltoTokensAppShutdownEvent", out createdShutdown, security))
{
Console.WriteLine("Let other instances shutdown");
whStartup.Set();
Console.WriteLine("If other instances exist wait for them to shutdown");
if (!createdShutdown)
{
whShutdown.WaitOne();
}
whShutdown.Reset();
Console.WriteLine("Start tray app");
var tokenProc = Process.Start(#"C:\Temp\FAMILY3_WIN\TokensApp.exe");
Console.WriteLine(tokenProc.ProcessName);
Console.WriteLine("Wait to see if another instance to tries to start");
whStartup.Reset();
whStartup.WaitOne();
Console.WriteLine("Shutdown if another instance starts");
//if (tokenProc != null) tokenProc.Kill();
foreach (var process in Process.GetProcesses())
{
if (process.ProcessName.StartsWith("javaw"))
{
process.Kill();
}
}
whShutdown.Set();
}
}
Console.WriteLine("Done...");
Console.ReadLine();
}
(N.B. I know there are issues with this code around (1) killing Java processes that are not the running prototype and (2) there’s no code to respond to lots of instances starting simultaneously, just two at a time. But that’s not what my question is about.)
Testing this under a single user account works fine. I can start my app, it in turn starts the prototype, and if I start a second instance of my app the first one kills the initial instance of the prototype before the second one starts another instance of the prototype afresh.
But if I try doing this from two different user accounts it fails (silently). If I
Start an instance of my application it starts the prototype
Switch user
Start an instance of my application then it starts the
prototype without my app from step 1 first shutting down the existing
instance.
Can anyone see what’s wrong with my code? How should I use EventWaitHandle across several simultaneous user sessions on the same machine?
Isn't it always the way, minutes after writing a long question the answer leaps to mind!
I got the slash the wrong way around in the name of the EventWaitHandle. For example replacing the constructor call:
new EventWaitHandle(false, EventResetMode.AutoReset, "Global/AaltoTokensAppShutdownEvent", out createdShutdown, security)
with this one:
new EventWaitHandle(false, EventResetMode.AutoReset, #"Global\AaltoTokensAppShutdownEvent", out createdShutdown, security)
fixes my problem.

Creating a Cross-Process EventWaitHandle

I have two windows application, one is a windows service which create EventWaitHandle and wait for it. Second application is a windows gui which open it by calling EventWaitHandle.OpenExisting() and try to Set the event. But I am getting an exception in OpenExisting. The Exception is "Access to the path is denied".
windows Service code
EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset, "MyEventName");
wh.WaitOne();
Windows GUI code
try
{
EventWaitHandle wh = EventWaitHandle.OpenExisting("MyEventName");
wh.Set();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
I tried the same code with two sample console application, it was working fine.
You need to use the version of the EventWaitHandle constructor that takes an EventWaitHandleSecurity instance. For example, the following code should work (it's not tested, but hopefully will get you started):
// create a rule that allows anybody in the "Users" group to synchronise with us
var users = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
var rule = new EventWaitHandleAccessRule(users, EventWaitHandleRights.Synchronize | EventWaitHandleRights.Modify,
AccessControlType.Allow);
var security = new EventWaitHandleSecurity();
security.AddAccessRule(rule);
bool created;
var wh = new EventWaitHandle(false, EventResetMode.AutoReset, "MyEventName", out created, security);
...
Also, if you're running on Vista or later, you need to create the event in the global namespace (that is, prefix the name with "Global\"). You'd also have to do this on Windows XP if you use the "Fast User Switching" feature.
This might be caused by the service process running at an elevated privilege level, but the GUI process is not. If you put the same code into two console apps, they'll both be running at user level and won't have any trouble accessing each other's named shared objects.
Try running the GUI app with the "Run as administrator" flag from the Windows start menu. If that solves the issue, you need to read up on how to request elevation within your code. (I haven't done that)

Categories

Resources