Plugins: Tasks

Another type of plugins gives us the ability to perform various actions during server/database startup process or enables us to perform actions periodically. For these instances we have introduced two interfaces and one abstract class.

public interface IStartupTask
{
	void Execute(DocumentDatabase database);
}

public interface IServerStartupTask
{
	void Execute(RavenDbServer server);
}

public abstract class AbstractBackgroundTask : IStartupTask
{
	private static readonly ILog log = LogManager.GetCurrentClassLogger();

	public DocumentDatabase Database { get; set; }

	public void Execute(DocumentDatabase database)
	{
		Database = database;
		Initialize();
		Task.Factory.StartNew(BackgroundTask, TaskCreationOptions.LongRunning);
	}

	protected virtual void Initialize()
	{
	}

	int workCounter;
	public void BackgroundTask()
	{
		string name = GetType().Name;
		WorkContext context = Database.WorkContext;
		while (context.DoWork)
		{
			bool foundWork = false;
			try
			{
				foundWork = HandleWork();
			}
			catch (Exception e)
			{
				log.ErrorException("Failed to execute background task", e);
			}
			if (foundWork == false)
			{
				context.WaitForWork(TimeoutForNextWork(), ref workCounter, name);
			}
			else
			{
				context.UpdateFoundWork();
			}
		}
	}

	protected virtual TimeSpan TimeoutForNextWork()
	{
		return TimeSpan.FromHours(1);
	}

	protected abstract bool HandleWork();
}

where:
* IStartupTask can be used to implement a task which will be started during database initialization.
* IServerStartupTask can be used to implement a task which will be started during server initialization.
* AbstractBackgroundTask is a base for all periodic tasks.

Example - Send email when server is starting

public class SendEmailWhenServerIsStartingTask : IServerStartupTask
{
	public void Execute(RavenDbServer server)
	{
		MailMessage message = new MailMessage("ravendb@myhost.com", "admin@myhost.com")
		{
			Subject = "RavenDB server started.",
			Body = "Start at: " + DateTime.Now.ToShortDateString()
		};

		using (SmtpClient smtpClient = new SmtpClient("mail.myhost.com"))
		{
			smtpClient.Send(message);
		}
	}
}

Example - Perform a cleanup task during database initialization

public class CleanupWhenDatabaseIsStarting : IStartupTask
{
	private const string SpecificDatabaseName = "Northwind";

	public void Execute(DocumentDatabase database)
	{
		if (database.Name != SpecificDatabaseName)
			return;

		using (CancellationTokenSource cts = new CancellationTokenSource())
		{
			bool stale;
			IEnumerable<string> queryResults = database.Queries.QueryDocumentIds(
				"Notifications/Temp",
				new IndexQuery(),
				CancellationTokenSource.CreateLinkedTokenSource(database.WorkContext.CancellationToken, cts.Token),
				out stale);

			foreach (string documentId in queryResults)
			{
				database.Documents.Delete(documentId, null, null);
			}
		}
	}
}

Example - Perform a cleanup task every six hours

public class RemoveAllTemporaryNotificationsTask : AbstractBackgroundTask
{
	protected override bool HandleWork()
	{
		QueryResultWithIncludes queryResults = Database.Queries.Query("Notifications/Temp", new IndexQuery(), Database.WorkContext.CancellationToken);
		foreach (RavenJObject document in queryResults.Results)
		{
			string id = ((RavenJObject)document["@metadata"]).Value<string>("@id");
			Database.Documents.Delete(id, null, null);
		}

		return true;
	}

	protected override TimeSpan TimeoutForNextWork()
	{
		return TimeSpan.FromHours(6);
	}
}