-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added mutex locks in register_target.cpp and created a multithreading… #2224
Changes from 10 commits
efb502b
52d75e0
bab76d5
d8f1ebb
f6b08c2
55b87a6
81d4516
c61c099
3d812cd
d0db0cd
6befe93
fd21950
d9ad082
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
/* | ||
* The MIT License (MIT) | ||
* | ||
* Copyright (c) 2015-2022 Advanced Micro Devices, Inc. All rights reserved. | ||
* Copyright (c) 2015-2023 Advanced Micro Devices, Inc. All rights reserved. | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
|
@@ -21,6 +21,7 @@ | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
#include <mutex> | ||
#include <string> | ||
#include <unordered_map> | ||
#include <migraphx/register_target.hpp> | ||
|
@@ -33,42 +34,88 @@ inline namespace MIGRAPHX_INLINE_NS { | |
void store_target_lib(const dynamic_loader& lib) | ||
{ | ||
static std::vector<dynamic_loader> target_loader; | ||
static std::mutex mutex; | ||
std::unique_lock<std::mutex> lock(mutex); | ||
|
||
target_loader.emplace_back(lib); | ||
} | ||
|
||
/** | ||
* Returns a singleton map of targets and names. | ||
*/ | ||
std::unordered_map<std::string, target>& target_map() | ||
{ | ||
static std::unordered_map<std::string, target> m; // NOLINT | ||
return m; | ||
} | ||
|
||
/** | ||
* Returns a singleton mutex used by the various register_target methods. | ||
*/ | ||
std::mutex& target_mutex() | ||
{ | ||
static std::mutex m; // NOLINT | ||
return m; | ||
} | ||
|
||
void register_target_init() { (void)target_map(); } | ||
|
||
void unregister_target(const std::string& name) | ||
{ | ||
std::unique_lock<std::mutex> lock(target_mutex()); | ||
assert(target_map().count(name)); | ||
target_map().erase(name); | ||
} | ||
|
||
void register_target(const target& t) { target_map()[t.name()] = t; } | ||
/** | ||
* Insert a target name in the target_map; thread safe. | ||
*/ | ||
void register_target(const target& t) | ||
{ | ||
std::unique_lock<std::mutex> lock(target_mutex()); | ||
target_map()[t.name()] = t; | ||
} | ||
|
||
/** | ||
* Search for a target by name in the target_map; thread-safe. | ||
*/ | ||
migraphx::optional<target> find_target(const std::string& name) | ||
{ | ||
// search for match or return none | ||
std::unique_lock<std::mutex> lock(target_mutex()); | ||
const auto it = target_map().find(name); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add unlock before each return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need to--see above |
||
if(it == target_map().end()) | ||
return nullopt; | ||
return it->second; | ||
} | ||
|
||
/** | ||
* Get a target by name. Load target library and register target if needed. | ||
* Thread safe. | ||
*/ | ||
target make_target(const std::string& name) | ||
{ | ||
if(not contains(target_map(), name)) | ||
// no lock required here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we not concerned with something trying to reregister a target in another thread? If between find_target, we end up doing an additional find() Is the intent to not expose find_target() outside of register_target? If so, why not just add the locking around here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is that find_target does contain a lock, so make_target is already protected. The second thread would have to wait for the first thread to finish before trying to register it, then see that it's already there. |
||
auto t = find_target(name); | ||
if(t == nullopt) | ||
{ | ||
std::string target_name = "libmigraphx_" + name + ".so"; | ||
// register_target is called by this | ||
store_target_lib(dynamic_loader(target_name)); | ||
t = find_target(name); | ||
} | ||
const auto it = target_map().find(name); | ||
if(it == target_map().end()) | ||
{ | ||
MIGRAPHX_THROW("Requested target '" + name + "' is not loaded or not supported"); | ||
} | ||
return it->second; | ||
// at this point we should always have a target | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it guaranteed to have a target at this point? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, we should check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's redundant since |
||
|
||
return *t; | ||
} | ||
|
||
/** | ||
* Get list of names of registered targets. | ||
*/ | ||
std::vector<std::string> get_targets() | ||
{ | ||
std::unique_lock<std::mutex> lock(target_mutex()); | ||
std::vector<std::string> result; | ||
std::transform(target_map().begin(), | ||
target_map().end(), | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
*/ | ||
#include <migraphx/register_target.hpp> | ||
#include <migraphx/target.hpp> | ||
#include <thread> | ||
#include "test.hpp" | ||
|
||
TEST_CASE(make_target) | ||
|
@@ -50,4 +51,43 @@ TEST_CASE(targets) | |
EXPECT(ts.size() >= 1); | ||
} | ||
|
||
TEST_CASE(concurrent_targets) | ||
bpickrel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
std::vector<std::thread> threads; | ||
#ifdef HAVE_GPU | ||
std::string target_name = "gpu"; | ||
#elif defined(HAVE_CPU) | ||
std::string target_name = "cpu"; | ||
#elif defined(HAVE_FPGA) | ||
std::string target_name = "fpga"; | ||
#else | ||
std::string target_name = "ref"; | ||
#endif | ||
|
||
auto n_threads = std::thread::hardware_concurrency() * 4; | ||
|
||
for(auto i = 0u; i < n_threads; i++) | ||
{ | ||
auto thread_body = [&target_name]() { | ||
// TODO: remove all existing targets, if any. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Followup: I disabled this extra test but when I tested the existing fixes with amdinfer, the Inference Server, the segmentation violation still occurs. I'm going to recommend we commit this PR as is but continue to search for the root cause of Issue #2208. |
||
// The existing code cannot pass a test in which different threads | ||
// register and unregister the same targets; not known if this is | ||
// needed in any deployed product. | ||
// std::vector<std::string> target_list = migraphx::get_targets(); | ||
// for(const auto& tt : target_list) | ||
// migraphx::unregister_target(tt); | ||
|
||
auto ref_target = migraphx::make_target(target_name); | ||
migraphx::register_target(ref_target); | ||
bpickrel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
EXPECT(test::throws([&] { ref_target = migraphx::make_target("xyz"); })); | ||
|
||
migraphx::get_targets(); | ||
}; | ||
|
||
threads.emplace_back(thread_body); | ||
} | ||
for(auto& tt : threads) | ||
tt.join(); | ||
bpickrel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
int main(int argc, const char* argv[]) { test::run(argc, argv); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be unlocking here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With std::unique_lock, the destructor automatically unlocks it when it goes out of scope. This is supposed not only to be a coding convenience, but gives protection when things are interrupted by an exception. From cplusplus.com:
"This class guarantees an unlocked status on destruction (even if not called explicitly). Therefore it is especially useful as an object with automatic duration, as it guarantees the mutex object is properly unlocked in case an exception is thrown."