#include <chrono>
#include <future>
#include <thread>
#include "gtest/gtest.h"
#include "triton/core/tritonbackend.h"
#include "triton/core/tritonserver.h"
#define FAIL_TEST_IF_ERR(X) \
do { \
std::shared_ptr<TRITONSERVER_Error> err__((X), TRITONSERVER_ErrorDelete); \
ASSERT_TRUE((err__ == nullptr)) \
<< TRITONSERVER_ErrorCodeString(err__.get()) << " - " \
<< TRITONSERVER_ErrorMessage(err__.get()); \
} while (false)
TRITONSERVER_Error*
ResponseAlloc(
TRITONSERVER_ResponseAllocator* allocator, const char* tensor_name,
size_t byte_size, TRITONSERVER_MemoryType preferred_memory_type,
int64_t preferred_memory_type_id, void* userp, void** buffer,
void** buffer_userp, TRITONSERVER_MemoryType* actual_memory_type,
int64_t* actual_memory_type_id)
{
*actual_memory_type = TRITONSERVER_MEMORY_CPU;
*actual_memory_type_id = preferred_memory_type_id;
if (byte_size == 0) {
*buffer = nullptr;
*buffer_userp = nullptr;
} else {
void* allocated_ptr = nullptr;
allocated_ptr = malloc(byte_size);
if (allocated_ptr != nullptr) {
*buffer = allocated_ptr;
*buffer_userp = new std::string(tensor_name);
}
}
return nullptr; }
TRITONSERVER_Error*
ResponseRelease(
TRITONSERVER_ResponseAllocator* allocator, void* buffer, void* buffer_userp,
size_t byte_size, TRITONSERVER_MemoryType memory_type,
int64_t memory_type_id)
{
return nullptr; }
void
InferRequestComplete(
TRITONSERVER_InferenceRequest* request, const uint32_t flags, void* userp)
{
}
void
InferResponseComplete(
TRITONSERVER_InferenceResponse* response, const uint32_t flags, void* userp)
{
if (response != nullptr) {
std::promise<TRITONSERVER_InferenceResponse*>* p =
reinterpret_cast<std::promise<TRITONSERVER_InferenceResponse*>*>(userp);
p->set_value(response);
delete p;
}
}
class RequestCancellationTest : public ::testing::Test {
protected:
static void SetUpTestSuite()
{
TRITONSERVER_ServerOptions* server_options = nullptr;
FAIL_TEST_IF_ERR(TRITONSERVER_ServerOptionsNew(&server_options));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerOptionsSetModelRepositoryPath(
server_options, "./models"));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerOptionsSetBackendDirectory(
server_options, "/opt/tritonserver/backends"));
FAIL_TEST_IF_ERR(
TRITONSERVER_ServerOptionsSetLogVerbose(server_options, 1));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerOptionsSetRepoAgentDirectory(
server_options, "/opt/tritonserver/repoagents"));
FAIL_TEST_IF_ERR(
TRITONSERVER_ServerOptionsSetStrictModelConfig(server_options, true));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerNew(&server_, server_options));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerOptionsDelete(server_options));
}
static void TearDownTestSuite()
{
FAIL_TEST_IF_ERR(TRITONSERVER_ServerDelete(server_));
}
void SetUp() override
{
ASSERT_TRUE(server_ != nullptr) << "Server has not created";
size_t health_iters = 0;
while (true) {
bool live, ready;
FAIL_TEST_IF_ERR(TRITONSERVER_ServerIsLive(server_, &live));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerIsReady(server_, &ready));
if (live && ready) {
break;
}
if (++health_iters >= 10) {
FAIL() << "failed to find healthy inference server";
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
FAIL_TEST_IF_ERR(TRITONSERVER_ResponseAllocatorNew(
&allocator_, ResponseAlloc, ResponseRelease, nullptr ));
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestNew(
&irequest_, server_, "model", -1 ));
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestSetReleaseCallback(
irequest_, InferRequestComplete, nullptr ));
std::vector<int64_t> input0_shape({1, 1000});
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestAddInput(
irequest_, "INPUT0", TRITONSERVER_TYPE_INT32, input0_shape.data(),
input0_shape.size()));
const auto input0_byte_size = sizeof(input0_data_[0]) * input0_data_.size();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestAppendInputData(
irequest_, "INPUT0", input0_data_.data(), input0_byte_size,
TRITONSERVER_MEMORY_CPU, 0));
}
void TearDown() override
{
FAIL_TEST_IF_ERR(TRITONSERVER_ResponseAllocatorDelete(allocator_));
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestDelete(irequest_));
}
static TRITONSERVER_Server* server_;
TRITONSERVER_ResponseAllocator* allocator_ = nullptr;
static std::vector<int32_t> input0_data_;
TRITONSERVER_InferenceRequest* irequest_ = nullptr;
};
TRITONSERVER_Server* RequestCancellationTest::server_ = nullptr;
std::vector<int32_t> RequestCancellationTest::input0_data_(1000, 1);
TEST_F(RequestCancellationTest, Cancellation)
{
auto p = new std::promise<TRITONSERVER_InferenceResponse*>();
std::future<TRITONSERVER_InferenceResponse*> future = p->get_future();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestSetResponseCallback(
irequest_, allocator_, nullptr ,
InferResponseComplete, reinterpret_cast<void*>(p)));
TRITONBACKEND_Request* backend_request =
reinterpret_cast<TRITONBACKEND_Request*>(irequest_);
FAIL_TEST_IF_ERR(
TRITONSERVER_ServerInferAsync(server_, irequest_, nullptr ));
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestCancel(irequest_));
TRITONBACKEND_ResponseFactory* response_factory;
FAIL_TEST_IF_ERR(
TRITONBACKEND_ResponseFactoryNew(&response_factory, backend_request));
bool is_cancelled = false;
FAIL_TEST_IF_ERR(
TRITONSERVER_InferenceRequestIsCancelled(irequest_, &is_cancelled));
ASSERT_TRUE(is_cancelled);
is_cancelled = false;
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryIsCancelled(
response_factory, &is_cancelled));
ASSERT_TRUE(is_cancelled);
TRITONSERVER_InferenceResponse* response = future.get();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceResponseDelete(response));
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryDelete(response_factory));
std::this_thread::sleep_for(std::chrono::seconds(2));
p = new std::promise<TRITONSERVER_InferenceResponse*>();
future = p->get_future();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestSetResponseCallback(
irequest_, allocator_, nullptr ,
InferResponseComplete, reinterpret_cast<void*>(p)));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerInferAsync(
server_, irequest_, nullptr
));
FAIL_TEST_IF_ERR(
TRITONBACKEND_ResponseFactoryNew(&response_factory, backend_request));
is_cancelled = true;
FAIL_TEST_IF_ERR(
TRITONSERVER_InferenceRequestIsCancelled(irequest_, &is_cancelled));
ASSERT_FALSE(is_cancelled);
is_cancelled = true;
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryIsCancelled(
response_factory, &is_cancelled));
ASSERT_FALSE(is_cancelled);
response = future.get();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceResponseDelete(response));
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryDelete(response_factory));
}
TEST_F(RequestCancellationTest, CancellationAfterRelease)
{
auto p = new std::promise<TRITONSERVER_InferenceResponse*>();
std::future<TRITONSERVER_InferenceResponse*> future = p->get_future();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestSetResponseCallback(
irequest_, allocator_, nullptr ,
InferResponseComplete, reinterpret_cast<void*>(p)));
FAIL_TEST_IF_ERR(TRITONSERVER_ServerInferAsync(
server_, irequest_, nullptr
));
TRITONBACKEND_Request* backend_request =
reinterpret_cast<TRITONBACKEND_Request*>(irequest_);
TRITONBACKEND_ResponseFactory* response_factory;
FAIL_TEST_IF_ERR(
TRITONBACKEND_ResponseFactoryNew(&response_factory, backend_request));
FAIL_TEST_IF_ERR(TRITONBACKEND_RequestRelease(
backend_request, TRITONSERVER_REQUEST_RELEASE_ALL));
bool is_cancelled = true;
FAIL_TEST_IF_ERR(
TRITONSERVER_InferenceRequestIsCancelled(irequest_, &is_cancelled));
ASSERT_FALSE(is_cancelled);
is_cancelled = true;
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryIsCancelled(
response_factory, &is_cancelled));
ASSERT_FALSE(is_cancelled);
is_cancelled = false;
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceRequestCancel(irequest_));
is_cancelled = false;
FAIL_TEST_IF_ERR(
TRITONSERVER_InferenceRequestIsCancelled(irequest_, &is_cancelled));
ASSERT_TRUE(is_cancelled);
TRITONSERVER_InferenceResponse* response = future.get();
FAIL_TEST_IF_ERR(TRITONSERVER_InferenceResponseDelete(response));
is_cancelled = false;
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryIsCancelled(
response_factory, &is_cancelled));
ASSERT_TRUE(is_cancelled);
FAIL_TEST_IF_ERR(TRITONBACKEND_ResponseFactoryDelete(response_factory));
}
int
main(int argc, char** argv)
{
#ifdef TRITON_ENABLE_LOGGING
LOG_SET_VERBOSE(2);
#endif
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}