Thank you for your answer!


I spent a lot of time testing a whole lot of different combinations and the results still confused me...


> There's also a part of me that would like to know what can possibly be

> the point of this API; it's hard to even think of what "reasonable"

> behaviour is otherwise. Do you happen to know?

>

> My best guess is that it's meant to explicitly interrupt poll requests

> somehow (what, I/O cancellation wasn't good enough for them?), in which

> case it'd be good to explicitly confirm or refute that the "exclusive"

> poll really does act like a normal poll, and doesn't e.g. return

> immediately.


I discovered this behavior while investigating an app that seemed to rely on them to interrupt some polls, and so far it seems to be its sole purpose.

There is only one reason I can imagine a use for this instead of CancelIo(): As the NtDeviceIoControlFile()'s file handle is independent of the socket in params, it provides a way to interrupt another poll that waits on the same socket without the need to know the file handle that was originally given to it.

 

Regardless its behaviour is kind of crazy.

I tried to restrict to a subset of the tests to avoid polluting test_poll() too much, but indeed it requires an entire function to test it properly.


> I think these tests could be more extensive. For example:

>

> * What happens if you try to perform a non-exclusive wait while an

> exclusive wait is in progress? i.e. the inverse of the test added here.

So after some extensive testing, I'm rather confident that the logic for a new poll on a socket is:

(The main poll is an arbitrary designation for one of the poll waiting on the socket)

If there is no main poll on the socket, then the new poll becomes the main poll.
If the main poll is terminated, then there is no main poll anymore.

If the main poll is exclusive and the new poll is exclusive, then the main poll is terminated and the new poll becomes the main poll.


The flag does not seem to do anything more than this (apart from requiring 0x7fffffff00000000 <= timeout <= 0x7fffffffffffffff).

 

>

> * Can we verify that exclusive waits are specific to a given socket

> (i.e. you can simultaneously do an exclusive wait on two different sockets?)


Yes, the mechanism described earlier seems to be done on a per-socket basis.

>

> * Can we test that exclusive waits apply if two unequal sets overlap?

>


Yes, it works the same way (a single common socket between exclusive polls is enough to wake the poll)


> * Can you do an exclusive wait on the same socket from two different

> threads?

 

Can yes, didn't want to, but did it anyway :)
I get the expected result: It works exactly the same as the single threaded version.


So I guess I now have enough insight to write a proper implementation.

The patch for the tests is attached to this job: https://testbot.winehq.org/JobDetails.pl?Key=97234

Feel free to tell me if other tests come to your mind.


Le mercredi 1 septembre 2021, 17:52:14 CEST Guillaume Charifi a écrit :

> Signed-off-by: Guillaume Charifi <guillaume.charifi@sfr.fr>

> ---

>  dlls/ws2_32/tests/afd.c | 73 ++++++++++++++++++++++++++++++++++++++++-

>  1 file changed, 72 insertions(+), 1 deletion(-)

>

> diff --git a/dlls/ws2_32/tests/afd.c b/dlls/ws2_32/tests/afd.c

> index eb7c8ee50de..2568f251b6f 100644

> --- a/dlls/ws2_32/tests/afd.c

> +++ b/dlls/ws2_32/tests/afd.c

> @@ -148,8 +148,10 @@ static void test_poll(void)

>      const struct sockaddr_in bind_addr = {.sin_family = AF_INET,

> .sin_addr.s_addr = htonl(INADDR_LOOPBACK)}; char in_buffer[offsetof(struct

> afd_poll_params, sockets[3])]; char out_buffer[offsetof(struct

> afd_poll_params, sockets[3])]; +    char out_buffer1[offsetof(struct

> afd_poll_params, sockets[3])]; struct afd_poll_params *in_params = (struct

> afd_poll_params *)in_buffer; struct afd_poll_params *out_params = (struct

> afd_poll_params *)out_buffer; +    struct afd_poll_params *out_params1 =

> (struct afd_poll_params *)out_buffer1; int large_buffer_size = 1024 * 1024;

>      SOCKET client, server, listener;

>      struct sockaddr_in addr;

> @@ -157,13 +159,14 @@ static void test_poll(void)

>      IO_STATUS_BLOCK io;

>      LARGE_INTEGER now;

>      ULONG params_size;

> -    HANDLE event;

> +    HANDLE event, event1;

>      int ret, len;

>

>      large_buffer = malloc(large_buffer_size);

>      memset(in_buffer, 0, sizeof(in_buffer));

>      memset(out_buffer, 0, sizeof(out_buffer));

>      event = CreateEventW(NULL, TRUE, FALSE, NULL);

> +    event1 = CreateEventW(NULL, TRUE, FALSE, NULL);

>

>      listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

>      ret = bind(listener, (const struct sockaddr *)&bind_addr,

> sizeof(bind_addr)); @@ -208,10 +211,77 @@ static void test_poll(void)

>              IOCTL_AFD_POLL, in_params, params_size, out_params,

> params_size); ok(ret == STATUS_INVALID_PARAMETER, "got %#x\n", ret);

>

> +    /* Test exclusive flag */

> +

> +    client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

> +

> +    in_params->timeout = -1000 * 10000;

> +    in_params->count = 1;

> +    in_params->exclusive = FALSE;

> +    in_params->sockets[0].socket = client;

> +    in_params->sockets[0].flags = ~0;

> +    in_params->sockets[0].status = 0;

> +

> +    out_params->sockets[0].socket = 0;

> +    out_params->sockets[0].flags = 0;

> +    out_params->sockets[0].status = 0xdeadbeef;

> +

> +    ret = NtDeviceIoControlFile((HANDLE)listener, event, NULL, NULL, &io,

> +            IOCTL_AFD_POLL, in_params, params_size, out_params,

> params_size); +    ok(ret == STATUS_PENDING, "got %#x\n", ret);

> +

> +    in_params->timeout = 0x7fffffffffffffff; /* TIMEOUT_INFINITE */

> +    in_params->exclusive = TRUE;

> +

> +    ret = NtDeviceIoControlFile((HANDLE)listener, event1, NULL, NULL, &io,

> +            IOCTL_AFD_POLL, in_params, params_size, out_params1,

> params_size); +    ok(ret == STATUS_PENDING, "got %#x\n", ret);

> +

> +    ret = WaitForSingleObject(event, 100);

> +    ok(ret == STATUS_TIMEOUT, "got %#x\n", ret);

> +    ok(!out_params->sockets[0].socket, "got socket %#Ix\n",

> out_params->sockets[0].socket); +    ok(!out_params->sockets[0].flags, "got

> flags %#x\n", out_params->sockets[0].flags); +  

> ok(out_params->sockets[0].status == 0xdeadbeef, "got status %#x\n",

> out_params->sockets[0].status); +

> +    closesocket(client);

> +

> +    ret = WaitForSingleObject(event, 100);

> +    ok(!ret, "got %#x\n", ret);

> +

> +    client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

> +

> +    out_params->sockets[0].socket = 0;

> +    out_params->sockets[0].flags = 0;

> +    out_params->sockets[0].status = 0xdeadbeef;

> +

> +    ret = NtDeviceIoControlFile((HANDLE)listener, event, NULL, NULL, &io,

> +            IOCTL_AFD_POLL, in_params, params_size, out_params,

> params_size); +    ok(ret == STATUS_PENDING, "got %#x\n", ret);

> +

> +    ret = NtDeviceIoControlFile((HANDLE)listener, event1, NULL, NULL, &io,

> +            IOCTL_AFD_POLL, in_params, params_size, out_params1,

> params_size); +    ok(ret == STATUS_PENDING, "got %#x\n", ret);

> +

> +    ret = WaitForSingleObject(event, 100);

> +    ok(!ret, "got %#x\n", ret);

> +    ok(io.Status == STATUS_SUCCESS, "got %#x\n", io.Status);

> +    ok(io.Information == offsetof(struct afd_poll_params, sockets[0]), "got

> %#Ix\n", io.Information); +    ok(out_params->timeout ==

> 0x7fffffffffffffff, "got timeout %#I64x\n", out_params->timeout); +  

> ok(!out_params->count, "got count %u\n", out_params->count);

> +    ok(!out_params->sockets[0].socket, "got socket %#Ix\n",

> out_params->sockets[0].socket); +    ok(!out_params->sockets[0].flags, "got

> flags %#x\n", out_params->sockets[0].flags); +  

> ok(out_params->sockets[0].status == 0xdeadbeef, "got status %#x\n",

> out_params->sockets[0].status); +

> +    closesocket(client);

> +

> +    ret = WaitForSingleObject(event1, 100);

> +    ok(!ret, "got %#x\n", ret);

> +

>      /* Basic semantics of the ioctl. */

>

>      in_params->timeout = 0;

>      in_params->count = 1;

> +    in_params->exclusive = FALSE;

>      in_params->sockets[0].socket = listener;

>      in_params->sockets[0].flags = ~0;

>      in_params->sockets[0].status = 0xdeadbeef;

> @@ -743,6 +813,7 @@ static void test_poll(void)

>      closesocket(server);

>

>      CloseHandle(event);

> +    CloseHandle(event1);

>      free(large_buffer);

>  }