Second Life of a Hungarian SharePoint Geek

October 12, 2015

Waiting for Project Server Queue Operations in Server Side Code

Filed under: PS 2013 — Tags: — Peter Holpar @ 23:48

Recently we found a bug in our server-side Project Server code. Our goal was to set some project-related enterprise custom field values. Before setting the values, we tested, if the project is checked-out (to another user), and if so, we forced a a check-in, to be able to check out again to ourselves. After setting the values, we updated the project and published it, including check-in.

  1. if (proj.IsCheckedOut)
  2. {
  3.     proj.Draft.CheckIn(true);
  4. }
  5. DraftProject draftProj = proj.CheckOut();
  6. // set some custom field values
  7. draftProj.SetCustomFieldValue("customFieldName", "customFieldValue");
  8. draftProj.Update();
  9. draftProj.Publish(true);

If the project was not checked-out, the code worked as expected. However, if the project was checked-out to a use, we got an exception despite of the test on the line

DraftProject draftProj = proj.CheckOut();

The exception was:

Microsoft.ProjectServer.PJClientCallableException was unhandled
  _HResult=-2146233088
  _message=CICOCheckedOutInOtherSession
  HResult=-2146233088
  IsTransient=false
  Message=CICOCheckedOutInOtherSession
  Source=Microsoft.ProjectServer
  PSErrorCode=10103
  PSErrorName=CICOCheckedOutInOtherSession
  StackTrace:
       at Microsoft.ProjectServer.PublishedProject.CheckOut()
       at SPGeneral.Program.Main(String[] args) in c:\projects\PSTest\Program.cs:line 37
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

The message “CICOCheckedOutInOtherSession” suggested, that the project is not yet checked-in, although our traces showed that the line proj.Draft.CheckIn(true) was executed.

To understand the source of the problem, we should first understand how the CheckIn / CheckOut and Publish methods are executed:

Both of the CheckIn and Publish methods of the DraftProject class are executed asynchronously. These methods return a QueueJob object. The actual job is performed by the queue subsystem of Project Server.

The CheckOut method of the PublishedProject class is executed synchronously, it returns a DraftProject instance immediately.

In the above sample it means, that proj.CheckOut() is called, before the project would be effectively checked-in as a result of executing proj.Draft.CheckIn(true).

How to solve the problem, keeping our code readable if possible?

If you are working with client-side object model of Project Server, you know probably, that there is a solution for such issues: the WaitForQueue method of the ProjectContext object.Unfortunately, the equivalent of this method is not implemented in the server-side object model. It’s pretty strange, as usually its opposite is the case: methods and properties available on the server-side are many times missing on the client-side OM.

No problem, we can implement a similar method for ourselves!

The WaitForQueue method “wait for the specified queue job to complete, or for a maximum number of seconds”. It returns a JobState object, that should be JobState.Success if the queue job succeeded. If we have a look at the implementation of the method, we can see, that it calls the internal IsPendingJob method of the ProjectContext object to compare the current state of the job with the expected values, the refresh the job status by polling the server side objects. The wait time is decremented by 2 seconds in every iteration, the IsPendingJob method is responsible to sleep the thread for this two seconds.

Note: It means that the maximum wait time specified when calling WaitForQueue method is only an approximate value, as it does not include the send / response time of the server requests involved in the refreshing the job status, for example a one-minute wait time means 30 iterations, that is 30 requests / responses. So don’t be surprise if your wait times are considerably longer than specified if you have a slow network or busy server.

After this theory, lets see our own implementation:

  1. public static bool IsPending(this QueueJob job, out QueueConstants.JobState state)
  2. {
  3.         state = job.JobState;
  4.         switch (state)
  5.         {
  6.             case QueueConstants.JobState.Unknown:
  7.             case QueueConstants.JobState.ReadyForProcessing:
  8.             case QueueConstants.JobState.SendIncomplete:
  9.             case QueueConstants.JobState.Processing:
  10.             case QueueConstants.JobState.ProcessingDeferred:
  11.             case QueueConstants.JobState.OnHold:
  12.             case QueueConstants.JobState.Sleeping:
  13.             case QueueConstants.JobState.ReadyForLaunch:
  14.                 Thread.Sleep(new TimeSpan(0, 0, 2));
  15.                 return true;
  16.         }
  17.     state = QueueConstants.JobState.Success;
  18.     return false;
  19. }
  20.  
  21. public static QueueConstants.JobState WaitToFinish(this QueueJob job, int timeoutSeconds)
  22. {
  23.     QueueConstants.JobState state = QueueConstants.JobState.Unknown;
  24.     while ((timeoutSeconds > 0) && job.IsPending(out state))
  25.     {
  26.         timeoutSeconds -= 2;
  27.     }
  28.     return state;
  29. }

As you can see, instead of extending the PSContext object with a WaitForQueue method, I decided to extend the QueueJob object itself with a WaitToFinish method. It seems to me simply more appropriate.

Note: As you probably know, and as I mentioned in my former posts, the server-side object model is based on the PSI infrastructure. It means, that the extra wait time mentioned above may apply to this solution as well.

Note 2: To be able to use the JobState enumeration value in your code, you should reference the Microsoft.Office.Project.Server.Library assembly in your project.

The modified logic in our application is displayed below:

  1. var timeOutInSec = 60; // wait max a minute
  2. bool canCheckOut = !proj.IsCheckedOut;
  3. if (!canCheckOut)
  4. {
  5.     // Project is checked out. Forcing check-in.
  6.     var job = proj.Draft.CheckIn(true);
  7.     var jobState = job.WaitToFinish(timeOutInSec);
  8.     if (jobState == QueueConstants.JobState.Success)
  9.     {
  10.         canCheckOut = true;
  11.     }
  12.     else
  13.     {
  14.         // WARNING Time-out on project check-in, or job failed
  15.     }
  16. }
  17.  
  18. if (canCheckOut)
  19. {
  20.     DraftProject draftProj = proj.CheckOut();
  21.     // set some custom field values
  22.     draftProj.SetCustomFieldValue("customFieldName", "customFieldValue");
  23.     draftProj.Update();
  24.  
  25.     // Publishing project (incl. check-in!)
  26.     var job = draftProj.Publish(true);
  27.     var jobState = job.WaitToFinish(Constants.DefaultPSJobTimeOut);
  28.     if (jobState == QueueConstants.JobState.Success)
  29.     {
  30.         // Project checked-in + published.
  31.     }
  32.     else
  33.     {
  34.         // WARNING Time-out on project publish / check-in or job failed
  35.     }
  36. }
  37. else
  38. {
  39.     // WARNING Project can not be checked-out / processed
  40. }

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: