Skip to content
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

SetCreationTime, SetLastAccessTime, SetLastWriteTime Should not open a new stream to obtain a SafeFileHandle #20234

Closed
TrabacchinLuigi opened this issue Feb 15, 2017 · 25 comments · Fixed by #60507
Labels
api-approved API was approved in API review, it can be implemented area-System.IO help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@TrabacchinLuigi
Copy link

API Proposal

namespace System.IO
{
    public static class FileHandleExtensions
    {
        public static DateTime GetLastWriteTimeUtc(this SafeFileHandle fileHandle);
        public static void SetLastWriteTimeUtc(this SafeFileHandle fileHandle, DateTime time);
        public static DateTime GetLastAccessTimeUtc(this SafeFileHandle fileHandle);
        public static void SetLastAccessTimeUtc(this SafeFileHandle fileHandle, DateTime time);
        public static DateTime GetCreationTimeUtc(this SafeFileHandle fileHandle);
        public static void SetCreationTimeUtc(this SafeFileHandle fileHandle, DateTime time);
    }
}

Original post

I know that in many cases it is ok to do so, but we found ourself in the case where we need to set file times without closing the actual FileStream, and this is possible via Windows API.
To do so we had to write unsafe code which mimic the functionality of the existing functions.
(which include rewriting System.IO.__Error.WinIOError(), this is obviusly a bad thing, because we have to maintain code wich already exist)
This could be extremely more usable and maintainable if the framework exposed those methods as SafeFileHandle Method/ExtensionMethod.

My proposal is to leave those functions unaltered but move the actual implementation in an ipotetic function:
SafeFileHandle.SetFileTimesUtc(DateTime? creationTimeUtc, DateTime? lastAccessTimeUtc, DateTime? lastWriteTimeUtc) which should be public.

@kostadinmarinov
Copy link

Is there a plan this problem to be fixed soon?

@danmoseley
Copy link
Member

@TrabacchinLuigi @kostadinmarinov next step would be for someone to format this as an API proposal as per the example linked in https://github.com/dotnet/corefx/blob/master/Documentation/project-docs/api-review-process.md

@JeremyKuhne do you have any feedback up front about this?

@JeremyKuhne
Copy link
Member

I'm ok with us having this functionality, but I'd rather see this as extension methods on SafeFileHandle. This will allow hitting the functionality anytime you have a handle, not just with FileStream.

Something like:

namespace System.IO
{
    public static class FileHandleExtensions
    {
        public static DateTime GetLastWriteTimeUtc(this SafeFileHandle fileHandle);
        public static void SetLastWriteTimeUtc(this SafeFileHandle fileHandle, DateTime time);
        // etc...
    }
}

@kostadinmarinov
Copy link

Won't it be better and more consistent with the existing interface, if the methods are part of File class. This class already contains that functionality hidden as internal. We can move the body of the using into that public method and make the current one call it.

However, based on my copy-paste experience, I see one problem - __Error.WinIOError(errorCode, path) needs path and we don't have it. I am not so familiar with SafeFileHandle, but I don't see from where we can get it. I also see that it's not good to skip it as it will affect the exception messages. Instead we can change the SafeFileHandle parameter to be FileStream which contains the file path into Name property. It's just a suggestion on that problem.

@TrabacchinLuigi
Copy link
Author

TrabacchinLuigi commented Aug 18, 2017

@kostadinmarinov I've tried to copy paste the actual implementation and i've end in the same situation where __Error.WinIoError(errorCode, path) need the path, IMHO a little refactor where the path is not needed and throw a simpler or dedicated exception and then FileStream catch it and re-throw shouldn't be that hard and so much slower ...
But Touching those parts of code seems like something very dangerous/scary.

I also like your solution where our extension methods target FileStream wich exposes SafeFileHandle and work with both ... we just need to make some methods from __Error to be public/accessible enough ... But i don't know where else SafeFileHandle are used and how ...

@TrabacchinLuigi
Copy link
Author

I'm Already using SafeFileHandle ExtensionMethods with success, i just don't have the path in the exception message, but following the stack i'll get to more contextualized scope, so ... like i said, i wouldn't depend on the path...

@JeremyKuhne
Copy link
Member

This is the full initial API shape we want to review.

namespace System.IO
{
    public static class FileHandleExtensions
    {
        public static DateTime GetLastWriteTimeUtc(this SafeFileHandle fileHandle);
        public static void SetLastWriteTimeUtc(this SafeFileHandle fileHandle, DateTime time);
        public static DateTime GetLastAccessTimeUtc(this SafeFileHandle fileHandle);
        public static void SetLastAccessTimeUtc(this SafeFileHandle fileHandle, DateTime time);
        public static DateTime GetCreationTimeUtc(this SafeFileHandle fileHandle);
        public static void SetCreationTimeUtc(this SafeFileHandle fileHandle, DateTime time);
    }
}

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the Future milestone Jan 31, 2020
@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review labels Feb 4, 2020
@terrajobst
Copy link
Member

terrajobst commented Feb 4, 2020

Video

  • The scenario makes sense, but defining them as extension methods on SafeFileHandle seems odd
  • It seems more logical to define them as overloads to the string based APIs on File.
namespace System.IO
{
    public static partial class File
    {
        public static DateTime GetCreationTime(SafeFileHandle fileHandle);
        public static DateTime GetCreationTimeUtc(SafeFileHandle fileHandle);
        public static DateTime GetLastAccessTime(SafeFileHandle fileHandle);
        public static DateTime GetLastAccessTimeUtc(SafeFileHandle fileHandle);
        public static DateTime GetLastWriteTime(SafeFileHandle fileHandle);
        public static DateTime GetLastWriteTimeUtc(SafeFileHandle fileHandle);

        public static void SetCreationTime(SafeFileHandle fileHandle, DateTime creationTime);
        public static void SetCreationTimeUtc(SafeFileHandle fileHandle, DateTime creatinTimeUtc);
        public static void SetLastAccessTime(SafeFileHandle fileHandle, DateTime lastAccessTime);
        public static void SetLastAccessTimeUtc(SafeFileHandle fileHandle, DateTime lastAccessTimeUtc);
        public static void SetLastWriteTime(SafeFileHandle fileHandle, DateTime lastWriteTime);
        public static void SetLastWriteTimeUtc(SafeFileHandle fileHandle, DateTime lastWriteTimeUtc);

        // Seems we should be adding support for these too.
        // If we can't make it work, dropping is fine.
        public static FileAttributes GetAttributes(SafeFileHandle fileHandle);
        public static void SetAttributes(SafeFileHandle fileHandle, FileAttributes fileAttributes);
    }
}

@danmoseley
Copy link
Member

Any volunteers interested in adding these?

@fowl2
Copy link

fowl2 commented Feb 5, 2020

The scenario makes sense, but defining them as extension methods on SafeFileHandle seems odd

They could be extension methods on SafeFileHandle in File for discoverability?

@fowl2
Copy link

fowl2 commented Feb 5, 2020

There is an advantage to putting these on FileStream (perhaps in addition) beyond paths in exceptions and that is that we could avoid the SafeFileHandle property accessor which does this:

The SafeFileHandle property automatically flushes the stream and sets the current stream position to 0. This allows the file to be moved or the stream position to be reset by another stream using the SafeFileHandle returned by this property.

@terrajobst
Copy link
Member

@fowl2

They could be extension methods on SafeFileHandle in File for discoverability?

We don't define any extension methods in the File class today, but wouldn't be a blocker IMHO. What I find odd is that we don't really treat handles as things you invoke methods on (modulo anything related to the life time of said handle). Not that it's wrong, but it's just not the pattern.

There is an advantage to putting these on FileStream (perhaps in addition) beyond paths in exceptions and that is that we could avoid the SafeFileHandle property accessor [which calls Flush on every read].

Is there a key scenario where avoiding flushing is necessary? Given that Flush() will only persist what it's pending in the buffer it seems that's generally desired.

@TrabacchinLuigi
Copy link
Author

TrabacchinLuigi commented Feb 6, 2020

I avoided replying till now because i have absolutely zero experience on maintaining a framework.
But to me seems a desired behavior to flush BEFORE setting LastWriteTime or LastAccessTime, because that would be the actual last access time, after flushing.
Also it could be specified in the function annotations what the behavior will be, and the user could just save the position before calling this and then reset it.

I see no weirdness in using SafeFileHandle extensions, what really matter to me is not getting an exception doing it, i don't want to give up the handle to that file and be able to set those values.
So in that regard it won't really matter if it will be a SafeFileHandle extension or a FileStreamExtension.

Just to give some context i had to do that because i'm writing an usermode filesystem, so my code get's called by a driver and it expect to be able to do that.
Obviously, i could write my own logic to overcome that necessity, but it is WAY faster like that. and i want it to be fast.

@fowl2
Copy link

fowl2 commented Feb 10, 2020

Is there a key scenario where avoiding flushing is necessary

Given this is primarily a performance optimisation - a 66% syscall reduction going from Open()->SetXx()->Close() to just SetXx() - going back to Write()->SetXx() could be significant.

Resetting the stream position to 0 is what I'm be more worried about - that's surprising and could potentially cause data loss if mixing Read/Write and SetXx() calls. Unlikely, I guess?

what really matter to me is not getting an exception

I think that would be a separate issue about getting Try*/result-code-returning versions of most of System.IO? I'm surprised I can't find an issue for that already actually.

@TrabacchinLuigi
Copy link
Author

what really matter to me is not getting an exception

I think that would be a separate issue about getting Try*/result-code-returning versions of most of System.IO? I'm surprised I can't find an issue for that already actually.

Sorry i think i mis-phrased that. The exception was not the issue (but i agree, exception workflow isn't so great, and alternative methods would be nice), the issue was that wasn't possible without closing the filestream first .
So i had to close it, set the lastwrite, reopen it, seek to the end, that part was quite slow.

@terrajobst
Copy link
Member

Resetting the stream position to 0 is what I'm be more worried about - that's surprising and could potentially cause data loss if mixing Read/Write and SetXx() calls.

Flush() wouldn’t change the stream’s position. Or do I misunderstand what you’re saying?

@fowl2
Copy link

fowl2 commented Feb 10, 2020

Flush() wouldn’t change the stream’s position. Or do I misunderstand what you’re saying?

Accessing the SafeFileHandle property on a FileStream does reset the position, which - as you've just demonstrated - is unexpected!

@terrajobst
Copy link
Member

terrajobst commented Feb 10, 2020

Accessing the SafeFileHandle property on a FileStream does reset the position, which - as you've just demonstrated - is unexpected!

Do you have a repro for .NET Core? Looking at the code, that doesn't seem intentional. Rather, it should merely validate that you haven't advanced the underlying file pointer that is backed by the file handle by calling OS APIs yourself.

@fowl2
Copy link

fowl2 commented Feb 10, 2020

Indeed, it seems the docs are wrong.

@terrajobst
Copy link
Member

Cool, sanity restored 😀 I’ll file a doc bug.

@fowl2
Copy link

fowl2 commented Feb 10, 2020

Ok so reading a bit more, it also sets _exposedHandle true, which does end up causing an extra seek(0) syscall from VerifyOSHandlePosition() every read/write. Which seems like bad, and if you squint kinda vaguely relates to what the docs say.

@terrajobst
Copy link
Member

terrajobst commented Feb 11, 2020

Yes, but it uses SeekCore(_fileHandle, 0, SeekOrigin.Current) which seeks to 0 relative to current, which means it shouldn't change, assume the underlying handle wasn't seeked, which is what this is trying validate.

@fowl2
Copy link

fowl2 commented Feb 11, 2020

Ahh, right I have no idea what the docs mean then. It's still an extra syscall though.

@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@JeremyKuhne JeremyKuhne removed the untriaged New issue has not been triaged by the area owner label Mar 3, 2020
@carlossanlop carlossanlop added the help wanted [up-for-grabs] Good issue for external contributors label Mar 6, 2020
@carlossanlop carlossanlop added this to To do in System.IO - FileStream via automation Mar 6, 2020
@deeprobin
Copy link
Contributor

I would like to implement this :). If you want you can assign me the issue 😄

@Liryna
Copy link

Liryna commented Oct 16, 2021

Hi @deeprobin , it would be awesome! Please open a pull request when you are ready and ping me directly:) I would be glad to review your code !

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Nov 13, 2021
System.IO - FileStream automation moved this from To do to Done Jul 12, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jul 12, 2022
@dotnet dotnet locked as resolved and limited conversation to collaborators Aug 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.IO help wanted [up-for-grabs] Good issue for external contributors
Projects