blob: ae1ad1ba298c1cf20bf9a1020466699558cd768f [file]
// Copyright 2022 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <chrono>
#include <thread>
#include "iree/base/api.h"
#include "iree/base/internal/wait_handle.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"
// NOTE: this file is meant to be included inside of a _test.cc source file.
// The file must define these functions to allocate/free the loop.
// |out_status| should receive the last global error encountered in the loop.
void AllocateLoop(iree_status_t* out_status, iree_allocator_t allocator,
iree_loop_t* out_loop);
void FreeLoop(iree_allocator_t allocator, iree_loop_t loop);
namespace iree {
namespace testing {
struct LoopTest : public ::testing::Test {
iree_allocator_t allocator = iree_allocator_system();
iree_loop_t loop;
iree_status_t loop_status = iree_ok_status();
void SetUp() override {
IREE_TRACE_SCOPE();
AllocateLoop(&loop_status, allocator, &loop);
}
void TearDown() override {
IREE_TRACE_SCOPE();
FreeLoop(allocator, loop);
iree_status_ignore(loop_status);
}
};
//===----------------------------------------------------------------------===//
// iree_loop_call
//===----------------------------------------------------------------------===//
// Tests the simple call interface for running work.
TEST_F(LoopTest, Call) {
IREE_TRACE_SCOPE();
struct UserData {
iree_status_t call_status = iree_status_from_code(IREE_STATUS_DATA_LOSS);
} user_data;
IREE_ASSERT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->call_status = status;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
IREE_ASSERT_OK(user_data.call_status);
}
// Tests a call that forks into two other calls.
TEST_F(LoopTest, CallFork) {
IREE_TRACE_SCOPE();
struct UserData {
bool called_a = false;
bool called_b = false;
bool called_c = false;
} user_data;
// A -> [B, C]
IREE_ASSERT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->called_a = true;
// B
IREE_EXPECT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->called_b = true;
return iree_ok_status();
},
user_data));
// C
IREE_EXPECT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->called_c = true;
return iree_ok_status();
},
user_data));
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.called_a);
EXPECT_TRUE(user_data.called_b);
EXPECT_TRUE(user_data.called_c);
}
// Tests a repeating call - since the loops are intended to be stackless we
// should in theory be able to issue calls forever. This test ensures we can do
// a really large amount without blowing the native stack.
struct CallRepeatedData {
int remaining = 2 * 1024;
};
static iree_status_t CallRepeatedFn(void* user_data_ptr, iree_loop_t loop,
iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<CallRepeatedData*>(user_data_ptr);
if (--user_data->remaining) {
IREE_RETURN_IF_ERROR(iree_loop_call(loop, IREE_LOOP_PRIORITY_DEFAULT,
CallRepeatedFn, user_data));
}
return iree_ok_status();
}
TEST_F(LoopTest, CallRepeated) {
IREE_TRACE_SCOPE();
CallRepeatedData user_data;
IREE_ASSERT_OK(iree_loop_call(loop, IREE_LOOP_PRIORITY_DEFAULT,
CallRepeatedFn, &user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_EQ(user_data.remaining, 0);
}
// Tests a call that results in failure.
TEST_F(LoopTest, CallFailure) {
IREE_TRACE_SCOPE();
struct UserData {
bool completed = false;
} user_data;
IREE_ASSERT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->completed);
user_data->completed = true;
return iree_status_from_code(IREE_STATUS_DATA_LOSS);
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, loop_status);
}
// Tests that a failure will abort other pending tasks.
TEST_F(LoopTest, CallFailureAborts) {
IREE_TRACE_SCOPE();
struct UserData {
bool did_call_callback = false;
bool did_wait_callback = false;
} user_data;
// Issue the call that will fail.
IREE_ASSERT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->did_call_callback);
user_data->did_call_callback = true;
return iree_status_from_code(IREE_STATUS_DATA_LOSS);
},
&user_data));
// Enqueue a wait that will never complete - if it runs it means we didn't
// correctly abort it.
IREE_ASSERT_OK(iree_loop_wait_until(
loop, iree_make_timeout_ms(1 * 60 * 1000),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_ABORTED, status);
iree_status_ignore(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->did_wait_callback);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, loop_status);
EXPECT_TRUE(user_data.did_call_callback);
EXPECT_TRUE(user_data.did_wait_callback);
}
// Tests that a failure will abort other pending tasks, including those enqueued
// from within the failing call itself.
TEST_F(LoopTest, CallFailureAbortsNested) {
IREE_TRACE_SCOPE();
struct UserData {
bool did_call_callback = false;
bool did_wait_callback = false;
} user_data;
// Issue the call that will fail.
IREE_ASSERT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->did_call_callback);
user_data->did_call_callback = true;
// Enqueue a wait that will never complete - if it runs it means we
// didn't correctly abort it. We are enqueuing it reentrantly as a user
// would before we encounter the error below.
IREE_EXPECT_OK(iree_loop_wait_until(
loop, iree_make_timeout_ms(1 * 60 * 1000),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_ABORTED, status);
iree_status_ignore(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->did_wait_callback);
user_data->did_wait_callback = true;
return iree_ok_status();
},
user_data));
return iree_status_from_code(IREE_STATUS_DATA_LOSS);
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, loop_status);
EXPECT_TRUE(user_data.did_call_callback);
EXPECT_TRUE(user_data.did_wait_callback);
}
//===----------------------------------------------------------------------===//
// iree_loop_dispatch
//===----------------------------------------------------------------------===//
// Tests a grid dispatch operation with an empty grid.
// The completion callback should still be issued but no workgroups.
TEST_F(LoopTest, DispatchEmpty) {
IREE_TRACE_SCOPE();
struct UserData {
std::atomic<int> workgroup_count = {0};
bool completed = false;
} user_data;
const uint32_t xyz[3] = {1, 0, 0};
IREE_ASSERT_OK(iree_loop_dispatch(
loop, xyz,
+[](void* user_data_ptr, iree_loop_t loop, uint32_t workgroup_x,
uint32_t workgroup_y, uint32_t workgroup_z) {
IREE_TRACE_SCOPE();
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
++user_data->workgroup_count;
return iree_ok_status();
},
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->completed);
user_data->completed = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_EQ(user_data.workgroup_count, 0);
EXPECT_TRUE(user_data.completed);
}
// Tests a grid dispatch operation and ensures all workgroups are issued.
TEST_F(LoopTest, DispatchGrid) {
IREE_TRACE_SCOPE();
struct UserData {
std::atomic<int> workgroup_count = {0};
bool completed = false;
} user_data;
const uint32_t xyz[3] = {4, 2, 1};
IREE_ASSERT_OK(iree_loop_dispatch(
loop, xyz,
+[](void* user_data_ptr, iree_loop_t loop, uint32_t workgroup_x,
uint32_t workgroup_y, uint32_t workgroup_z) {
IREE_TRACE_SCOPE();
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
++user_data->workgroup_count;
return iree_ok_status();
},
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->completed);
user_data->completed = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_EQ(user_data.workgroup_count, xyz[0] * xyz[1] * xyz[2]);
EXPECT_TRUE(user_data.completed);
}
// Tests a grid dispatch operation with a workgroup failure.
TEST_F(LoopTest, DispatchWorkgroupFailure) {
IREE_TRACE_SCOPE();
struct UserData {
bool completed = false;
} user_data;
const uint32_t xyz[3] = {4, 2, 1};
IREE_ASSERT_OK(iree_loop_dispatch(
loop, xyz,
+[](void* user_data_ptr, iree_loop_t loop, uint32_t workgroup_x,
uint32_t workgroup_y, uint32_t workgroup_z) {
IREE_TRACE_SCOPE();
return iree_status_from_code(IREE_STATUS_DATA_LOSS);
},
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DATA_LOSS, status);
iree_status_ignore(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->completed);
user_data->completed = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.completed);
}
//===----------------------------------------------------------------------===//
// iree_loop_wait_until
//===----------------------------------------------------------------------===//
// Tests a wait-until delay with an immediate timeout.
TEST_F(LoopTest, WaitUntilImmediate) {
IREE_TRACE_SCOPE();
struct UserData {
iree_status_t wait_status = iree_status_from_code(IREE_STATUS_DATA_LOSS);
} user_data;
IREE_ASSERT_OK(iree_loop_wait_until(
loop, iree_immediate_timeout(),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->wait_status = status;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
IREE_ASSERT_OK(user_data.wait_status);
}
// Tests a wait-until delay with an actual delay.
TEST_F(LoopTest, WaitUntil) {
IREE_TRACE_SCOPE();
struct UserData {
iree_time_t start_ns = iree_time_now();
iree_time_t end_ns = IREE_TIME_INFINITE_FUTURE;
iree_status_t wait_status = iree_status_from_code(IREE_STATUS_DATA_LOSS);
} user_data;
IREE_ASSERT_OK(iree_loop_wait_until(
loop, iree_make_timeout_ms(50),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->end_ns = iree_time_now();
user_data->wait_status = status;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
IREE_ASSERT_OK(user_data.wait_status);
// Not checking exact timing as some devices may not have clocks.
EXPECT_GE(user_data.end_ns, user_data.start_ns);
}
// Tests that multiple wait-until's can be active at once.
// NOTE: loops are not required to wake in any particular order.
TEST_F(LoopTest, MultiWaitUntil) {
IREE_TRACE_SCOPE();
struct UserData {
bool woke_a = false;
bool woke_b = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_until(
loop, iree_make_timeout_ms(25),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->woke_a = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_wait_until(
loop, iree_make_timeout_ms(50),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->woke_b = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.woke_a);
EXPECT_TRUE(user_data.woke_b);
}
//===----------------------------------------------------------------------===//
// iree_loop_wait_one
//===----------------------------------------------------------------------===//
// Tests a wait-one with an immediate timeout.
// The handle is never resolved and if we didn't bail immediately we'd hang.
TEST_F(LoopTest, WaitOneImmediate) {
IREE_TRACE_SCOPE();
// An event that never resolves.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_one(
loop, wait_source, iree_immediate_timeout(),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event);
}
// Tests a wait-one with a non-immediate timeout.
TEST_F(LoopTest, WaitOneTimeout) {
IREE_TRACE_SCOPE();
// An event that never resolves.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_one(
loop, wait_source, iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event);
}
// Tests a wait-one that times out does not abort other loop ops.
// The deadline exceeded status passed to the callback is sufficient.
TEST_F(LoopTest, WaitOneTimeoutNoAbort) {
IREE_TRACE_SCOPE();
// An event that never resolves.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
struct UserData {
bool did_wait_callback = false;
bool did_call_callback = false;
} user_data;
// Wait that will time out.
IREE_ASSERT_OK(iree_loop_wait_one(
loop, wait_source, iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
// Call that should still be issued correctly.
// Note that we queue it here as if we did it outside the wait we'd
// immediately execute it on out-of-order implementations.
IREE_EXPECT_OK(iree_loop_call(
loop, IREE_LOOP_PRIORITY_DEFAULT,
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
EXPECT_FALSE(user_data->did_call_callback);
user_data->did_call_callback = true;
return iree_ok_status();
},
user_data));
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
EXPECT_TRUE(user_data.did_call_callback);
iree_event_deinitialize(&event);
}
// Tests a wait-one with an already signaled wait source.
TEST_F(LoopTest, WaitOneSignaled) {
IREE_TRACE_SCOPE();
// An event that is resolved immediately.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_one(
loop, wait_source, iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event);
}
// Tests a wait-one on a wait handle signaled out-of-band.
TEST_F(LoopTest, WaitOneBlocking) {
IREE_TRACE_SCOPE();
// Initially unsignaled.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
// Spin up the thread to signal the event after a short delay.
// We need to do this before we issue the wait so that loops which perform the
// wait inline can still make forward progress even if they block.
//
// Note: there is a race here that can cause flakes. If this new thread
// starts running after the iree_loop_wait_* timeout below, the status check
// will fail. We make the timeout there sufficiently long to give the OS
// enough time to switch threads a few times.
std::thread thread([&]() {
IREE_TRACE_SCOPE();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
iree_event_set(&event);
});
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_one(
loop, wait_source, iree_make_timeout_ms(2000),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
thread.join();
iree_event_deinitialize(&event);
}
//===----------------------------------------------------------------------===//
// iree_loop_wait_any
//===----------------------------------------------------------------------===//
// Tests a wait-any with a immediate timeout (a poll).
TEST_F(LoopTest, WaitAnyImmediate) {
IREE_TRACE_SCOPE();
// Events that are never resolved such that we time out.
iree_event_t event_a;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a));
iree_wait_source_t wait_source_a = iree_event_await(&event_a);
iree_event_t event_b;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_b));
iree_wait_source_t wait_source_b = iree_event_await(&event_b);
iree_wait_source_t wait_sources[2] = {
wait_source_a,
wait_source_b,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_any(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_immediate_timeout(),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event_a);
iree_event_deinitialize(&event_b);
}
// Tests a wait-any with a non-immediate timeout.
TEST_F(LoopTest, WaitAnyTimeout) {
IREE_TRACE_SCOPE();
// Events that are never resolved such that we time out.
iree_event_t event_a;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a));
iree_wait_source_t wait_source_a = iree_event_await(&event_a);
iree_event_t event_b;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_b));
iree_wait_source_t wait_source_b = iree_event_await(&event_b);
iree_wait_source_t wait_sources[2] = {
wait_source_a,
wait_source_b,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_any(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event_a);
iree_event_deinitialize(&event_b);
}
// Tests a wait-any with an already-resolved wait handle.
TEST_F(LoopTest, WaitAnySignaled) {
IREE_TRACE_SCOPE();
// An event that is resolved immediately.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
// Always unsignaled so we test the wait-any behavior.
iree_event_t unresolved_event;
IREE_ASSERT_OK(
iree_event_initialize(/*initial_state=*/false, &unresolved_event));
iree_wait_source_t unresolved_wait_source =
iree_event_await(&unresolved_event);
iree_wait_source_t wait_sources[2] = {
wait_source,
unresolved_wait_source,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_any(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event);
iree_event_deinitialize(&unresolved_event);
}
// Tests a wait-any with a wait handle signaled out-of-band.
TEST_F(LoopTest, WaitAnyBlocking) {
IREE_TRACE_SCOPE();
// Initially unsignaled.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
// Always unsignaled so we test the wait-any behavior.
iree_event_t unresolved_event;
IREE_ASSERT_OK(
iree_event_initialize(/*initial_state=*/false, &unresolved_event));
iree_wait_source_t unresolved_wait_source =
iree_event_await(&unresolved_event);
// Spin up the thread to signal the event after a short delay.
// We need to do this before we issue the wait so that loops which perform the
// wait inline can still make forward progress even if they block.
//
// Note: there is a race here that can cause flakes. If this new thread
// starts running after the iree_loop_wait_* timeout below, the status check
// will fail. We make the timeout there sufficiently long to give the OS
// enough time to switch threads a few times.
std::thread thread([&]() {
IREE_TRACE_SCOPE();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
iree_event_set(&event);
});
iree_wait_source_t wait_sources[2] = {
wait_source,
unresolved_wait_source,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_any(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_make_timeout_ms(2000),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
thread.join();
iree_event_deinitialize(&event);
iree_event_deinitialize(&unresolved_event);
}
//===----------------------------------------------------------------------===//
// iree_loop_wait_all
//===----------------------------------------------------------------------===//
// Tests a wait-all with a immediate timeout (a poll).
TEST_F(LoopTest, WaitAllImmediate) {
IREE_TRACE_SCOPE();
// One unresolved and one resolved event (should fail the wait-all).
iree_event_t event_a;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a));
iree_wait_source_t wait_source_a = iree_event_await(&event_a);
iree_event_t event_b;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_b));
iree_wait_source_t wait_source_b = iree_event_await(&event_b);
iree_wait_source_t wait_sources[2] = {
wait_source_a,
wait_source_b,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_all(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_immediate_timeout(),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event_a);
iree_event_deinitialize(&event_b);
}
// Tests a wait-all with a non-immediate timeout.
TEST_F(LoopTest, WaitAllTimeout) {
IREE_TRACE_SCOPE();
// One unresolved and one resolved event (should fail the wait-all).
iree_event_t event_a;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event_a));
iree_wait_source_t wait_source_a = iree_event_await(&event_a);
iree_event_t event_b;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_b));
iree_wait_source_t wait_source_b = iree_event_await(&event_b);
iree_wait_source_t wait_sources[2] = {
wait_source_a,
wait_source_b,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_all(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_STATUS_IS(IREE_STATUS_DEADLINE_EXCEEDED, status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event_a);
iree_event_deinitialize(&event_b);
}
// Tests a wait-all with already-resolved wait handles.
TEST_F(LoopTest, WaitAllSignaled) {
IREE_TRACE_SCOPE();
// Signaled events so the wait-all succeeds.
iree_event_t event_a;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_a));
iree_wait_source_t wait_source_a = iree_event_await(&event_a);
iree_event_t event_b;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/true, &event_b));
iree_wait_source_t wait_source_b = iree_event_await(&event_b);
iree_wait_source_t wait_sources[2] = {
wait_source_a,
wait_source_b,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_all(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_make_timeout_ms(10),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
iree_event_deinitialize(&event_a);
iree_event_deinitialize(&event_b);
}
// Tests a wait-all with wait handles signaled out-of-band.
TEST_F(LoopTest, WaitAllBlocking) {
IREE_TRACE_SCOPE();
// Initially unsignaled.
iree_event_t event;
IREE_ASSERT_OK(iree_event_initialize(/*initial_state=*/false, &event));
iree_wait_source_t wait_source = iree_event_await(&event);
// Always unsignaled so we test the wait-any behavior.
iree_event_t resolved_event;
IREE_ASSERT_OK(
iree_event_initialize(/*initial_state=*/true, &resolved_event));
iree_wait_source_t resolved_wait_source = iree_event_await(&resolved_event);
// Spin up the thread to signal the event after a short delay.
// We need to do this before we issue the wait so that loops which perform the
// wait inline can still make forward progress even if they block.
//
// Note: there is a race here that can cause flakes. If this new thread
// starts running after the iree_loop_wait_* timeout below, the status check
// will fail. We make the timeout there sufficiently long to give the OS
// enough time to switch threads a few times.
std::thread thread([&]() {
IREE_TRACE_SCOPE();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
iree_event_set(&event);
});
iree_wait_source_t wait_sources[2] = {
wait_source,
resolved_wait_source,
};
struct UserData {
bool did_wait_callback = false;
} user_data;
IREE_ASSERT_OK(iree_loop_wait_all(
loop, IREE_ARRAYSIZE(wait_sources), wait_sources,
iree_make_timeout_ms(2000),
+[](void* user_data_ptr, iree_loop_t loop, iree_status_t status) {
IREE_TRACE_SCOPE();
IREE_EXPECT_OK(status);
auto* user_data = reinterpret_cast<UserData*>(user_data_ptr);
user_data->did_wait_callback = true;
return iree_ok_status();
},
&user_data));
IREE_ASSERT_OK(iree_loop_drain(loop, iree_infinite_timeout()));
IREE_ASSERT_OK(loop_status);
EXPECT_TRUE(user_data.did_wait_callback);
thread.join();
iree_event_deinitialize(&event);
iree_event_deinitialize(&resolved_event);
}
} // namespace testing
} // namespace iree