Skip to content

Commit a157c56

Browse files
authored
[cublas] Fix race condition in cublas handle deletion (#136)
* [cublas] Fix race condition in cublas handle deletion This was causing segmentation faults at the end of the program when running tests. Cublas handles need to be created per thread and per context, therefore they're currently being stored in a `static thread_local` map, mapping contexts to cublas handles. These handles are deleted in two places, the map destructor called when the thread is deleted, or the context destructor, where a callback is registered on the context to delete the cublas handle. Since there is two places where a handle can be deleted and that these two will likely run in separate threads, the handle was placed in an C++ atomic to avoid race conditions, and allocated on the heap so that it can stay alive when one of either the thread or the context is deleted. But there was two issues with this, the main one is that the callback registered on the context was not given the pointer to the atomic handle, rather the pointer to the slot in the `thread_local` map where the atomic pointer was stored. Which meant that whenever the thread would be deleted before the context, the pointer available to the callback would be invalid as the map was deleted. The solution for that is to simply pass directly the pointer to the atomic handle to the callback. The second issue is that both of these places were deleting the atomic in all cases, which means one of the deletion was invalid. The solution for this is to only do the deletion in the last one being called, that is to say the one which will get a `nullptr` when atomically accessing the handle pointer. With these two patches the tests are running fine. * [cublas] Fix formatting of cublas handle patch * Fix half types for DPC++ `half` has been removed from the global scope in DPC++, see intel/llvm#4818
1 parent 738c067 commit a157c56

File tree

3 files changed

+34
-30
lines changed

3 files changed

+34
-30
lines changed

include/oneapi/mkl/types.hpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,8 @@ enum class order : char {
108108
} //namespace mkl
109109
} //namespace oneapi
110110

111-
// Workaround for supporting ::half for hipSYCL
111+
// Workaround for supporting ::half for hipSYCL and DPC++
112112
// TODO: This should be removed after the interface is SYCL2020 conformant
113-
#ifdef __HIPSYCL__
114113
using ::cl::sycl::half;
115-
#endif
116114

117115
#endif //_ONEMKL_TYPES_HPP_

src/blas/backends/cublas/cublas_handle.hpp

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,33 @@ namespace mkl {
2626
namespace blas {
2727
namespace cublas {
2828

29-
template<typename T>
29+
template <typename T>
3030
struct cublas_handle {
3131
using handle_container_t = std::unordered_map<T, std::atomic<cublasHandle_t> *>;
3232
handle_container_t cublas_handle_mapper_{};
33-
~cublas_handle() noexcept(false){
34-
for (auto &handle_pair : cublas_handle_mapper_) {
35-
cublasStatus_t err;
36-
if (handle_pair.second != nullptr) {
37-
auto handle = handle_pair.second->exchange(nullptr);
38-
if (handle != nullptr) {
39-
CUBLAS_ERROR_FUNC(cublasDestroy, err, handle);
40-
handle = nullptr;
33+
~cublas_handle() noexcept(false) {
34+
for (auto &handle_pair : cublas_handle_mapper_) {
35+
cublasStatus_t err;
36+
if (handle_pair.second != nullptr) {
37+
auto handle = handle_pair.second->exchange(nullptr);
38+
if (handle != nullptr) {
39+
CUBLAS_ERROR_FUNC(cublasDestroy, err, handle);
40+
handle = nullptr;
41+
}
42+
else {
43+
// if the handle is nullptr it means the handle was already
44+
// destroyed by the ContextCallback and we're free to delete the
45+
// atomic object.
46+
delete handle_pair.second;
47+
}
48+
49+
handle_pair.second = nullptr;
4150
}
42-
delete handle_pair.second;
43-
handle_pair.second = nullptr;
4451
}
52+
cublas_handle_mapper_.clear();
4553
}
46-
cublas_handle_mapper_.clear();
47-
}
4854
};
4955

50-
5156
} // namespace cublas
5257
} // namespace blas
5358
} // namespace mkl

src/blas/backends/cublas/cublas_scope_handle.cpp

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,21 @@ CublasScopedContextHandler::~CublasScopedContextHandler() noexcept(false) {
6363
}
6464

6565
void ContextCallback(void *userData) {
66-
auto *ptr = static_cast<std::atomic<cublasHandle_t> **>(userData);
66+
auto *ptr = static_cast<std::atomic<cublasHandle_t> *>(userData);
6767
if (!ptr) {
6868
return;
6969
}
70-
if (*ptr != nullptr) {
71-
auto handle = (*ptr)->exchange(nullptr);
72-
if (handle != nullptr) {
73-
cublasStatus_t err1;
74-
CUBLAS_ERROR_FUNC(cublasDestroy, err1, handle);
75-
handle = nullptr;
76-
}
77-
delete *ptr;
78-
*ptr = nullptr;
70+
auto handle = ptr->exchange(nullptr);
71+
if (handle != nullptr) {
72+
cublasStatus_t err1;
73+
CUBLAS_ERROR_FUNC(cublasDestroy, err1, handle);
74+
handle = nullptr;
75+
}
76+
else {
77+
// if the handle is nullptr it means the handle was already destroyed by
78+
// the cublas_handle destructor and we're free to delete the atomic
79+
// object.
80+
delete ptr;
7981
}
8082
}
8183

@@ -113,9 +115,8 @@ cublasHandle_t CublasScopedContextHandler::get_handle(const cl::sycl::queue &que
113115
auto insert_iter = handle_helper.cublas_handle_mapper_.insert(
114116
std::make_pair(piPlacedContext_, new std::atomic<cublasHandle_t>(handle)));
115117

116-
auto ptr = &(insert_iter.first->second);
117-
118-
sycl::detail::pi::contextSetExtendedDeleter(placedContext_, ContextCallback, ptr);
118+
sycl::detail::pi::contextSetExtendedDeleter(placedContext_, ContextCallback,
119+
insert_iter.first->second);
119120

120121
return handle;
121122
}

0 commit comments

Comments
 (0)