how does the instanceId get set on SqlWorkflowPersistenceService
Hi,
I'm trying to set up multiple servers each hosting workflow runtimes of their own. These servers persist their instances to the same SQL server. I can't get the locking mechanism to throw an ownership exception when a second runtime tries to load an instance from the persistance store.
Looking at this, the Persistance service provides an @ownerId parameter to the stored procedures, I assume that this identifies which runtime locked the workflow Instance so that only that runtime can get it back while it is still locked. The problem is that this always seems to be null.Digging a bit deeper, the persistance service would seem to be storing a Guid, that it uses for this but it is set to Guid.Empty in all of the constructor overloads and I can't find any way to change this so that the each of the machines in our farm would have diferent identities.
If anybody can show me how this works I would be grateful.
Joe
Answers
Joe,
Ownerid could be null becuase
1. Ownershiptimeout is not configured.
2. If timeout is configured, unloadonidle is set to true due to which whenever workflow becomes idle, it is unloaded (and unlocked which sets unlock = 1 and ownerid = null)
Sqlworkflowpersistenceservice takes care of setting the ownerid while loading a workflowinstance if locking mechanism is enabled. To enable it you must set the ownership timout in the constructor of the sqlworkflowpersistence service (or in the config file) of all the hosts that want to participate in instance locking. When a host loads the instance it will lock the instance for a time of Now + Ownershiptimeout. At this point if any other host tries to load this instance, it will get an ownershipexception. This makes sure that if there are multiple hosts pointing to the same persistence store and ownershiptimeout is configured, only one runtime will be able to load a workflow. This lock is released when the host unloads before the timeout has expired. One thing to note is if the host tries to unload at a time > ownershiptimeout it will not be able to do so because the ownership time has expired and it will get ownershipexception. When ever the lock is released, unlock is set to 1 and ownerid is set to null which indicates that workflowinstance is unlocked and any other host can load it.Tracing will give you more details about the workflow instance execution. http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=363145&SiteID=1
Hope this helps,
SonaliIan,
You are right. On following up, I found there was a bug in 2.2 and locking is enabled only if you use the NameValueCollection constructor. This is fixed in RTM and locking will be enabled if you use either NameValueCollection ctor or the ctor which sets the ownershiptimeout explicitly.
Thanks for the information.
All Replies
Joe,
Ownerid could be null becuase
1. Ownershiptimeout is not configured.
2. If timeout is configured, unloadonidle is set to true due to which whenever workflow becomes idle, it is unloaded (and unlocked which sets unlock = 1 and ownerid = null)
Sqlworkflowpersistenceservice takes care of setting the ownerid while loading a workflowinstance if locking mechanism is enabled. To enable it you must set the ownership timout in the constructor of the sqlworkflowpersistence service (or in the config file) of all the hosts that want to participate in instance locking. When a host loads the instance it will lock the instance for a time of Now + Ownershiptimeout. At this point if any other host tries to load this instance, it will get an ownershipexception. This makes sure that if there are multiple hosts pointing to the same persistence store and ownershiptimeout is configured, only one runtime will be able to load a workflow. This lock is released when the host unloads before the timeout has expired. One thing to note is if the host tries to unload at a time > ownershiptimeout it will not be able to do so because the ownership time has expired and it will get ownershipexception. When ever the lock is released, unlock is set to 1 and ownerid is set to null which indicates that workflowinstance is unlocked and any other host can load it.Tracing will give you more details about the workflow instance execution. http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=363145&SiteID=1
Hope this helps,
SonaliHi,
Thanks for your help but I still seem to be having a problem with this the constructor for the Persistence service is constructed with the following code:SqlWorkflowPersistenceService psserv = new SqlWorkflowPersistenceService(connectionString, false, new TimeSpan(0,10,0), new TimeSpan(0,0,30));
_runtime.AddService(psserv);When a previously unloaded workflow instance is loaded into memory a sql profiler trace shows this sql gets executed:
declare @P1 int
set @P1=0
declare @P2 uniqueidentifier
set @P2=NULL
exec RetrieveInstanceState @uidInstanceID = '525D84F2-18E4-48C2-9E50-B37F5C41AD3E', @ownerID = default, @ownedUntil = 'Apr 28 2006 10:11:43:120AM', @result = @P1 output,
@currentOwnerID = @P2 output
select @P1, @P2The SQl Persistence service seems to set an ownedUntil value but not an ownerId.
When I run two seperate processes each accessing the same persistence store they do not appear to lock each other out and I can have instances of the same workflow for both runtimes.
I hope somebody can help me out with this.
Many Thanks
JoeJoe,
Can you please check the values in instancestate table in persistence store after you unload and later after you load? Since you have set "unloadonidle" parameter to false, your workflow will not unload when it is idle. You have mentioned "a previously unloaded workflow is loaded..." what causes this unload? Are you explicitly unloading the workflow ? If you can give some information about your workflow it will help me to understand the behaviour you see.
Meanwhile here is a workflow to verify the ownership behaviour
Workflow
Delay ( 30 sec)
Code1
Console.ReadLine();
E1 ( HandleExternalEvent)
Code2
Console.ReadLine();SqlWorkflowPersistenceService psserv = new SqlWorkflowPersistenceService(connectionString, true, new TimeSpan(0,1,0), new TimeSpan(0,0,5));
I am setting second parameter (UnloadOnIdle) to true so that workflow will be unloaded when it has no work to doLets say there 3 hosts H1, H2 and H3 all pointing to same persistence store and using persistence service with settings as in psserv
1. Host H1 starts the workflow with instanceid equal to say 'myInstanceId'
2. Host H2 tries to load the workflow using GetWorkflow(myInstanceId)
3. Host H3 fires event E1 on the workflowLets walk through the execution of above workflow with these 3 hosts
1. Start executing H1
2. During execution, delay causes the workflow to be idle and 'unloadonilde' = true causes workflow to be unloaded.
3.Check db, you will see unlock=1 and ownerid = null , owneduntil = null because the WF is unloaded.
4. After the delay expires, workflow will be loaded in the host H1 and Code1 will start executing
5.While workflow is waiting in code1, check the db. You will notice ownerid and ownedunitl values are set, unlock = 0 which means host H1 has locked the instance.
6. Start second host H2 in less than 1 minute. Here you will get an ownership exception. This host will be able to load the instance, only after either H1 unloads the workflow or the ownershiptimeout expires.
7. Allow host H1 to execute the workflow by pressing any key in H1's command window.
8.Workflow will wait for event E1 to occur, go in idle state and unload. Again, unloading causes the instance to be unlocked. Check the db and you will see unlock = 1 , ownerid = null and owneduntil = null. This means at this point any other host is free to load and run this instance.
8. To do this, start host H3. H3 fires E1 which will load the workflow..E1, code2 will execute and workflow will wait at Console.Readline(). At this point values in db will be unlock =0 and ownerid, owneduntil are non-null
9. After you hit any key on H3 command window, workflow will complete and it will be deleted from the persistence storeHope this helps. I will upload the sample soon
-Sonali
Check the uploaded sample at http://blogs.msdn.com/sonalic/default.aspx
Hi,
Thanks for the sample. Sorry to take up so much of your time but running the sample I don't get the same behaviour as you describe in the readme.everything runs fine up to step 6, where the workflow is loaded back into memory and locked in the db. The locking does not occur when I run this and unlock remains set to 1 and both ownerId and ownedUntil are still null and no exception is raised when host 2 tries to load the instance.
There is a screenshot of it running, with both host1 and host2 holding the same instance in memory, here:
http://static.flickr.com/48/138956039_5303333710_o.jpg
Are we using different versions of the runtime as we seem to be seeing different behaviour. I have tried this on both the beta 2 and 2.2 versions and get the same problem.
Digging into the assembly it seems that all of the overloads on the constructor for SqlWorkflowPersistenceService initialise the instanceId to Guid.Empty and this does not seem to ever get set again. This is passed to the DBAccessor which replaces Guid.Empty with a null value to be passed over to the stored procedure. The stored procedure does not lock the workflowInstance record because of the following line that surrounds the locking and lock checking statements:
if @ownerID IS NOT NULL -- if id is null then just loading readonly state, so ignore the ownership checkSorry to be a pain, if this is resolved by a later version of the runtime I would appreciate your help in locating it.
Many thanks
JoeHi
I believe I'm having exactly the same issue. I'm investigating how to "load balance" between multiple hosts and running into trouble with multiple hosts loading the same instances at the same time. My investigation led me to the same conclusion: because the constructors for SqlWorkflowPersistenceService are setting the _serviceInstanceId to Guid.Empty, this leads it to pass NULL for owner id, hence no locking occurs.
Does anyone have an update on this issue yet?
Many thanks
Ian
- I think I have found a workaround. Using the overload for the SqlWorkflowPersistenceService that takes a NameValueCollection, you can configure the service like so:
NameValueCollection parameters = new NameValueCollection();
parameters.Add("ConnectionString", connectionString);
parameters.Add("OwnershipTimeoutSeconds", "10");
parameters.Add("UnloadOnIdle", true.ToString() );
parameters.Add("LoadIntervalSeconds", "1");
SqlWorkflowPersistenceService persistenceService = new SqlWorkflowPersistenceService(parameters);
_workflowRuntime.AddService(persistenceService);This constructor does set the _serviceInstanceId to a new Guid, and so the locking mechanism works. In my tests, I can show now show that two hosts cannot process the same flow at the same time.
As far as I can tell, the fact that the other two overloads don't set this field is a bug?
By the way this was using WinFX beta 2. Perhaps this has changed or is fixed later...
Regards
Ian
Ian,
You are right. On following up, I found there was a bug in 2.2 and locking is enabled only if you use the NameValueCollection constructor. This is fixed in RTM and locking will be enabled if you use either NameValueCollection ctor or the ctor which sets the ownershiptimeout explicitly.
Thanks for the information.
I have been reading your posts and I have somewhat a similar problem with Instance locking after a Delay Activity in a multiple host multiple workflow type environment. In the above scenario Instance Locking seems to work because the three hosts are using the same type of workflow. If you consider two different hosts A and B, A hosting workflow type W1 and B hosting workflow type W2. After Delay activity the workflow instance W1 gets persisted with OwnerId = Null, OwnedUntil = Null, UnLock = 1 and NextTimer = CurrentTime + delay. This tells me that any WorkflowPersistenceService pointing to this particular database can try to load this instance after the NextTimer is Expired. The problem is If Host B's Persistence Service trys to load workflow type W1 which it is not supposed to load, the following Exception will be thrown by the runtime
"Could not load file or assembly 'W1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified."
This time it is impossible for the right host to load the instance(The instance will not resume execution after NextTimer is expired).
We have the following things already taken care of:
1. Installed the RTM
2.Using the Persistence constructor that takes the ownershiptimeout=maxval, persistOnIdle=true, loadinginterval=2min
3. We also tried the NameValueCollection constructor, but didn't work either
Please let me know if anyone knows how to fix this issue?
Hi,
Thanks for your help but I still seem to be having a problem with this the constructor for the Persistence service is constructed with the following code:SqlWorkflowPersistenceService psserv = new SqlWorkflowPersistenceService(connectionString, false, new TimeSpan(0,10,0), new TimeSpan(0,0,30));
_runtime.AddService(psserv);When a previously unloaded workflow instance is loaded into memory a sql profiler trace shows this sql gets executed:
declare @P1 int
set @P1=0
declare @P2 uniqueidentifier
set @P2=NULL
exec RetrieveInstanceState @uidInstanceID = '525D84F2-18E4-48C2-9E50-B37F5C41AD3E', @ownerID = default, @ownedUntil = 'Apr 28 2006 10:11:43:120AM', @result = @P1 output,
@currentOwnerID = @P2 output
select @P1, @P2The SQl Persistence service seems to set an ownedUntil value but not an ownerId.
____________

