diff --git a/src/platform/linux/cuda.cpp b/src/platform/linux/cuda.cpp index 3944efe1a53..8f3613d2f03 100644 --- a/src/platform/linux/cuda.cpp +++ b/src/platform/linux/cuda.cpp @@ -101,7 +101,9 @@ namespace cuda { static cdf_t cdf; /** - * @brief Log a failed call through a CUDA function table. + * @brief Log a failed CUDA call with its numeric result and safe fallback error text. + * + * Error lookups may fail or return null, so only use text from successful, non-null lookups. * * @param functions CUDA function table. * @param result Result of the call. @@ -110,13 +112,17 @@ namespace cuda { */ inline static int check(const CudaFunctions &functions, CUresult result, const std::string_view &sv) { if (result != CUDA_SUCCESS) { - const char *name; - const char *description; + const char *name = nullptr; + const char *description = nullptr; - functions.cuGetErrorName(result, &name); - functions.cuGetErrorString(result, &description); + if (functions.cuGetErrorName(result, &name) != CUDA_SUCCESS || !name) { + name = "Unknown CUDA error"; + } + if (functions.cuGetErrorString(result, &description) != CUDA_SUCCESS || !description) { + description = "No error description available"; + } - BOOST_LOG(error) << sv << name << ':' << description; + BOOST_LOG(error) << sv << name << " (" << static_cast(result) << "): " << description; return -1; } diff --git a/tests/unit/platform/linux/test_cuda.cpp b/tests/unit/platform/linux/test_cuda.cpp index 04bcdd1894e..f34a2b8ee49 100644 --- a/tests/unit/platform/linux/test_cuda.cpp +++ b/tests/unit/platform/linux/test_cuda.cpp @@ -17,6 +17,7 @@ #include #include #include + #include #include #include #include @@ -2185,6 +2186,91 @@ TEST_F(CudaGlDriverTest, PlaneTextureRegistration) { EXPECT_EQ(cuda::register_plane_texture(api().cuda, &resource, 8), -1); } +namespace { + /** + * @brief CUDA error lookup behavior and the text that is safe to log. + */ + struct cuda_error_lookup_case_t { + tcuGetErrorName *lookup; ///< Error name or description lookup with the shared CUDA signature. + const char *expected; ///< Text to log, or nullptr when the appropriate fallback is required. + }; +} // namespace + +TEST(CudaErrorLoggingTest, HandlesLookupFailuresAndNullTextIndependently) { + const std::array cases { + cuda_error_lookup_case_t {[](CUresult, const char **text) { + *text = "resolved"; + return CUDA_SUCCESS; + }, + "resolved"}, + cuda_error_lookup_case_t {[](CUresult, const char **) { + return cu_failed; + }, + nullptr}, + cuda_error_lookup_case_t {[](CUresult, const char **text) { + *text = reinterpret_cast(std::uintptr_t {1}); + return cu_failed; + }, + nullptr}, + cuda_error_lookup_case_t {[](CUresult, const char **text) { + *text = nullptr; + return CUDA_SUCCESS; + }, + nullptr}, + }; + + for (std::size_t name_case = 0; name_case < cases.size(); ++name_case) { + for (std::size_t description_case = 0; description_case < cases.size(); ++description_case) { + SCOPED_TRACE(testing::Message() << "name=" << name_case << ", description=" << description_case); + api() = fake_api_t {}; + auto functions = fake_cuda_functions(); + functions.cuGetErrorName = cases[name_case].lookup; + functions.cuGetErrorString = cases[description_case].lookup; + functions.cuGraphicsGLRegisterImage = [](CUgraphicsResource *, GLuint, GLenum, unsigned int) { + return static_cast(100); // CUDA_ERROR_NO_DEVICE is not named in nv-codec-headers. + }; + CUgraphicsResource resource = nullptr; + logging::log_flush(); + std::ifstream log_file {"test_sunshine.log", std::ios::ate}; + ASSERT_TRUE(log_file); + const auto log_position = log_file.tellg(); + + EXPECT_EQ(cuda::register_plane_texture(functions, &resource, 7), -1); + + logging::log_flush(); + log_file.seekg(log_position); + std::string line; + ASSERT_TRUE(static_cast(std::getline(log_file, line))); + const std::string expected = std::string {"Couldn't register plane texture: "} + + (cases[name_case].expected ? cases[name_case].expected : "Unknown CUDA error") + + " (100): " + + (cases[description_case].expected ? cases[description_case].expected : "No error description available"); + EXPECT_NE(line.find(expected), std::string::npos) << line; + } + } +} + +TEST(CudaErrorLoggingTest, SuccessfulCallsDoNotLookUpOrLogErrors) { + api() = fake_api_t {}; + auto functions = fake_cuda_functions(); + functions.cuGetErrorName = functions.cuGetErrorString = [](CUresult, const char **) { + ADD_FAILURE() << "Successful CUDA calls must not look up errors"; + return cu_failed; + }; + CUgraphicsResource resource = nullptr; + logging::log_flush(); + std::ifstream log_file {"test_sunshine.log", std::ios::ate}; + ASSERT_TRUE(log_file); + const auto log_position = log_file.tellg(); + + EXPECT_EQ(cuda::register_plane_texture(functions, &resource, 7), 0); + + logging::log_flush(); + log_file.seekg(log_position); + EXPECT_EQ(log_file.peek(), std::ifstream::traits_type::eof()); + EXPECT_EQ(resource, &api().resources[0]); +} + TEST_F(CudaGlDriverTest, GlCommandsWaitDeletesItsFence) { EXPECT_EQ(cuda::wait_for_gl_commands(), 0);