Batching resource changes

When you need to modify resources in the workspace, it is important to keep in mind that other plug-ins might be working with the same resources. The resources API provides robust mechanisms for keeping plug-ins informed about changes in the workspace, and for making sure that multiple plug-ins do not modify the same resource at the same time. Where possible, your plug-in's modifications to the workspace should be batched in units of work inside a workspace runnable. These runnables help to reduce the amount of change notifications generated by changes. They also allow you to declare which part of the workspace is to be modified, so that other plug-ins can be locked out of changing the same part of the workspace.

The protocol for IWorkspaceRunnable is fairly simple. A workspace runnable looks just like a long-running operation or platform job. The actual work is done inside a run method, with progress reported to the supplied IProgressMonitor. Code that manipulates the workspace is performed inside the run method.

IWorkspaceRunnable myRunnable = 
	new IWorkspaceRunnable() {
		public void run(IProgressMonitor monitor) throws CoreException {
			//do the actual work in here
			...
		}
}

When it is time to the run the code, your plug-in tells the workspace to run the code on its behalf. This way, the workspace can generate any necessary change events and ensure that no two plug-ins are modifying the same resource at the same time. (Even if your plug-in is not using background jobs and the concurrency framework to modify the workspace, other plug-ins may be doing so.)

Scheduling rules and locking

IWorkspace protocol is used to run a workspace runnable. The preferred technique is using the long form of the run method which supplies a scheduling rule and specifies how resource change events are broadcast.

Specifying a scheduling rule when running a workspace runnable allows the workspace to determine whether the resource changes will conflict with workspace changes happening in other threads. (See Scheduling rules for an overview of scheduling rules and ISchedulingRule protocol.) Fortunately, IResource protocol includes the protocol for ISchedulingRule, which means that a resource can often be used as a scheduling rule for itself.

Confused? Code can help to clarify this point. Suppose your plug-in is getting ready to modify a bunch of resources in a particular project. It can use the project itself as the scheduling rule for making the changes. The following snippet runs the workspace runnable that we created earlier:

IWorkspace workspace = ResourcesPlugin.getWorkspace();
workspace.run(myRunnable, myProject, IWorkspace.AVOID_UPDATE, null);

The runnable is passed to the workspace, followed by the project that the code is manipulating. This tells the workspace that all of the changes in the runnable are confined to myProject. Any requests by other threads to change myProject will be blocked until this runnable completes. Likewise, this call will block if some other thread is already modifying myProject. By specifying which part of the resource tree will be modified by the runnable, you are allowing other threads to continue modifying other portions of the workspace. It is important to be sure that your resource rule matches the work being done inside the runnable. When a non-null scheduling rule is used, any attempt to access a resource outside the scope of the scheduling rule will trigger an exception.

There are two special scheduling rules that are important to consider. First, if you use an instance of IWorkspaceRoot as the scheduling rule, it means your thread is blocking access to all resources in the tree. While a thread holds the root rule, no other thread is allowed to modify the workspace. Conversely, a rule of null indicates that the thread will block access to no resources in the tree. While a thread with the null rule is free to modify the workspace itself, other threads will not be prevented from performing their own modifications. The IWorkspaceRoot and null scheduling rules occupy opposite ends of the concurrency spectrum. With IWorkspaceRoot there is no concurrency and only one thread is modifying the workspace at a time. With a null rule, there is maximum concurrency as all threads can modify the workspace concurrently.

The third parameter to the run method specifies whether any periodic resource change events should be broadcast during the scope of this call. Using IWorkspace.AVOID_UPDATE tells the platform to suppress any resource change events while the runnable is running and to broadcast one event at the end of the changes. During this call, any other runnables created in the runnable will be considered part of the parent batch operation. Resource changes made in those runnables will appear in the parent's resource change notification.

Resource rule factory

In the example above, we assumed that the code inside our runnable only modified resources in a particular project. This made it very easy to specify a scheduling rule for the runnable. In practice, it can be more difficult to compute what parts of the workspace are affected by a particular change. For example, moving a resource from one project to another affects both projects. IResourceRuleFactory can be used to help compute an appropriate resource rule for certain kinds of resource changes. You can get a resource rule factory from the workspace itself.

IWorkspace workspace = ResourcesPlugin.getWorkspace();
IResourceRuleFactory ruleFactory = workspace.getRuleFactory();

The factory can supply rules appropriate for many kinds of operations. If your runnable is moving a resource from one location to another, it can obtain a rule appropriate for this operation:

ISchedulingRule movingRule = ruleFactory.moveResource(sourceResource, destinationResource);
workspace.run(myRunnable, movingRule, IWorkspace.AVOID_UPDATE, null);

See the javadoc for IResourceRuleFactory for the list of available rules. The resources plug-in uses these rules itself to implement most resource operations. Browsing the code that references these rule methods will help demonstrate how they are used in practice.

Multiple rules can be combined using MultiRule.

ISchedulingRule movingRule = ruleFactory.moveResource(sourceResource, destinationResource);
ISchedulingRule modifyRule = ruleFactory.modifyResource(destinationResource);
workspace.run(myRunnable, MultiRule.combine(movingRule, modifyRule), IWorkspace.AVOID_UPDATE, null);

Ignoring the rules

The short form of the run method in IWorkspace is also available. It is retained for backward compatibility. The short form does not include a rule or an update flag.

workspace.run(myRunnable, null);

is effectively the same as calling

workspace.run(myRunnable, workspace.getRoot(), IWorkspace.AVOID_UPDATE, null);

Specifying the workspace root as the scheduling rule will put a lock on the entire workspace until the runnable is finished. This is the most conservative way to perform a workspace update, but it is not very friendly to other concurrency-minded plug-ins.