diff --git a/CMakeLists.txt b/CMakeLists.txt index ccdfe0db062bc3ce52c1fca5df02608a4fdf3191..a2d3ce9076b014027a5df4a855b1feb212bbd05b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,13 @@ install(TARGETS arbor-config-defs EXPORT arbor-targets) # Interface library `arbor-private-deps` collects dependencies, options etc. # for the arbor library. add_library(arbor-private-deps INTERFACE) -target_link_libraries(arbor-private-deps INTERFACE arbor-config-defs ext-random123 ${CMAKE_DL_LIBS}) +target_link_libraries(arbor-private-deps INTERFACE arbor-config-defs ext-random123) +if (UNIX) + target_compile_definitions(arbor-config-defs INTERFACE ARB_HAVE_DL) + target_link_libraries(arbor-private-deps INTERFACE ${CMAKE_DL_LIBS}) +else() + message(FATAL_ERROR "No support for dynamic loading on current platform.") +endif () install(TARGETS arbor-private-deps EXPORT arbor-targets) # Interface library `arborenv-private-deps` collects dependencies, options etc. diff --git a/arbor/arbexcept.cpp b/arbor/arbexcept.cpp index 185a0d5cbc923d81018f8d81943fb14eb0fd9399..fc4ffe7ec585c20e6b7141d1e705acac35d35e27 100644 --- a/arbor/arbexcept.cpp +++ b/arbor/arbexcept.cpp @@ -124,14 +124,16 @@ range_check_failure::range_check_failure(const std::string& whatstr, double valu {} file_not_found_error::file_not_found_error(const std::string &fn) - : arbor_exception(pprintf("Could not find file '{}'", fn)), + : arbor_exception(pprintf("Could not find readable file at '{}'", fn)), filename{fn} {} -bad_catalogue_error::bad_catalogue_error(const std::string &fn, const std::string& call) - : arbor_exception(pprintf("Error in '{}' while opening catalogue '{}'", call, fn)), - filename{fn}, - failed_call{call} +bad_catalogue_error::bad_catalogue_error(const std::string& msg) + : arbor_exception(pprintf("Error while opening catalogue '{}'", msg)) +{} + +bad_catalogue_error::bad_catalogue_error(const std::string& msg, const std::any& pe) + : arbor_exception(pprintf("Error while opening catalogue '{}'", msg)), platform_error(pe) {} unsupported_abi_error::unsupported_abi_error(size_t v): diff --git a/arbor/include/arbor/arbexcept.hpp b/arbor/include/arbor/arbexcept.hpp index 01d4e91e21bd86c78ab2f3e7b0ae366cd8d1472a..2185c5dfbfef704fded3e0090fa655aa649a0288 100644 --- a/arbor/include/arbor/arbexcept.hpp +++ b/arbor/include/arbor/arbexcept.hpp @@ -146,10 +146,11 @@ struct file_not_found_error: arbor_exception { std::string filename; }; +// struct bad_catalogue_error: arbor_exception { - bad_catalogue_error(const std::string& fn, const std::string& call); - std::string filename; - std::string failed_call; + bad_catalogue_error(const std::string&); + bad_catalogue_error(const std::string&, const std::any&); + std::any platform_error; }; // ABI errors diff --git a/arbor/mechcat.cpp b/arbor/mechcat.cpp index 3109f17e3611bade1a152f7f27c2548848adf1ca..829339fd39adf5ce96a543b746accb4c607b1945 100644 --- a/arbor/mechcat.cpp +++ b/arbor/mechcat.cpp @@ -5,8 +5,6 @@ #include <vector> #include <cassert> -#include <dlfcn.h> - #include <arbor/version.hpp> #include <arbor/arbexcept.hpp> #include <arbor/mechcat.hpp> @@ -17,6 +15,7 @@ #include "util/rangeutil.hpp" #include "util/maputil.hpp" #include "util/span.hpp" +#include "util/dl.hpp" /* Notes on implementation: * @@ -584,21 +583,18 @@ std::pair<mechanism_ptr, mechanism_overrides> mechanism_catalogue::instance_impl mechanism_catalogue::~mechanism_catalogue() = default; -static void check_dlerror(const std::string& fn, const std::string& call) { - auto error = dlerror(); - if (error) { throw arb::bad_catalogue_error{fn, call}; } -} - const mechanism_catalogue& load_catalogue(const std::string& fn) { typedef const void* global_catalogue_t(); - - auto plugin = dlopen(fn.c_str(), RTLD_LAZY); - check_dlerror(fn, "dlopen"); - assert(plugin); - - auto get_catalogue = (global_catalogue_t*)dlsym(plugin, "get_catalogue"); - check_dlerror(fn, "dlsym"); - + global_catalogue_t* get_catalogue = nullptr; + try { + auto plugin = dl_open(fn); + get_catalogue = dl_get_symbol<global_catalogue_t*>(plugin, "get_catalogue"); + } catch(dl_error& e) { + throw bad_catalogue_error{e.what(), {e}}; + } + if (!get_catalogue) { + throw bad_catalogue_error{util::pprintf("Unusable symbol 'get_catalogue' in shared object '{}'", fn)}; + } /* NOTE We do not free the DSO handle here and accept retaining the handles until termination since the mechanisms provided by the catalogue may have a different lifetime than the actual catalogue itfself. This is not a diff --git a/arbor/util/dl.hpp b/arbor/util/dl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8454036eb75fbb4b34294baab962535c39db407b --- /dev/null +++ b/arbor/util/dl.hpp @@ -0,0 +1,5 @@ +#pragma once + +#ifdef ARB_HAVE_DL +#include "util/dl_platform_posix.hpp" +#endif diff --git a/arbor/util/dl_platform_posix.hpp b/arbor/util/dl_platform_posix.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e2442c15d0191c8beea7676ec46c2a8905eef84f --- /dev/null +++ b/arbor/util/dl_platform_posix.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include <fstream> +#include <string> + +#include <dlfcn.h> + +#include <arbor/arbexcept.hpp> + +#include "util/strprintf.hpp" + +namespace arb { + +struct dl_error: arbor_exception { + dl_error(const std::string& msg): arbor_exception{msg} {} +}; + +struct dl_handle { + void* dl = nullptr; +}; + +inline +dl_handle dl_open(const std::string& fn) { + // Test if we can open file + if (!std::ifstream{fn.c_str()}.good()) throw file_not_found_error{fn}; + // Call once to clear errors not caused by us + dlerror(); + // Try to open shared object + auto result = dlopen(fn.c_str(), RTLD_LAZY); + // dlopen fails by returning NULL + if (!result) throw dl_error{util::pprintf("[POSIX] dl_open failed with: {}", dlerror())}; + return {result}; +} + +template<typename T> inline +T dl_get_symbol(const dl_handle& handle, const std::string& symbol) { + // Call once to clear errors not caused by us + dlerror(); + // Get symbol from shared object, may return NULL if that is what symbol refers to + auto result = dlsym(handle.dl, symbol.c_str()); + // ... so we can only ask for dlerror here + if (auto error = dlerror()) throw dl_error{util::pprintf("[POSIX] dl_get_symbol failed with: {}", error)}; + return reinterpret_cast<T>(result); +} + +inline +void dl_close(dl_handle& handle) { + dlclose(handle.dl); + handle.dl = nullptr; +} + +} diff --git a/python/pyarb.cpp b/python/pyarb.cpp index 9a6782b87fccf0ae6209c2b516bd155b0e6ef591..85871de6fc0c2216758bf490c05ad0c0de705740 100644 --- a/python/pyarb.cpp +++ b/python/pyarb.cpp @@ -3,6 +3,7 @@ #include <arbor/spike.hpp> #include <arbor/common_types.hpp> +#include <arbor/arbexcept.hpp> #include <arbor/version.hpp> #include "pyarb.hpp" @@ -43,6 +44,9 @@ PYBIND11_MODULE(_arbor, m) { m.doc() = "arbor: multicompartment neural network models."; m.attr("__version__") = ARB_VERSION; + // Translate Arbor errors -> Python exceptions. + pybind11::register_exception<arb::file_not_found_error>(m, "FileNotFoundError", PyExc_FileNotFoundError); + pyarb::register_cable_loader(m); pyarb::register_cable_probes(m, global_ptr); pyarb::register_cells(m); diff --git a/python/test/unit/test_catalogues.py b/python/test/unit/test_catalogues.py index 4009192ba3a38bbd0c2701b96e3f59103e5eb69d..00cc2ff8afb5e28b7a4a9b56ddc4c9c4e9b5cf47 100644 --- a/python/test/unit/test_catalogues.py +++ b/python/test/unit/test_catalogues.py @@ -48,7 +48,7 @@ class recipe(arb.recipe): class Catalogues(unittest.TestCase): def test_nonexistent(self): - with self.assertRaises(RuntimeError): + with self.assertRaises(FileNotFoundError): arb.load_catalogue("_NO_EXIST_.so") def test_shared_catalogue(self): diff --git a/test/unit/test_mechcat.cpp b/test/unit/test_mechcat.cpp index 488534d00ff2e44eda2f0687b9257ce96f0d9af4..4d3d0cd31c0a1375c83fcce0d9e0cfa939f7fc02 100644 --- a/test/unit/test_mechcat.cpp +++ b/test/unit/test_mechcat.cpp @@ -280,7 +280,7 @@ TEST(mechcat, names) { #ifdef USE_DYNAMIC_CATALOGUES TEST(mechcat, loading) { - EXPECT_THROW(load_catalogue(LIBDIR "/does-not-exist-catalogue.so"), bad_catalogue_error); + EXPECT_THROW(load_catalogue(LIBDIR "/does-not-exist-catalogue.so"), file_not_found_error); EXPECT_THROW(load_catalogue(LIBDIR "/libarbor.a"), bad_catalogue_error); const mechanism_catalogue* cat = nullptr; EXPECT_NO_THROW(cat = &load_catalogue(LIBDIR "/dummy-catalogue.so"));