1

If FILE_SKIP_COMPLETION_PORT_ON_SUCCESS is set on a file handle that is bound to an I/O completion port, then an OVERLAPPED structure needs to be deallocated when its I/O completes synchronously. Otherwise, it needs to stay alive until a worker processes the notification from an I/O completion port.

This all sounds good until you realize that this only works if you manage the file handle yourself.
But if someone else gives you the file handle, how are you supposed to know when you should free the OVERLAPPED structure? Is there any way to discover this after the fact?
Otherwise, does this basically imply you cannot correctly perform overlapped I/O on any file handle that you cannot guarantee the completion notification state of...?

user541686
  • 205,094
  • 128
  • 528
  • 886
  • It might be possible to figure this out somehow, I don't know. But it seems to me that you already need to know a great deal about the state of the file handle - that it is asynchronous, which I/O completion port it is bound to, that it actually is a file handle to begin with - so it hardly seems unreasonable to need to know whether it has been set to skip notifications. – Harry Johnston Dec 08 '16 at 00:52
  • 1
    If you are the one performing an overlapped I/O operation on the fle handle then you are the one allocating the `OVERLAPPED` struct and you can deallocate it immediately if the operation does not fail with an `ERROR_PENDING` error. But if you are not the one performing the I/O then you don't know how the `OVERLAPPED` struct is allocated (it might be part of a larger struct carrying app data) and thus cannot deallocate it. – Remy Lebeau Dec 08 '16 at 00:59
  • @RemyLebeau - OVERLAPPED allocated per every single I/O operation - so no problem when multiple calls on file. every caller allocate self(own) OVERLAPPED and not need know about others – RbMm Dec 08 '16 at 01:04
  • @RemyLebeau, I think that in this scenario, we know the OP is performing the I/O operation; otherwise, they wouldn't be responsible for dealing with the case where the I/O completes synchronously. – Harry Johnston Dec 08 '16 at 01:07
  • @RbMm: clearly you did not understand what I said. Please re-read my earlier comment again more carefully. – Remy Lebeau Dec 08 '16 at 03:19
  • I re-read the question, and I see now the real issue is how to know whether `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` has been applied to a `HANDLE` you did not create in order to know if a `SUCCESS` result has been queued to the IOCP or not. – Remy Lebeau Dec 08 '16 at 03:23
  • 1
    I haven't read all of the existing comments and answers, but this seems like a non-issue in the real world. A correctly designed API would not simply stumble across a file handle and try to use it. Rather, it would require that any file handle it was given would also be accompanied with sufficient state information. If you are having to solve this problem, something is badly broken. – Cody Gray - on strike Dec 08 '16 at 12:28
  • @CodyGray: Could you explain then why you think they provided an API precisely for achieving exactly what I asked, as shown in the accepted answer? – user541686 Dec 08 '16 at 16:31
  • 1
    Um, they didn't provide that API. It's used internally by the operating system and exposed for drivers, but not for applications. There's a lot of stuff that the OS needs to know and do that is bad design when applications do it. Apparently this is a discussion has already been had in the comments to the accepted question. Everyone thinks they're a special flower, that they're above the rules, that their case is really special and they *have* to use the undocumented APIs. And I guess I wouldn't care if it only made that person's life harder, but it negatively affects all of us. – Cody Gray - on strike Dec 08 '16 at 16:35
  • @CodyGray: Uhm, they did provide it. Drivers would need a kernel-mode API in `ntoskrnl`, whereas they specifically provided the libraries and exported functions in `ntdll` to link user-mode applications against. Really now? – user541686 Dec 08 '16 at 16:42
  • 1
    "Provided" doesn't mean "someone wrote it". When you're talking about an API, it means written, documented, and explicitly designed for public consumption. This function satisfies only the first of those criteria, and the last one is the most important. It resoundingly fails that test. This is specifically undocumented and reserved as an implementation detail not for applications to use. RbMm and others seem to think that this was all done as a test to see if programmers are smart enough to reverse engineer them, so that the cool kids who do so can then use them. That isn't how it works. – Cody Gray - on strike Dec 08 '16 at 16:46
  • @CodyGray: Actually, that kind of *is* how it has worked in the past. e.g. that's how `GetFinalPathNameByHandle` came around later. The APIs it uses [were already exposed](http://stackoverflow.com/a/5286888/541686) but they just didn't have a Win32 layer written for it yet. Furthermore, have you looked at e.g. Chrome's use of native APIs? And if you know any history, you know that the backwards compatibility of `XXX_INFO_CLASS`es is rock solid, maybe more than the Win32 APIs'. Contrary to your baseless assertion, this isn't negatively affecting you one bit, so stop whining about nonsense. – user541686 Dec 08 '16 at 16:55
  • 1
    If you want a comprehensive answer on the disadvantages of using undocumented functions, ask a question about it. I ran out of room in the comment to add a detailed justification, or really any at all. It affects me significantly, every time a major design decision has to be made in the name of backwards compatibility with people who thought they were too good to follow the documentation. The arguments you're making appear rather silly to me, but I don't have room to dissect them in a comment. Raymond Chen writes an entire blog about this, and he still hasn't convinced everyone. – Cody Gray - on strike Dec 08 '16 at 16:59
  • @CodyGray: *"The arguments you're making appear rather silly to me, but I don't have room to dissect them in a comment."* Then don't start a stupid discussion and waste everyone's time to begin with. I have my reasons, you have yours, Raymond has his, others have theirs, and nobody was asking for advice here. You're not part of the API police, so just don't pretend to be. – user541686 Dec 08 '16 at 17:01
  • Now you're revising history. If you'll re-read my original comment, you'll notice that it said nothing about documented vs. undocumented APIs. All I said was that a correctly designed application/interface should not require this information. I even conceded that I hadn't read the existing answers, so I was unaware at the time of any discussion about documented vs. undocumented APIs. That distinction was only mentioned in my response to your assertion that an undocumented function does, in fact, exist to obtain this information. And you *did* ask for advice. You're also free to ignore it. – Cody Gray - on strike Dec 08 '16 at 17:08
  • @CodyGray: *"And you did ask for advice."* Really? where? – user541686 Dec 08 '16 at 17:11

2 Answers2

4

I'm not sure that your scenario makes sense.

Your clarified scenario - successfully performing I/O on an arbitrary file handle, without even knowing whether it is asynchronous or not - is challenging, I think very unusual, and almost certainly not how the API was designed to be used, but perhaps (as you suggest) not entirely implausible.

(Although I don't think you can avoid requiring some cooperation between the caller and your code, because in the IOCP case, the caller has to be able to tell whose I/O a dequeued packet belongs to. You could do this by having the caller allocate the OVERLAPPED structures, as RbMm suggests, but it might be simpler to ask them for a completion key to use.)

I'm not certain offhand how Windows behaves if you provide a redundant event handle, e.g., when the I/O is actually synchronous or using IOCP. But I would guess that it isn't going to be a problem in practice, so provided you're not too worried about future-proofing, you're probably OK.


At any rate, it isn't all that difficult to deal with the particular issue your question asks about. Basically, you just need to prevent the structure from being released twice.

  • Before making each call, assign a unique completion key and add it to a linked list or other suitable global structure. (The structure must be capable of an atomic find-and-remove operation, or protected by a critical section or similar.)

  • If the call succeeds immediately, i.e., does not report that the I/O is pending, treat it exactly as if a queued packet were received from the IOCP queue. Typically, you would either use a common function that is called by both your IOCP thread and your I/O thread, or a call to PostQueuedCompletionStatus to manually insert a packet to the IOCP queue.

  • When a packet is received (or when the call succeeds immediately) first perform a find-and-remove for the completion key against the global structure. If the find fails, you know that you have already been notified of the success of the I/O, and don't need to do anything.

  • If the find-and-remove succeeds, process the I/O as appropriate and release the OVERLAPPED structure.

There are undoubtedly ways to optimize the same basic approach.

Addendum: if the caller is processing the IOCP packets, and providing you with a completion key to use, you won't be able to use a unique completion key on each request. In this scenario, you can use the pointer to the OVERLAPPED structure instead.

The reason (in the general case) for not using the pointer is that you might receive a packet containing a completion key from one I/O request along with an OVERLAPPED structure from a different one, because the OVERLAPPED structure might be both released and reassigned before a duplicate notification is processed. That doesn't matter in this case, because all of your requests will use the same completion key anyway.

Addendum^2: if you don't know anything about the handle, you'll also need to provide an event object for each OVERLAPPED structure, and wait on them in case notification of the I/O completion arrives that way. It's getting too late in the day for me to try to figure out the exact consequences of that, but it may mean that under some circumstances you get three notifications for the same I/O operation. You might be able to avoid that, but if not, this approach will still work.

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • *"and remove it again when the packet is dequeued"*... I don't think you understand the problem? Which dequeued packet are you talking about? The entire problem is that when the flag is set, this packet that you're waiting for doesn't even exist, because the entire point of the flag is to suppress this packet... – user541686 Dec 08 '16 at 01:50
  • As for the first paragraph claiming this doesn't make sense, I think it's rather shortsighted. There's no need for you to depend in any way on the IOCP in the first place (in fact, the entire point is to not depend on it). You can imagine writing a library where you issue your I/O and handle the non-IOCP cases correctly, and where you require that the user call a special callback whenever a packet of yours is dequeued from any IOCP the user might be using. In that case, you're responsible for releasing your own `OVERLAPPED` structure, but you have no idea when to do it. – user541686 Dec 08 '16 at 01:56
  • @Mehrdad - my vision about this - even if you not direct make I/O request on file, but ask do this some LIB - the initial caller responsible for allocate/deallocate OVERLAPED. but not LIB code. `WSASend` for example. LIB (ws2_32) make inside this function `ZwDeviceIoControlFile` call. but `WSASend` dont know are we have synchronous or asynchronous socket and not care about OVERLAPED fate. who call `WSASend` must care about `OVERLAPED`. – RbMm Dec 08 '16 at 02:18
  • in general (assume no skip flag) manage lifetime of `OVERLAPED` ( `IO_STATUS_BLOCK` ) not hard - if `0 <= status` - our callback will be called, where we deallocate it. if `NT_ERROR(status)` - will be no callback - so need call it yourself or just free `IO_STATUS_BLOCK` based data. but if `0x80000000 <= status < 0xc0000000` ? this is not documented, but based on windows src and binary code - packet will be queued to IOCP in this case (yes, on error!). – RbMm Dec 08 '16 at 02:35
  • but win32 layer convert this range to error too (`0 > status`) and return false. so really on win32 layer - if error returned - we can not be 100% sure that packet not queued to IOCP. i never meet case when `0x80000000 <= status < 0xc0000000` was true on I/O operation, but always I felt some discomfort at this point – RbMm Dec 08 '16 at 02:35
  • @RbMm: Thanks for the response! But you're claiming that if a library makes some I/O request, it must obtain a distinct `OVERLAPPED` struct from the user for every single request... so imagine if a library wants to issue 100 I/O requests internally, maybe some in parallel, some sequentially. Do you really think the user should pass 100 `OVERLAPPED` structs to the library just for this purpose? Isn't that a little weird? – user541686 Dec 08 '16 at 03:01
  • @Mehrdad: I've rewritten to clarify my meaning. I had thought it was obvious; I'm going to blame [the illusion of transparency](http://lesswrong.com/lw/ke/illusion_of_transparency_why_no_one_understands/). I've also addressed your second comment, this is a much broader scenario than I'd originally imagined. I can't say I'm convinced that this is a good idea, but I can't see any compelling reason why it isn't possible. – Harry Johnston Dec 08 '16 at 04:59
  • Ahh!! Okay now I understand what you meant regarding the find failing. That makes a lot more sense now; thanks for clarifying. +1 – user541686 Dec 08 '16 at 05:27
  • @Mehrdad - about library - i not met situation written by you. usual or lib function, which take handle is shell over single I/O operation and in this case caller allocate and manage OVERLAPED. or library yourself opened this handle and return for caller in some initial call. library yourself select handle create options (synchronous or not) yourself bind(or not bind) handle to IOCP. caller is considers handle as opaque in this case - he simply pass it to another lib functions, but never yourself direct perform any operation on handle – RbMm Dec 08 '16 at 09:15
  • @RbMm: You're saying it's OK to do 1 read or write on a handle provided by the caller, but not 2 or more? Do you have any sound reason for that besides the fact that you haven't seen it before? – user541686 Dec 08 '16 at 17:08
  • @Mehrdad - the question here is who owns / controls handle ? who has the right to bind or not bind handle to some IOCP ? app or lib ? it is clear that not both at once, otherwise it will conflict. if app "owns" handle - lib functions with this handle only shell over single I/O operation usual, or if multiple - all of this synchronous. in case lib "owns" handle - it opaque for app, he use it only as context pointer, which pass to lib functions. – RbMm Dec 08 '16 at 17:42
  • @Mehrdad in this case lib of course can make multiple I/O operations (synchronous and asynchronous) during single call. this because lib have knowledge about handle, it mode, IOCP, etc and full control situation. you can provide example of other schemes ? – RbMm Dec 08 '16 at 17:42
  • @RbMm: But the problem is, you can't force the caller to be the one to create the handle. What if the caller wants to sometimes provide you with `GetStdHandle(STD_INPUT_HANDLE)`, and other times with a different handle? Are you going to disallow that? (This is just one simple example to illustrate the point; I'm not saying it's the only example by any means.) – user541686 Dec 08 '16 at 20:52
  • @Mehrdad - sorry, but not understand about what we speaking now :) i hope understand question - how check `FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` flag, and give "undocumented" formal answer. about usage - i can understand some system tool, which show handle property in UI. about another usage.. may be i something not understand. if *not you* bind IOCP on file and you yourself allocate OVERLAPPED based struct - when I/O complete - the original owner, but not you, got pointer to OVERLAPPED, which he not allocate - so can not correct handle and deallocate – RbMm Dec 08 '16 at 21:04
  • @RbMm: Yes, your answer was exactly what I was looking for in the question; I'm merely responding to your comments here. I'm saying, imagine 3 pieces of code: X creates file handle and passes it to Y, Y uses it for some purpose, using Z underneath as a library. It is X's responsibility to decide whether to open the handle in overlapped mode, and Y's decision whether to bind it to an IOCP, and Z's decision to allocate/deallocate as many `OVERLAPPED` as required. Z shouldn't restrict X in what it can provide. (Example: X = some application, Y = database library, Z = compression library.) – user541686 Dec 08 '16 at 21:10
  • @Mehrdad - if X create file and chose yourself mode (sync/async) X and must decide bind IOCP and how or not. but not Y. what be if X by self bind handle to some IOCP and then Y decide rebind. but main problem in Z. assume Z issue I/O with completion. but who is dequeu packet ? Y !! because Y bind IOCP to file. so Z allocate struct, but receive it Y ! which even can not correct free struct. really Z in this case can use only event based completion, but not APC or IOCP – RbMm Dec 08 '16 at 21:21
  • @RbMm: I understand what you're saying; I just don't understand *why*. You're claiming whoever created the file handle is responsible for binding it to an IOCP, but why? What is your reason for this? – user541686 Dec 08 '16 at 21:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/130152/discussion-between-rbmm-and-mehrdad). – RbMm Dec 08 '16 at 21:29
1

Is there any way to discover this after the fact?

yes, exist - need use ZwQueryInformationFile with FileIoCompletionNotificationInformation FILE_IO_COMPLETION_NOTIFICATION_INFORMATION is defined in wdm.h

so code which we need for query:

FILE_IO_COMPLETION_NOTIFICATION_INFORMATION ficni;
ZwQueryInformationFile(hFile, &iosb, &ficni, sizeof(ficni), FileIoCompletionNotificationInformation);

demo code for set and query

HANDLE hFile;
IO_STATUS_BLOCK iosb;
STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot\\notepad.exe");
if (0 <= ZwOpenFile(&hFile, FILE_GENERIC_READ, &oa, &iosb, FILE_SHARE_VALID_FLAGS, 0))
{
    FILE_IO_COMPLETION_NOTIFICATION_INFORMATION ficni = { FILE_SKIP_COMPLETION_PORT_ON_SUCCESS };
    if (0 <= ZwSetInformationFile(hFile, &iosb, &ficni, sizeof(ficni), FileIoCompletionNotificationInformation))
    {
        ficni.Flags = 0x12345678;
        if (
            0 > ZwQueryInformationFile(hFile, &iosb, &ficni, sizeof(ficni), FileIoCompletionNotificationInformation)
            ||
            !(ficni.Flags & FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)
            )
        {
            __debugbreak();
        }
    }

    ZwClose(hFile);
}

also let copy paste from wdm.h (for not say that this is "undocumented" )

//
// Don't queue an entry to an associated completion port if returning success
// synchronously.
//
#define FILE_SKIP_COMPLETION_PORT_ON_SUCCESS    0x1

//
// Don't set the file handle event on IO completion.
//
#define FILE_SKIP_SET_EVENT_ON_HANDLE           0x2

//
// Don't set user supplied event on successful fast-path IO completion.
//
#define FILE_SKIP_SET_USER_EVENT_ON_FAST_IO     0x4

typedef  struct _FILE_IO_COMPLETION_NOTIFICATION_INFORMATION {
    ULONG Flags;
} FILE_IO_COMPLETION_NOTIFICATION_INFORMATION, *PFILE_IO_COMPLETION_NOTIFICATION_INFORMATION;

I have question - for what reason this is declared in wdm.h ?

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • 3
    The function is documented *for use in device drivers*. The OP is not writing a device driver. – Harry Johnston Dec 08 '16 at 01:03
  • 2
    @HarryJohnston - this is very easy do from user mode to (this code I just run before paste) - all what need link to ntdll.lib and call this exported api – RbMm Dec 08 '16 at 01:06
  • 3
    Yes, it's easy. And it works. But it isn't *documented* to work. Let's not have this argument again - and I'm not downvoting, either - I just want to make it clear to the OP and to future readers that this approach does not have the Microsoft Seal of Approval (so to speak). – Harry Johnston Dec 08 '16 at 01:08
  • @HarryJohnston - ok, but are exist solution by not using ntdll api here ? for set we can use `SetFileCompletionNotificationModes` , but for query ? not I ask this question. I even not sure that this is actual problem. but uf say true solution is exist. and only this - i am not to blame in this (that no win32 api for query) – RbMm Dec 08 '16 at 01:13
  • I think there are other possible approaches to solve the OPs problem. Failing all else, they can simply insist that whoever is providing him with the handle also provide information on whether the flag in question has been set or not. – Harry Johnston Dec 08 '16 at 01:17
  • @HarryJohnston - I agree that this is invent problem - if we yourself open and operate on file - we know mode. if somebody give to as handle - must be some agreement and knowledge - which handle we got. for me hard imagine where this actual need query. but was concrete question - are possible and how. and I give for this concrete answer - possible. and show how – RbMm Dec 08 '16 at 01:23
  • @HarryJohnston: Lets' not have this argument indeed. People use these "undocumented" APIs to get stuff done and they live to tell the tale just fine. There's nothing wrong with this, it's exactly the kind of thing I was looking for. If you have a better solution then by all means post it and I'll use that instead. Thanks! – user541686 Dec 08 '16 at 01:31
  • Btw, for anyone interested -- I just realized that a newer version of [FileTest](http://www.zezula.net/en/fstools/filetest.html) than the one I had supports this. – user541686 Dec 08 '16 at 01:42
  • `ficni.Flags != FILE_SKIP_COMPLETION_PORT_ON_SUCCESS` should be `(ficni.Flags & FILE_SKIP_COMPLETION_PORT_ON_SUCCESS) == 0` instead. – Remy Lebeau Dec 08 '16 at 03:27
  • @RemyLebeau - yes, agree - correct check as you wrote – RbMm Dec 08 '16 at 09:36
  • 1
    @Mehrdad: The argument in question isn't, whether or not you should be using undocumented functionality. The argument with the author of this answer is, that they constantly refuse to disclose, whether something is guaranteed to work, or isn't documented to work. Stackoverflow is for professionals, and when professionals make decisions, they need to see the full picture. – IInspectable Dec 08 '16 at 12:37
  • @IInspectable - I have been always interested in - why ntdll.lib always was in WDK(DDK) libs, despite use it is "not supported" (kernel mode can not use nrdll.lib, only user mode apps) and still view in MSDN - about some funcs from ntdll "This function has no associated import library" (direct lie!). and are say boot execute apps (like `chkdsk.exe`) are "supported" at all ? – RbMm Dec 08 '16 at 13:00
  • 1
    @RbMm: *"I have been always interested in [...]"* - So then, go ask those that can answer your question. You'll find ways to *"Contact Microsoft"* from [Support and community for hardware developers](https://msdn.microsoft.com/en-us/windows/hardware/gg454528.aspx). An answer will not change the fact, that you are responsible for disclosing, whenever you suggest to use unsupported APIs. – IInspectable Dec 08 '16 at 13:11
  • 1
    @IInspectable - here was direct question - how. and I give direct answer. and by some reason not win32 api shell exist over this query. but OP look like not beginner and good understand what is ntdll api and what is "undocumented" and I assume that the most of user here not small child's and can understand and by self decide use some way or no – RbMm Dec 08 '16 at 13:15
  • 1
    @RbMm: I don't distrust visitors of this site to make judicious judgements, **if** they have all the information. You, however, leave vital pieces of information undisclosed, out of habit. I'm constantly tempted to down vote your contributions for being dangerous, not because they are wrong. Anyway, like others have said: *"Let's not have this argument again."* – IInspectable Dec 08 '16 at 13:23
  • @IInspectable - ok. let be as you and Harry say – RbMm Dec 08 '16 at 13:27