Broke recently: createInputStream() fails on second instance if given the same file name


Hi folks,

Just wanted to point out that this change set breaks creating two file readers that reference the same physical audio file. This is because the call to createInputStream() now fails on the second instance.
Apparently adding the new FILE_SHARE_DELETE flag causes a second call to CreateFile to fail with a permissions error. This will likely cause a lot of regressions in code where people try to create a reader from the same file twice.

Revision: 3b8686aa97165e3aa41ddebb76764323e73406d0
Author: ed
Date: 12/4/2018 7:44:37 AM
Windows: Add the FILE_SHARE_DELETE when opening file handles to allow them to be renamed and deleted

Modified: modules/juce_core/native/juce_win32_Files.cpp



Can you post some code that reproduces this? The following is working fine for me on Windows 10:

	auto f = File::getSpecialLocation (File::userDesktopDirectory).getChildFile ("test.txt");

	std::unique_ptr<InputStream> stream1 (f.createInputStream());
	std::unique_ptr<InputStream> stream2 (f.createInputStream());

	jassert (stream1.get() != nullptr && stream2.get() != nullptr);

	auto f = File::getSpecialLocation (File::userDesktopDirectory).getChildFile ("test.wav");
	jassert (f.existsAsFile());

	AudioFormatManager manager;

	std::unique_ptr<AudioFormatReader> formatReader1 (manager.createReaderFor (f.createInputStream()));
	std::unique_ptr<AudioFormatReader> formatReader2 (manager.createReaderFor (f.createInputStream()));

	jassert (formatReader1.get() != nullptr && formatReader2.get() != nullptr);



Thanks for your reply. So I checked and there is more to it :slight_smile:
The root issue is that I was copying the source file to the target location. It is precisely the copy step that exposes the issue.

File fileTarget( m_strAudioFolder.c_str());
fileTarget = fileTarget.getChildFile( file.getFileName() );

file.copyFileTo( fileTarget ); // copy file to target folder

std::unique_ptr reader( manager.createReaderFor (fileTarget) );

The code above is how the reader is being created. Prior to that the source file is copied to the target folder. For the second time that code executes (when the target file exists) the actual copy operation ends up failing - likely because the prior reader is still using the file. What is really weird is this:

bool File::copyFileTo (const File& newFile) const
return (*this == newFile)
|| (exists() && newFile.deleteFile() && copyInternal (newFile));

In the above code newFile.deleteFile() thinks it succeeded but it actually does NOT delete the file. Then the next copyInternal fails with an error code.

After this failure point, any attempt to create a new reader referencing fileTarget will fail due to a permissions issue. FWIW this issue did not happen prior to the changeset that added the FILE_SHARE_DELETE permission.

I have worked around this problem on our end by creating copies of the file with a unique name, since in any case we shouldn’t have been blindly copying the source file to the dest folder.



Can you post a minimal code example that reproduces this? Here’s what I’ve got and it’s working as expected:

 The block below describes the properties of this PIP. A PIP is a short snippet
 of code that can be read by the Projucer and used to generate a JUCE project.


  name:             WinFilePermissions

  dependencies:     juce_core, juce_data_structures, juce_events
  exporters:        vs2017


  type:             Console



#pragma once

int main (int argc, char* argv[])
	auto original = File::getSpecialLocation (File::userDesktopDirectory).getChildFile ("source.wav");
	auto target   = original.getSiblingFile ("destination.wav");

	jassert (original.copyFileTo (target));

	AudioFormatManager manager;

	std::unique_ptr<AudioFormatReader> reader (manager.createReaderFor (target));
	jassert (reader != nullptr);

	auto target2 = original.getSiblingFile ("destination2.wav");
	jassert (target.copyFileTo (target2));

	std::unique_ptr<AudioFormatReader> reader2 (manager.createReaderFor (target2));
	jassert (reader2 != nullptr);


    return 0;