Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 319939 - Race condition in PlatformResourceURIHandlerImpl.
Summary: Race condition in PlatformResourceURIHandlerImpl.
Status: CLOSED FIXED
Alias: None
Product: EMF
Classification: Modeling
Component: Core (show other bugs)
Version: unspecified   Edit
Hardware: Macintosh Mac OS X
: P3 blocker with 1 vote (vote)
Target Milestone: ---   Edit
Assignee: Ed Merks CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 324520
  Show dependency tree
 
Reported: 2010-07-15 01:25 EDT by Nicolas Rouquette CLA
Modified: 2010-11-05 05:41 EDT (History)
4 users (show)

See Also:


Attachments
HelloWorld project for reproducing the EMF.Core PlatformResourceURIHandlerImpl race condition bug. (2.19 KB, application/octet-stream)
2010-07-15 01:31 EDT, Nicolas Rouquette CLA
no flags Details
Patch to fix the race condition in PlatformResourceURIHandlerImpl.flush() (1.69 KB, text/plain)
2010-07-15 02:07 EDT, Nicolas Rouquette CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Nicolas Rouquette CLA 2010-07-15 01:25:50 EDT
The EMF.Core PlatformResourceURIHandlerImpl has one weakness that can lead to a race condition between refreshing Eclipse resources & saving EMF Resources.

To demonstrate this, I wrote an Ant script that executes 2 tasks:
- a cleanup task that deletes ecore resources
- QVTo transformation that creates an ecore resource

When the 2 tasks execute for the first time, there's nothing to cleanup, the QVT transformation executes cleanly:


Buildfile: /Volumes/FMTools/Helios1/runtime.imce/EMF.PlatformResourceURIHandler.RaceConditionBug/build.xml

Clean:

Transform:
[qvto:transformation] QVTo Hello World
[qvto:transformation] Transformation 'platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/transforms/HelloWorld.qvto' has been executed

CleanAndTransform:
BUILD SUCCESSFUL
Total time: 1 second

When invoking the Ant script a second time, the cleanup task deletes the HelloWorld.ecore resource before launching the QVT transformation task.
Because Ant is running in the Eclipse environment, the Eclipse workspace may not have been refreshed between the first and second task.
This exacerbates the weakness in EMF.Core's PlatformResourceURIHandlerImpl enough to trip the following exception:

Buildfile: /Volumes/FMTools/Helios1/runtime.imce/EMF.PlatformResourceURIHandler.RaceConditionBug/build.xml

Clean:
      [delete] Deleting /Volumes/FMTools/Helios1/runtime.imce/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore

Transform:
[qvto:transformation] QVTo Hello World

BUILD FAILED
/Volumes/FMTools/Helios1/runtime.imce/EMF.PlatformResourceURIHandler.RaceConditionBug/build.xml:13: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.

Total time: 1 second

The error message is incorrect; to appreciate this, we need to look at the stack trace information more closely:

!SUBENTRY 1 org.eclipse.ant.core 4 1 2010-07-14 22:13:38.615
!MESSAGE /Volumes/FMTools/Helios1/runtime.imce/EMF.PlatformResourceURIHandler.RaceConditionBug/build.xml:13: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
!STACK 0
/Volumes/FMTools/Helios1/runtime.imce/EMF.PlatformResourceURIHandler.RaceConditionBug/build.xml:13: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
	at org.eclipse.m2m.internal.qvt.oml.runtime.ant.QvtoAntTransformationTask.execute(QvtoAntTransformationTask.java:332)
	at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:288)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
	at org.apache.tools.ant.Task.perform(Task.java:348)
	at org.apache.tools.ant.Target.execute(Target.java:357)
	at org.apache.tools.ant.Target.performTasks(Target.java:385)
	at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1337)
	at org.apache.tools.ant.Project.executeTarget(Project.java:1306)
	at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
	at org.eclipse.ant.internal.core.ant.EclipseDefaultExecutor.executeTargets(EclipseDefaultExecutor.java:32)
	at org.apache.tools.ant.Project.executeTargets(Project.java:1189)
	at org.eclipse.ant.internal.core.ant.InternalAntRunner.run(InternalAntRunner.java:662)
	at org.eclipse.ant.internal.core.ant.InternalAntRunner.run(InternalAntRunner.java:495)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.eclipse.ant.core.AntRunner.run(AntRunner.java:378)
	at org.eclipse.ant.internal.launching.launchConfigurations.AntLaunchDelegate$1.run(AntLaunchDelegate.java:298)
	at java.lang.Thread.run(Thread.java:637)
Caused by: org.eclipse.m2m.internal.qvt.oml.common.MdaException: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
	at org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegateBase.saveTransformationResult(QvtLaunchConfigurationDelegateBase.java:256)
	at org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegateBase.doLaunch(QvtLaunchConfigurationDelegateBase.java:202)
	at org.eclipse.m2m.internal.qvt.oml.runtime.ant.QvtoAntTransformationTask$1.run(QvtoAntTransformationTask.java:317)
	at org.eclipse.m2m.internal.qvt.oml.common.launch.SafeRunner$SameThreadRunner.run(SafeRunner.java:33)
	at org.eclipse.m2m.internal.qvt.oml.common.launch.SafeRunner$1.run(SafeRunner.java:26)
	at org.eclipse.m2m.internal.qvt.oml.runtime.ant.QvtoAntTransformationTask.execute(QvtoAntTransformationTask.java:325)
	... 23 more
Caused by: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
	at org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtil.saveModel(EmfUtil.java:198)
	at org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegateBase.saveTransformationResult(QvtLaunchConfigurationDelegateBase.java:253)
	... 28 more
Caused by: org.eclipse.emf.ecore.resource.Resource$IOWrappedException: Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
	at org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl$PlatformResourceOutputStream.flush(PlatformResourceURIHandlerImpl.java:138)
	at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:278)
	at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:122)
	at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
	at org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl.write(XMLSaveImpl.java:1010)
	at org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl.save(XMLSaveImpl.java:266)
	at org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl.doSave(XMLResourceImpl.java:206)
	at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:1406)
	at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:993)
	at org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtil.saveModel(EmfUtil.java:196)
	... 29 more
Caused by: org.eclipse.core.internal.resources.ResourceException: Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
	at org.eclipse.core.internal.resources.Resource.checkExists(Resource.java:326)
	at org.eclipse.core.internal.resources.Resource.checkAccessible(Resource.java:200)
	at org.eclipse.core.internal.resources.File.setContents(File.java:361)
	at org.eclipse.core.internal.resources.File.setContents(File.java:468)
	at org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl$PlatformResourceOutputStream.flush(PlatformResourceURIHandlerImpl.java:131)
	... 38 more
--- Nested Exception ---
org.eclipse.m2m.internal.qvt.oml.common.MdaException: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
	at org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegateBase.saveTransformationResult(QvtLaunchConfigurationDelegateBase.java:256)
	at org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegateBase.doLaunch(QvtLaunchConfigurationDelegateBase.java:202)
	at org.eclipse.m2m.internal.qvt.oml.runtime.ant.QvtoAntTransformationTask$1.run(QvtoAntTransformationTask.java:317)
	at org.eclipse.m2m.internal.qvt.oml.common.launch.SafeRunner$SameThreadRunner.run(SafeRunner.java:33)
	at org.eclipse.m2m.internal.qvt.oml.common.launch.SafeRunner$1.run(SafeRunner.java:26)
	at org.eclipse.m2m.internal.qvt.oml.runtime.ant.QvtoAntTransformationTask.execute(QvtoAntTransformationTask.java:325)
	at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:288)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
	at org.apache.tools.ant.Task.perform(Task.java:348)
	at org.apache.tools.ant.Target.execute(Target.java:357)
	at org.apache.tools.ant.Target.performTasks(Target.java:385)
	at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1337)
	at org.apache.tools.ant.Project.executeTarget(Project.java:1306)
	at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41)
	at org.eclipse.ant.internal.core.ant.EclipseDefaultExecutor.executeTargets(EclipseDefaultExecutor.java:32)
	at org.apache.tools.ant.Project.executeTargets(Project.java:1189)
	at org.eclipse.ant.internal.core.ant.InternalAntRunner.run(InternalAntRunner.java:662)
	at org.eclipse.ant.internal.core.ant.InternalAntRunner.run(InternalAntRunner.java:495)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.eclipse.ant.core.AntRunner.run(AntRunner.java:378)
	at org.eclipse.ant.internal.launching.launchConfigurations.AntLaunchDelegate$1.run(AntLaunchDelegate.java:298)
	at java.lang.Thread.run(Thread.java:637)
Caused by: org.eclipse.m2m.internal.qvt.oml.emf.util.EmfException: Failed to save model to platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore 
	at org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtil.saveModel(EmfUtil.java:198)
	at org.eclipse.m2m.internal.qvt.oml.runtime.launch.QvtLaunchConfigurationDelegateBase.saveTransformationResult(QvtLaunchConfigurationDelegateBase.java:253)
	... 28 more
Caused by: org.eclipse.emf.ecore.resource.Resource$IOWrappedException: Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
	at org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl$PlatformResourceOutputStream.flush(PlatformResourceURIHandlerImpl.java:138)
	at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:278)
	at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:122)
	at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:212)
	at org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl.write(XMLSaveImpl.java:1010)
	at org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl.save(XMLSaveImpl.java:266)
	at org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl.doSave(XMLResourceImpl.java:206)
	at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:1406)
	at org.eclipse.emf.ecore.resource.impl.ResourceImpl.save(ResourceImpl.java:993)
	at org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtil.saveModel(EmfUtil.java:196)
	... 29 more
Caused by: org.eclipse.core.internal.resources.ResourceException: Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
	at org.eclipse.core.internal.resources.Resource.checkExists(Resource.java:326)
	at org.eclipse.core.internal.resources.Resource.checkAccessible(Resource.java:200)
	at org.eclipse.core.internal.resources.File.setContents(File.java:361)
	at org.eclipse.core.internal.resources.File.setContents(File.java:468)
	at org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl$PlatformResourceOutputStream.flush(PlatformResourceURIHandlerImpl.java:131)
	... 38 more
	
The problem comes from: org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl$PlatformResourceOutputStream.flush


    @Override
    public void flush() throws IOException
    {
      super.flush();

      if (previouslyFlushed)
      {
        if (count == 0)
        {
          return;
        }
      }
      else
      {
        createContainer(file.getParent());
      }

      byte[] contents = toByteArray();
      InputStream inputStream = new ByteArrayInputStream(contents, 0, contents.length);

      try
      {
        if (previouslyFlushed) // *1*
        {
          file.appendContents(inputStream, force, false, progressMonitor);
        }
        else if (!file.exists()) // *2*
        {
          file.create(inputStream, false, null);
          previouslyFlushed = true;
        }
        else
        { 
          // *3*
          if (!file.isSynchronized(IResource.DEPTH_ONE))
          {
            file.refreshLocal(IResource.DEPTH_ONE, progressMonitor);
          }
          // *4*
          file.setContents(inputStream, force, keepHistory, progressMonitor); // line 131
          previouslyFlushed = true;
        }
        reset();
      }
      catch (CoreException exception)
      {
        throw new Resource.IOWrappedException(exception);
      }
    }

The vulnerability comes from *1* where previouslyFlushed = false and the stale state of the information about files that results from the 1st Cleanup Ant task. 

This stale state results in file.exists() = true at step *2* until the code forces a refresh at step *3*.
However, at step *4*, the information about the file is no longer stale, the file has truly been deleted!
Consequently, the code will fail to execute file.setContents() at line 131 as confirmed by the exception trace:

Caused by: org.eclipse.core.internal.resources.ResourceException: Resource '/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore' does not exist.
	at org.eclipse.core.internal.resources.Resource.checkExists(Resource.java:326)
	at org.eclipse.core.internal.resources.Resource.checkAccessible(Resource.java:200)
	at org.eclipse.core.internal.resources.File.setContents(File.java:361)
	at org.eclipse.core.internal.resources.File.setContents(File.java:468)
	at org.eclipse.emf.ecore.resource.impl.PlatformResourceURIHandlerImpl$PlatformResourceOutputStream.flush(PlatformResourceURIHandlerImpl.java:131)

In this example, at *1*, previouslyFlushed = false.
at *2*, file.exists() = true because of stale information
at *3*, the refresh forces an update of the information; i.e., the file truly does not exist because the previous task deleted it!
Comment 1 Nicolas Rouquette CLA 2010-07-15 01:31:35 EDT
Created attachment 174370 [details]
HelloWorld project for reproducing the EMF.Core PlatformResourceURIHandlerImpl race condition bug.

Extract the project to the Eclipse workspace (you need QVTo).
Launch the ant script twice to reproduce the race condition bug as described.
Comment 2 Nicolas Rouquette CLA 2010-07-15 02:07:08 EDT
Created attachment 174371 [details]
Patch to fix the race condition in PlatformResourceURIHandlerImpl.flush()

With this patch, the ant script runs properly and there is no race condition due to the staleness of file information for saving EMF resources.
Comment 3 Ed Merks CLA 2010-07-15 11:34:33 EDT
A blocker implies you have no workaround, but it sounds like doing a refresh would avoid the problem.  Moving the isSynchronized test earlier looks like a good fix.
Comment 4 Nicolas Rouquette CLA 2010-07-16 19:40:18 EDT
I also thought that <eclipse.refreshLocal> would do the trick but as shown in the attached ant script shown below, the problem is still there despite the refresh.

<eclipse.convertPath resourcePath="/EMF.PlatformResourceURIHandler.RaceConditionBug" property="TOP"/>
	
	<target name="Clean">
		<delete failonerror="true" verbose="true">
			<fileset dir="${TOP}/models" includes="*.ecore"/>
		</delete>	
		<eclipse.refreshLocal resource="${TOP}/models" depth="one"/>	
	</target>
	
	<target name="Transform">
		<qvto:transformation uri="platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/transforms/HelloWorld.qvto">
			<out uri="platform:/resource/EMF.PlatformResourceURIHandler.RaceConditionBug/models/HelloWorld.ecore"/>
		</qvto:transformation>
	</target>
			
	<target name="CleanAndTransform" depends="Clean,Transform"/>

Your suggestion makes sense though because the proposed patch effectively inserts an <eclipse.refreshLocal> for a resource file prior to writing its contents when it is not in sync.

This bug is very misleading because it gives a false impression that there is a problem with a task that writes an EMF resource using the PlatformResourceURIHandlerImpl when in fact the problem is a race condition in the handler instead of the task.
Comment 5 Nicolas Rouquette CLA 2010-07-21 12:15:28 EDT
Can this bug be fixed for EMF 2.6.1?
Comment 6 Ed Merks CLA 2010-07-21 20:52:59 EDT
Given that I've already started a 2.6.1 maintenance stream and that the fix for this is simple and low risk (and that NASA did REALLY COOL STUFF at EclipseCon!!!) I'm certain inclined to invest the effort required to apply this fix to the maintenance stream.
Comment 7 Ed Merks CLA 2010-09-04 13:16:32 EDT
The fix is committed to CVS for 2.6.
Comment 8 Ed Merks CLA 2010-11-05 05:41:38 EDT
The fix is available in the latest build for the stream.