On 6/6/22 16:55, Paul Gofman wrote:
+ /* asyncs without completion port are always cancelled on thread exit. */ + for (i = 0; i < ARRAY_SIZE(tests_no_port); ++i) + { + winetest_push_context("test %u", i); + memset(&io, 0xcc, sizeof(io)); + ResetEvent(event); + ret = thread_NtDeviceIoControlFile(i, (HANDLE)listener, tests_no_port[i].event ? event : NULL,
Shouldn't "i" here be "tests_no_port[i].kill_thread"?
+ tests_no_port[i].apc, tests_no_port[i].apc_context, &io, + IOCTL_AFD_POLL, in_params, params_size, out_params, params_size); + ok(ret == STATUS_PENDING, "got %#x\n", ret); + ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); + if (tests_no_port[i].event) + { + ret = WaitForSingleObject(event, 1000); + ok(!ret, "got %#x\n", ret); + } + winetest_pop_context(); + } + + SleepEx(0, TRUE); + ok(!test_async_thread_termination_apc_count, "got APC.\n"); + + port = CreateIoCompletionPort((HANDLE)listener, NULL, 0, 0); + + for (i = 0; i < 2; ++i) + { + winetest_push_context("test %u", i); + memset(&io, 0xcc, sizeof(io)); + ResetEvent(event); + ret = thread_NtDeviceIoControlFile(i, (HANDLE)listener, event, NULL, (void *)0xdeadbeef, &io, + IOCTL_AFD_POLL, in_params, params_size, out_params, params_size); + ok(ret == STATUS_PENDING, "got %#x\n", ret); + ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); + ret = WaitForSingleObject(event, 1000); + ok(!ret, "got %#x\n", ret); + + memset(&io, 0xcc, sizeof(io)); + key = 0xcc; + value = 0; + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + ok(!ret, "got %#x\n", ret); + ok(!key, "got key %#Ix\n", key); + ok(value == 0xdeadbeef, "got value %#Ix\n", value); + ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); + winetest_pop_context(); + } + + for (i = 0; i < 2; ++i) + { + winetest_push_context("test %u", i); + memset(&io, 0xcc, sizeof(io)); + ResetEvent(event); + ret = thread_NtDeviceIoControlFile(i, (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); + ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); + + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + ok(ret == WAIT_TIMEOUT, "got %#x\n", ret); + winetest_pop_context(); + } + + for (i = 0; i < 2; ++i) + { + winetest_push_context("test %u", i); + memset(&io, 0xcc, sizeof(io)); + ret = thread_NtDeviceIoControlFile(i, (HANDLE)listener, NULL, NULL, NULL, &io, + IOCTL_AFD_POLL, in_params, params_size, out_params, params_size); + ok(ret == STATUS_PENDING, "got %#x\n", ret); + ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); + + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + ok(ret == WAIT_TIMEOUT, "got %#x\n", ret); + winetest_pop_context(); + } + + /* async is not cancelled if there is a completion port and no event. */ + for (i = 0; i < 2; ++i) + { + winetest_push_context("test %u", i); + memset(&io, 0xcc, sizeof(io)); + ret = thread_NtDeviceIoControlFile(i, (HANDLE)listener, NULL, NULL, (void *)0xdeadbeef, &io, + IOCTL_AFD_POLL, in_params, params_size, out_params, params_size); + ok(ret == STATUS_PENDING, "got %#x\n", ret); + ok(io.Status == 0xcccccccc, "got %#lx\n", io.Status); + + memset(&io, 0xcc, sizeof(io)); + key = 0xcc; + value = 0; + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + ok(ret == WAIT_TIMEOUT, "got %#x\n", ret); + CancelIoEx((HANDLE)listener, NULL); + ret = NtRemoveIoCompletion(port, &key, &value, &io, &zero); + ok(!ret, "got %#x\n", ret); + ok(!key, "got key %#Ix\n", key); + ok(value == 0xdeadbeef, "got value %#Ix\n", value); + ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); + winetest_pop_context(); + }
FWIW, in cases like this I often find it preferable to iterate over the same loop but do something like if (tests[i].apc_context && !tests[i].event) { ok(io.Status == 0xcccccccc, "got %#lx\n", io.Status); /* other tests... */ } else { ok(io.Status == STATUS_CANCELLED, "got %#lx\n", io.Status); /* other tests... */ } Just something to consider.