Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 319939

Summary: Race condition in PlatformResourceURIHandlerImpl.
Product: [Modeling] EMF Reporter: Nicolas Rouquette <nicolas.f.rouquette>
Component: CoreAssignee: Ed Merks <Ed.Merks>
Status: CLOSED FIXED QA Contact:
Severity: blocker    
Priority: P3 CC: axel.reichwein, dh_tue, michlz, nicolas.f.rouquette
Version: unspecified   
Target Milestone: ---   
Hardware: Macintosh   
OS: Mac OS X   
Whiteboard:
Bug Depends on:    
Bug Blocks: 324520    
Attachments:
Description Flags
HelloWorld project for reproducing the EMF.Core PlatformResourceURIHandlerImpl race condition bug.
none
Patch to fix the race condition in PlatformResourceURIHandlerImpl.flush() none

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.