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.