ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
BackendFactory.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#include "BackendFactory.h"
5
6#include "RebotBackend.h"
7#include "Utilities.h"
8
9#include <boost/algorithm/string.hpp>
10#ifdef CHIMERATK_HAVE_PCIE_BACKEND
11# include "PcieBackend.h"
12#endif
13#ifdef CHIMERATK_HAVE_XDMA_BACKEND
14# include "XdmaBackend.h"
15#endif
16#ifdef CHIMERATK_HAVE_UIO_BACKEND
17# include "UioBackend.h"
18#endif
19// Clang tidy reports a false positive. It seems to be case-sensitive although it should not be.
20// clang-format is fixing this correctly.
21// NOLINTNEXTLINE(llvm-include-order)
22#include "DeviceAccessVersion.h"
23#include "DMapFileParser.h"
24#include "DummyBackend.h"
25#include "Exception.h"
28#include "SharedDummyBackend.h"
29#include "SubdeviceBackend.h"
30
31#include <boost/bind/bind.hpp>
32#include <boost/function.hpp>
33
34#include <dlfcn.h>
35
36#include <utility>
37
38#ifdef _DEBUG
39# include <iostream>
40#endif
41
42namespace ChimeraTK {
43
44 /********************************************************************************************************************/
45
46 void BackendFactory::registerBackendType(const std::string& backendType,
47 boost::shared_ptr<DeviceBackend> (*creatorFunction)(
48 std::string address, std::map<std::string, std::string> parameters),
49 const std::vector<std::string>& sdmParameterNames, const std::string& version) {
50#ifdef _DEBUG
51 std::cout << "adding:" << backendType << std::endl << std::flush;
52#endif
54
55 if(creatorMap.find(backendType) != creatorMap.end()) {
56 throw ChimeraTK::logic_error("A backend with the type name '" + backendType + "' has already been registered.");
57 }
58
59 if(version != CHIMERATK_DEVICEACCESS_VERSION) {
60 // Register a function that throws an exception with the message when trying
61 // to create a backend. We do not throw here because this would throw an
62 // uncatchable exception inside the static instance of a backend registerer,
63 // which would violate the policy that a dmap file with broken backends
64 // should be usable if the particular backend is not used.
65 std::stringstream errorMessage;
66 errorMessage << "Backend plugin '" << backendType << "' compiled with wrong DeviceAccess version " << version
67 << ". Please recompile with version " << CHIMERATK_DEVICEACCESS_VERSION;
68 std::string errorMessageString = errorMessage.str();
69 // FIXME #11279 Implement API breaking changes from linter warnings
70 // NOLINTBEGIN(performance-unnecessary-value-param)
71 creatorMap_compat[make_pair(backendType, "")] = [errorMessageString](std::string host, std::string instance,
72 std::list<std::string> parameters, std::string mapFileName) {
74 host, instance, parameters, mapFileName, errorMessageString);
75 };
76 creatorMap[backendType] = [errorMessageString](std::string,
77 std::map<std::string, std::string>) -> boost::shared_ptr<ChimeraTK::DeviceBackend> {
78 // NOLINTEND(performance-unnecessary-value-param)
79 throw ChimeraTK::logic_error(errorMessageString);
80 };
81 return;
82 }
83 creatorMap[backendType] = creatorFunction;
84 // FIXME #11279 Implement API breaking changes from linter warnings
85 // NOLINTBEGIN(performance-unnecessary-value-param)
86 creatorMap_compat[make_pair(backendType, "")] = [creatorFunction, sdmParameterNames](std::string,
87 std::string instance, std::list<std::string> parameters,
88 std::string mapFileName) {
89 // NOLINTEND(performance-unnecessary-value-param)
90 std::map<std::string, std::string> pars;
91 size_t i = 0;
92 for(auto& p : parameters) {
93 if(i >= sdmParameterNames.size()) break;
94 pars[sdmParameterNames[i]] = p;
95 ++i;
96 }
97 if(!mapFileName.empty()) {
98 if(pars["map"].empty()) {
99 pars["map"] = mapFileName;
100 }
101 else {
102 std::cout << "WARNING: You have specified the map file name twice, in "
103 "the parameter list and in the 3rd "
104 "column of the DMAP file."
105 << std::endl;
106 std::cout << "Please only specify the map file name in the parameter list!" << std::endl;
107 }
108 }
109 return creatorFunction(std::move(instance), pars);
110 };
111 }
112
113 /********************************************************************************************************************/
114
115 void BackendFactory::registerBackendType(const std::string& interface, const std::string& protocol,
116 boost::shared_ptr<DeviceBackend> (*creatorFunction)(
117 std::string host, std::string instance, std::list<std::string> parameters, std::string mapFileName),
118 const std::string& version) {
119#ifdef _DEBUG
120 std::cout << "adding:" << interface << std::endl << std::flush;
121#endif
123 if(version != CHIMERATK_DEVICEACCESS_VERSION) {
124 // Register a function that throws an exception with the message when trying
125 // to create a backend. We do not throw here because this would throw an
126 // uncatchable exception inside the static instance of a backend registerer,
127 // which would violate the policy that a dmap file with broken backends
128 // should be usable if the particular backend is not used.
129 std::stringstream errorMessage;
130 errorMessage << "Backend plugin '" << interface << "' compiled with wrong DeviceAccess version " << version
131 << ". Please recompile with version " << CHIMERATK_DEVICEACCESS_VERSION;
132 std::string errorMessageString = errorMessage.str();
133 // FIXME #11279 Implement API breaking changes from linter warnings
134 // NOLINTBEGIN(performance-unnecessary-value-param)
135 creatorMap_compat[make_pair(interface, protocol)] = [errorMessageString](std::string host, std::string instance,
136 std::list<std::string> parameters,
137 std::string mapFileName) {
138 return BackendFactory::failedRegistrationThrowerFunction(
139 host, instance, parameters, mapFileName, errorMessageString);
140 };
141 creatorMap[interface] = [errorMessageString](std::string,
142 std::map<std::string, std::string>) -> boost::shared_ptr<ChimeraTK::DeviceBackend> {
143 // NOLINTEND(performance-unnecessary-value-param)
144 throw ChimeraTK::logic_error(errorMessageString);
145 };
146 return;
147 }
148 creatorMap_compat[make_pair(interface, protocol)] = creatorFunction;
149 // FIXME #11279 Implement API breaking changes from linter warnings
150 // NOLINTBEGIN(performance-unnecessary-value-param)
151 creatorMap[interface] = [interface](std::string,
152 std::map<std::string, std::string>) -> boost::shared_ptr<ChimeraTK::DeviceBackend> {
153 // NOLINTEND(performance-unnecessary-value-param)
154 throw ChimeraTK::logic_error("The backend type '" + interface +
155 "' does not yet support ChimeraTK device "
156 "descriptors! Please update the backend!");
157 };
158 }
159
160 /********************************************************************************************************************/
161
162 void BackendFactory::setDMapFilePath(std::string dMapFilePath) {
163 _dMapFile = std::move(dMapFilePath);
164 loadAllPluginsFromDMapFile();
165 }
166 /********************************************************************************************************************/
167
168 std::string BackendFactory::getDMapFilePath() {
169 return _dMapFile;
170 }
171 /********************************************************************************************************************/
172
173 BackendFactory::BackendFactory() {
174#ifdef CHIMERATK_HAVE_PCIE_BACKEND
175 registerBackendType("pci", &PcieBackend::createInstance, {"map"});
176#endif
177#ifdef CHIMERATK_HAVE_XDMA_BACKEND
178 registerBackendType("xdma", &XdmaBackend::createInstance, {"map"});
179#endif
180#ifdef CHIMERATK_HAVE_UIO_BACKEND
181 registerBackendType("uio", &UioBackend::createInstance, {"map"});
182#endif
183 registerBackendType("dummy", &DummyBackend::createInstance, {"map"});
184 registerBackendType("rebot", &RebotBackend::createInstance, {"ip", "port", "map", "timeout"});
185 registerBackendType("logicalNameMap", &LogicalNameMappingBackend::createInstance, {"map"});
186 registerBackendType("subdevice", &SubdeviceBackend::createInstance, {"map"});
187 registerBackendType("sharedMemoryDummy", &SharedDummyBackend::createInstance, {"map"});
188 registerBackendType("ExceptionDummy", &ExceptionDummy::createInstance, {"map"});
189 }
190 /********************************************************************************************************************/
191
192 BackendFactory& BackendFactory::getInstance() {
193#ifdef _DEBUG
194 std::cout << "getInstance" << std::endl << std::flush;
195#endif
196 static BackendFactory factoryInstance; /** Thread safe in C++11*/
197 return factoryInstance;
198 }
199
200 /********************************************************************************************************************/
201
202 boost::shared_ptr<DeviceBackend> BackendFactory::createBackend(const std::string& aliasOrUri) {
203 std::lock_guard<std::mutex> lock(_mutex);
204
205 if(Utilities::isDeviceDescriptor(aliasOrUri) || Utilities::isSdm(aliasOrUri)) {
206 // it is a URI, directly create a deviceinfo and call the internal creator
207 // function
208 DeviceInfoMap::DeviceInfo deviceInfo;
209 deviceInfo.uri = aliasOrUri;
210 return createBackendInternal(deviceInfo);
211 }
212
213 // it's not a URI. Try finding the alias in the dmap file.
214 if(_dMapFile.empty()) {
215 throw ChimeraTK::logic_error("DMap file not set.");
216 }
217 auto deviceInfo = Utilities::aliasLookUp(aliasOrUri, _dMapFile);
218 return createBackendInternal(deviceInfo);
219 }
220
221 /********************************************************************************************************************/
222
223 boost::shared_ptr<DeviceBackend> BackendFactory::createBackendInternal(const DeviceInfoMap::DeviceInfo& deviceInfo) {
224#ifdef _DEBUG
225 std::cout << "uri to parse" << deviceInfo.uri << std::endl;
226 std::cout << "Entries" << creatorMap.size() << std::endl << std::flush;
227#endif
228
229 // Check if backend already exists
230 auto iterator = _existingBackends.find(deviceInfo.uri);
231 if(iterator != _existingBackends.end()) {
232 auto strongPtr = iterator->second.lock();
233 if(strongPtr) {
234 return strongPtr;
235 }
236 }
237
238 // Check if descriptor string is a ChimeraTK device descriptor
239 if(Utilities::isDeviceDescriptor(deviceInfo.uri)) {
240 auto cdd = Utilities::parseDeviceDesciptor(deviceInfo.uri);
241 try {
242 auto backend = (creatorMap.at(cdd.backendType))(cdd.address, cdd.parameters);
243 _existingBackends[deviceInfo.uri] = backend;
244 return backend;
245 }
246 catch(std::out_of_range&) {
247 throw ChimeraTK::logic_error("Unknown backend: \"" + cdd.backendType + "\" at " + deviceInfo.dmapFileName +
248 ":" + std::to_string(deviceInfo.dmapFileLineNumber) + " for " + deviceInfo.uri);
249 }
250 }
251
252 // Check for deprecated SDM or device node
253 // Note: Deprecation message was added 2022-07-28. Remove functionality past end of 2023.
254 Sdm sdm;
255 if(Utilities::isSdm(deviceInfo.uri)) {
256 sdm = Utilities::parseSdm(deviceInfo.uri);
257 std::cout << "Using the SDM descriptor is deprecated. Please change to CDD (ChimeraTK device descriptor)."
258 << std::endl;
259 }
260 else {
261 sdm = Utilities::parseDeviceString(deviceInfo.uri);
262 std::cout
263 << "Using the device node in a dmap file is deprecated. Please change to CDD (ChimeraTK device descriptor)."
264 << std::endl;
265 }
266 for(auto& iter : creatorMap_compat) {
267 if((iter.first.first == sdm.interface)) {
268 auto backend = (iter.second)(sdm.host, sdm.instance, sdm.parameters, deviceInfo.mapFileName);
269 boost::weak_ptr<DeviceBackend> weakBackend = backend;
270 _existingBackends[deviceInfo.uri] = weakBackend;
271 // return the shared pointer, not the weak pointer
272 return backend;
273 }
274 }
275
276 throw ChimeraTK::logic_error("Unregistered device: Interface = " + sdm.interface + " Protocol = " + sdm.protocol);
277 return {}; // won't execute
278 }
279
280 /********************************************************************************************************************/
281
282 void BackendFactory::loadPluginLibrary(const std::string& soFile) {
283 // reset flag to check if the registerBackedType() function was called
285
286 // open the plugin library. RTLD_LAZY is enough since all symbols are loaded
287 // when needed
288 void* hndl = dlopen(soFile.c_str(), RTLD_LAZY);
289 if(hndl == nullptr) {
290 throw ChimeraTK::logic_error(dlerror());
291 }
292
293 // if no backend was registered, close the library and throw an exception
295 dlclose(hndl);
297 "'" + soFile + "' is not a valid DeviceAccess plugin, it does not register any backends!");
298 }
299 }
300
301 /********************************************************************************************************************/
302
304 if(_dMapFile.empty()) {
305 return;
306 }
307
308 auto dmap = DMapFileParser::parse(_dMapFile);
309
310 for(const auto& lib : dmap->getPluginLibraries()) {
311 try {
313 }
314 catch(ChimeraTK::logic_error& e) {
315 // Ignore library loading errors when doing this automatically for all
316 // plugins in the dmap file to keep dmap files usable if the broken
317 // backends are not used. Print a warning that the loading failed.
318 std::cerr << "Error: Caught exception loading plugin '" << lib << "' specified in dmap file: " << e.what()
319 << std::endl;
320 std::cerr << "Some backends will not be available!" << std::endl;
321 }
322 }
323 }
324
325 /********************************************************************************************************************/
326
327 // FIXME #11279 Implement API breaking changes from linter warnings
328 // NOLINTBEGIN(performance-unnecessary-value-param)
329 boost::shared_ptr<DeviceBackend> BackendFactory::failedRegistrationThrowerFunction(std::string /*host*/,
330 std::string /*instance*/, std::list<std::string> /*parameters*/, std::string /*mapFileName*/,
331 std::string exception_what) {
332 // NOLINTEND(performance-unnecessary-value-param)
333 throw ChimeraTK::logic_error(std::move(exception_what));
334 }
335} // namespace ChimeraTK
void registerBackendType(const std::string &backendType, boost::shared_ptr< DeviceBackend >(*creatorFunction)(std::string address, std::map< std::string, std::string > parameters), const std::vector< std::string > &sdmParameterNames={}, const std::string &deviceAccessVersion=CHIMERATK_DEVICEACCESS_VERSION)
Register a backend by the name backendType with the given creatorFunction.
bool called_registerBackendType
Flag whether the function registerBackendType() was called.
std::map< std::pair< std::string, std::string >, boost::function< boost::shared_ptr< DeviceBackend >(std::string host, std::string instance, std::list< std::string > parameters, std::string mapFileName)> > creatorMap_compat
Compatibility creatorMap for old-style backends which just take a plain list of parameters.
void loadPluginLibrary(const std::string &soFile)
Load a shared library (.so file) with a Backend at run time.
static boost::shared_ptr< DeviceBackend > failedRegistrationThrowerFunction(std::string host, std::string instance, std::list< std::string > parameters, std::string mapFileName, std::string exception_what)
std::string _dMapFile
The dmap file set at run time.
std::map< std::string, boost::function< boost::shared_ptr< DeviceBackend >(std::string address, std::map< std::string, std::string > parameters)> > creatorMap
Holds device type and function pointer to the createInstance function of plugin.
void loadAllPluginsFromDMapFile()
Load all shared libraries specified in the dmap file.
static DeviceInfoMapPointer parse(const std::string &file_name)
Performs parsing of specified DMAP file.
Exception thrown when a logic error has occured.
Definition Exception.h:51
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition Exception.cpp:20
STL namespace.
std::string to_string(const std::string &v)
This structure holds the information of an SDM.
Definition Utilities.h:40
std::string cdd
ctk::Device device