-
-
Notifications
You must be signed in to change notification settings - Fork 32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unstable and floating exception with the access of the file after reading it in Image.NewFromFile #139
Comments
Hello, I think the file is being held open in libvips' cache, given that this only occurs with Please see #6 (comment) and the corresponding libvips documentation for a way to disable this. AFAIK, this problem only occurs on Windows, other platforms let you delete open files. |
Hi @kleisauke ! Thank you for your quickly answer. I did this: NetVips.ModuleInitializer.Initialize();
NetVips.Cache.Max = 0;
NetVips.Cache.MaxFiles = 0; The test code works really with many exemplars of one file, but in the real environment the files all different. And exception also there... Please, test my code... |
I was able to reproduce it, and after some testing, I was able to resolve it with this patch: --- a/issue-139.cs
+++ b/issue-139.cs
@@ -2,6 +2,8 @@ static async Task Main(string[] args)
{
Console.WriteLine("Hello NetVips!");
+ Cache.MaxFiles = 0;
+
string basePath = "C:\\Temp\\TestNetVips\\";
Directory.SetCurrentDirectory(basePath);
@@ -31,7 +31,7 @@ static async Task Main(string[] args)
CreateNoWindow = true
}))
{
- proc?.WaitForExit();
+ await proc?.WaitForExitAsync();
}
// Do any await operation to make method really async and run it in different thread (tested 20 times without problems) So, it looks like a race condition between the There might be still a change you'll get a --- a/issue-139.cs
+++ b/issue-139.cs
@@ -47,12 +49,13 @@ static async Task Main(string[] args)
// Do nothing, just read, this is already enough for issue
//image.Jpegsave(".....", 75);
}
+ File.Delete(filePath);
}
// Delete temp directory !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Here sometimes unexpectedly we got (this is an issue):
// "System.IO.IOException: The process cannot access the file 'test-X.png' because it is being used by another process"
- Directory.Delete(tempDirectory, true);
+ Directory.Delete(tempDirectory, false);
}
catch (Exception e)
{ |
Hi @kleisauke ! Thank you very much for your research and for your conclusion! Really, I did not test WaitForExitAsync because our project still developing in .Net Core 3.1, and WaitForExitAsync method presents only in .Net Core 5.0 As for now, we cannot change the framework in our project. So, I see that the problem in WaitForExit of .Net Core 3.1, and the correct way is to move the project to .Net Core 5.0. Then I would like to ask your advice... (NewFromBuffer and File.ReadAllBytes(filePath) resolve the issue in .Net Core 3.1 but I am not sure that it is the best...) Thank you very much again, and I will wait for your advice. |
Ah, I didn't realize that The net-vips/tests/NetVips.Tests/ForeignTests.cs Lines 1055 to 1060 in 087f9f7
|
Hi @kleisauke ! Thank you for your help. As for The deal is that we have some approach (with some methods/wrappers) that allows writing nice and readable code which deletes objects immediately when it becomes unnecessary. In this case, the main code is not overloaded with Dispose operations or using variables. Sometimes we got this one: Sometimes another one: I changed the previous example and illustrated the problem here: TestNetVipsAsync-2.zip Here is the main part of the example code. if (Directory.Exists(resultDirectory))
Directory.Delete(resultDirectory, true);
Directory.CreateDirectory(resultDirectory);
// Open files for converting
foreach (var filePath in Directory.GetFiles(tempDirectory, "test-*.png", SearchOption.TopDirectoryOnly))
{
using (var input = File.OpenRead(filePath))
{
var image = Image.NewFromStream(input, access: Enums.Access.Sequential);
// Do some transformation, f.e. add alpha
using var imageWithAlpha = image.Bandjoin(255);
// !!!!! ------ Dispose in this place make the code wrong and occur exception
// ==========================================================================
image.Dispose();
// !!!!! Do save!
imageWithAlpha.Jpegsave(Path.Combine(resultDirectory, Path.GetFileNameWithoutExtension(filePath) + ".jpg"), 75);
// !!!!! +++++ Dispose in this place make the code correct
// ==========================================================================
//image.Dispose();
}
} Thank you in advance! |
I retested this with .NET Core 3.1, and was able to resolve it with this compatibility extension method for internal static class ExtensionMethods
{
/// <summary>
/// Waits asynchronously for the process to exit.
/// </summary>
/// <param name="process">The process to wait for cancellation.</param>
/// <param name="cancellationToken">A cancellation token. If invoked, the task will return
/// immediately as canceled.</param>
/// <returns>A Task representing waiting for the process to end.</returns>
public static Task WaitForExitAsync(this Process process,
CancellationToken cancellationToken = default)
{
if (process.HasExited) return Task.CompletedTask;
var tcs = new TaskCompletionSource<object>();
process.EnableRaisingEvents = true;
process.Exited += (sender, args) => tcs.TrySetResult(null);
if (cancellationToken != default)
cancellationToken.Register(() => tcs.SetCanceled());
return process.HasExited ? Task.CompletedTask : tcs.Task;
}
} (from https://stackoverflow.com/a/19104345) I also could reproduce the issue with the |
Hi @kleisauke ! Thank you very much for your research and help! Unfortunately, I have to say, that problem did not resolve still... Yes, I also had no error in the initial code with your fix. Then I moved back to this test tool and just increased the count of parallel tasks from 20 to 30... and... got the same error. You can also try to test this case. Moreover, I saw that this error occurs in the standard WaitForExitAsync method in .Net 5.0 (with the case of 30 parallel tasks)! So, as I see we still have to use
In this case, the code works well and is stable. @kleisauke, we truly thank you for your research and wasted time. Thank you! |
No problem. I was able to reproduce this issue with the v8.11.4 binaries released on NuGet. However, I couldn't reproduce it with the unreleased v8.12.0 binaries built from the master branch of libvips. Presumably this issue is the same as the one mentioned in comment #135 (comment), but setting The pre-compiled libvips Windows binaries build from the master branch can be downloaded here (for testing purposes): (This was build from commit libvips/build-win64-mxe@b09c142) |
FWIW, I'm testing with these changes: Changeset--- a/issue-139.cs
+++ b/issue-139.cs
@@ -2,6 +2,8 @@
{
Console.WriteLine("Hello NetVips!");
+ Cache.Max = 0;
+
string basePath = "C:\\Temp\\TestNetVips\\";
Directory.SetCurrentDirectory(basePath);
@@ -31,7 +33,7 @@
CreateNoWindow = true
}))
{
- proc?.WaitForExit();
+ await proc?.WaitForExitAsync();
}
// Do any await operation to make method really async and run it in different thread
@@ -42,7 +44,7 @@
// Open files for converting
foreach (var filePath in Directory.GetFiles(tempDirectory, "test-*.png", SearchOption.TopDirectoryOnly))
{
- using (Image image = Image.NewFromBuffer(File.ReadAllBytes(filePath), access: Enums.Access.Sequential))
+ using (Image image = Image.NewFromFile(filePath, access: Enums.Access.Sequential))
{
// Do nothing, just read, this is already enough for issue
//image.Jpegsave(".....", 75);
@@ -67,9 +69,9 @@
// 10 iteration because the exception not stable
for (int i = 0; i < 10; i++)
{
- // 20 parallel tasks for reading files via NetVips
+ // 30 parallel tasks for reading files via NetVips
var tasks = new List<Task>();
- for (int j = 0; j < 20; j++)
+ for (int j = 0; j < 30; j++)
tasks.Add(DoAll($"TempDirectory-{i}-{j}"));
await Task.WhenAll(tasks); |
Oh, I could still reproduce this after increasing it to 50 parallel tasks with the libvips binaries mentioned above. I added this in the exception handler: catch (Exception e)
{
var message = e.Message;
var start = message.IndexOf("file '") + 6;
var file = Path.Combine(tempDirectory, message[start..message.IndexOf('\'', start)]);
Console.WriteLine($"{file} is locked by these processes:");
foreach (var process in FileUtil.WhoIsLocking(file))
{
Console.WriteLine("\t" + process.ProcessName);
}
throw;
} ( This gives this information:
So, it looks like there's still something wrong with that |
Here's a simplified test program that doesn't use Test programnamespace ConvertExample
{
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NetVips;
class Program
{
static void Convert(DirectoryInfo source, DirectoryInfo target)
{
// Check whether we are in different threads
Debug.WriteLine($"Work with {target.FullName} in tid {Thread.CurrentThread.ManagedThreadId}");
// Create target directory
Directory.CreateDirectory(target.FullName);
// Copy each image into the new directory
foreach (var fi in source.GetFiles())
{
fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);
}
// Convert images to a new format
foreach (var fi in target.GetFiles())
{
using (var image = Image.NewFromFile(fi.FullName, access: Enums.Access.Sequential))
{
// TODO: Image converting logic here
}
// We're done with the image, delete it
fi.Delete();
}
// Delete the empty directory
target.Delete();
}
static void Main(string[] args)
{
Cache.Max = 0;
var basePath = @"C:\Temp\TestNetVips";
var tempPath = Path.GetTempPath();
var testImage = Path.Combine(basePath, "test.png");
var baseDirectory = Path.Combine(basePath, "TestDirectory");
// The base directory that we clone each time
var source = new DirectoryInfo(baseDirectory);
Directory.CreateDirectory(source.FullName);
// Prepare 50 test images
for (var i = 0; i < 50; i++)
File.Copy(testImage, Path.Combine(source.FullName, $"test-{i}.png"), true);
// Run as many parallel jobs as there are available CPU cores
Parallel.For(0, 1000, new ParallelOptions { MaxDegreeOfParallelism = NetVips.Concurrency },
i =>
{
var target = new DirectoryInfo(Path.Combine(tempPath, $"TempDirectory-{i}"));
Convert(source, target);
});
Console.WriteLine("Done!");
}
}
} |
Hi @kleisauke ! Yes, your last code example works well. But... I wrote the same in my initial post in this discussion:
The problem is not in My code contains |
I just found an interesting thread on Twitter: https://twitter.com/MagickNET/status/1451276675396816896 I can confirm that I was unable to reproduce this using |
PR libvips/libvips#2497 should fix this. The pre-compiled libvips Windows binaries build from that PR can be downloaded here (for testing purposes): |
Hi @kleisauke ! Thank you very much for the solution! As I understand for the production we will wait for the new version NetVips.Native 8.12. |
This fix will be in the future libvips 8.12. For now, you can use the pre-built binaries mentioned above. Another possible way to fix this is to serialize the calls to private static readonly Object obj = new Object();
lock (obj)
{
using var proc = Process.Start( ...
} (untested) Or by using the buffer/stream-based loaders, as you noticed. |
A release candidate of NetVips.Native v8.12.0 is now available for testing. |
NetVips.Native v8.12.1 is now available. |
Thank you very much! |
Hi @kleisauke!
I got an exception and think that it relates to the implementation of NetVips (or LibVips)
System.IO.IOException: The process cannot access the file 'test-X.png' because it is being used by another process
Explanation
So, we got an unstable and floating exception with the access of the file that was created via System.Diagnostics.Process in async operation and which was read in Image.NewFromFile after that.
So for the reproducing, needs 3 things:
The exception occurs on deleting the temp directory on the 3 step.
Why I think the issue relates to the NetVips (libvips).
The first reason, if I do not read files via Image.NewFromFile (2 step), then deletion temp directory works stable and well.
The second reason...
If I do:
Image.NewFromBuffer(File.ReadAllBytes(filePath), access: Enums.Access.Sequential)
instead of
Image.NewFromFile(filePath, access: Enums.Access.Sequential)
the code works stable and well also (no exception).
Resources for reproducing
Bellow, I listed the full test code where you can reproduce the issue.
Also, here the project with test code for easy reproducing: TestNetVipsAsync.zip
This is the test picture (test.png) needed for this test:
Important!
For reproducing this issue, the test picture must be quite small (as I created).
Full test example
(Please, create the directory C:\Temp\TestNetVips and put there the picture test.png)
The text was updated successfully, but these errors were encountered: