Where is My Timer Job? 

Tags: MOSS 2007 Archived Post

SharePoint Timer Jobs are great for accomplishing tasks asynchronously throughout the farm. You can create your own custom Timer Job by inheriting from SPJobDefinition. Recently a question arose regarding the exact nature of custom Timer Job execution. Specifically, which servers in the farm actually run the Timer Job?

Well, it turns out to be a pretty simple answer. The SPJobDefinition class has a property called LockType. When set to SPJobLockType.Job then the job only runs on the server where the job was submitted. If the value is SPJobLockType.None, then the job runs on every server in the farm.

Creating a Test Timer Job

In order to test out the various LockType values, I created a simple Timer Job that does nothing except increment a value in a database table. The idea is that a single field in a single database table would be incremented by all Timer Job instances. This way, I could count the number of times the job ran in the farm. My test farm had 2 web front end servers and a single application server.

 

Creating the Timer Job itself was reasonably straightforward. I created a new class that inherited from SPJobDefinition. Within the class, I could set the LockType property for the various tests. I also included a SubmitJob() method for submitting a new Timer Job instance. The following code shows the Timer Job I created, and you can download the complete code here. Note the value for the LockType property.

public class Ticker : SPJobDefinition
{
    public Ticker() : base() { }
    public Ticker(string jobName, SPWebApplication webApplication)
        : base(jobName, webApplication, null, SPJobLockType.Job)
    { this.Title = "Ticker"; }

    const string CONNECTION_STRING = "Server=MySQLServer\\MOSS_SQL2K5;Database=Jobs;Trusted_Connection=yes;";

    public override void Execute(Guid targetInstanceId)
    {
        try
        {
            using (SqlConnection conn = new SqlConnection())
            {
                conn.ConnectionString = CONNECTION_STRING;
                conn.Open();

                SqlCommand command = conn.CreateCommand();
                command.CommandType = CommandType.Text;
                command.CommandText = "UPDATE Ticks SET Count = Count + 1";
                command.ExecuteNonQuery();

                conn.Close();

            }
        }
        catch (Exception x)
        {
            TraceProvider.RegisterTraceProvider();
            TraceProvider.WriteTrace(TraceProvider.TagFromString("Ticks"), TraceProvider.StringToSeverity("Exception"), Guid.NewGuid(), "Execute", "TimerJobCounts", "Test", x.Message);
            TraceProvider.UnregisterTraceProvider();
        }
    }

    public void SubmitJob()
    {
        Schedule = new SPOneTimeSchedule(DateTime.Now);
        Title = "Ticker (" + (new Guid()).ToString() + ")";
        Update();
    }
}

 

Testing the Timer Job

Once the Timer Job class was created, I installed it into the GAC on each of the three servers in the farm. Then I created a simple console application to submit new job instances. The following code shows the console application.

class Program
{
    static void Main(string[] args)
    {
        try
        {
            SPWebApplicationCollection webApps = SPWebService.ContentService.WebApplications;

            TimerJobCounts.Ticker ticker = new TimerJobCounts.Ticker(args[0], webApps[args[1]]);
            ticker.SubmitJob();
        }
        catch (Exception x)
        {
            Console.WriteLine(x.Message);
            Console.WriteLine("First argument is a Title for the job.");
            Console.WriteLine("Second argument is Name of Web Application");
            Console.WriteLine("");

            SPWebApplicationCollection webAppCollection = SPWebService.ContentService.WebApplications;
            foreach (SPWebApplication webApp in webAppCollection)
            {
                Console.WriteLine(webApp.Name);
            }

        }
    }
}

For the tests, I first ran the Job Compiled with the LockType set to Job. I manually set the database value to zero, and then ran the Timer Job using the Console Application. The Timer Job immediately appeared in the list of scheduled jobs.

 

After the job ran, I checked the database, and sure enough the count had been incremented to 1. This confirmed that the job ran only on the server where it was submitted - that it the server where I ran the console application.

For the next test, I recompiled the Timer Job with the LockType set to None. After deploying the assembly again to the GAC on every server, I cleared the database value back to zero, and reran the job. This time the database value was incremented to 3 after the job ran, indicating that the job ran on every server in the farm.

 

Conclusion

When creating a custom Timer Job, you must decide whether you want the job to run only once for the farm or once on every server. Jobs that update files in the 12 directory, for example, generally need to run on every server. On the other hand, jobs that update a database may only need to run once.

 
Posted by Scot Hillier on 18-Jan-10
0 Comments  |  Trackback Url  |  Link to this post | Bookmark this post with:        
 

Links to this post

Comments