ChimeraTK-ApplicationCore 04.06.00
Loading...
Searching...
No Matches
CircularDependencyDetector.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
5
6#include "Application.h"
7#include "ApplicationModule.h"
8#include "DeviceManager.h"
9
10/**********************************************************************************************************************/
11
12namespace ChimeraTK::detail {
13
14 void CircularDependencyDetector::registerDependencyWait(VariableNetworkNode& node) {
15 assert(node.getType() == NodeType::Application);
16 std::unique_lock<std::mutex> lock(_mutex);
17
18 auto* dependent = dynamic_cast<Module*>(node.getOwningModule())->findApplicationModule();
19
20 // register the waiting node in the map (used for the detectBlockedModules() thread). This is done also for Device
21 // variables etc.
22 _awaitedNodes[dependent] = node;
23
24 // checking of direct circular dependencies is only done for Application-type feeders
25 auto feedingModule = node.getModel().visit(Model::returnApplicationModule, Model::keepPvAccess,
26 Model::keepApplicationModules, Model::adjacentInSearch, Model::returnFirstHit(Model::ApplicationModuleProxy{}));
27 if(!feedingModule.isValid()) {
28 return;
29 }
30 Module* dependency = &feedingModule.getApplicationModule();
31
32 // If a module depends on itself, the detector would always detect a circular dependency, even if it is resolved
33 // by writing initial values in prepare(). Hence we do not check anything in this case.
34 if(dependent == dependency) {
35 return;
36 }
37
38 // Register dependent-dependency relation in map
39 _awaitedVariables[dependent] = node.getQualifiedName();
40 _waitMap[dependent] = dependency;
41
42 // check for circular dependencies
43 auto* depdep = dependency;
44 while(_waitMap.find(depdep) != _waitMap.end()) {
45 auto* depdep_prev = depdep;
46 depdep = _waitMap[depdep];
47 if(depdep == dependent) {
48 // The detected circular dependency might still resolve itself, because registerDependencyWait() is called even
49 // if initial values are already present in the queues.
50 for(size_t i = 0; i < 1000; ++i) {
51 // give other thread a chance to read their initial value
52 lock.unlock();
53 usleep(10000);
54 lock.lock();
55 // if the module depending on an initial value for "us" is no longer waiting for us to send an initial value,
56 // the circular dependency is resolved.
57 if(_waitMap.find(depdep_prev) == _waitMap.end() || _waitMap[depdep] != dependent) {
58 return;
59 }
60 }
61
62 std::cerr << "*** Circular dependency of ApplicationModules found while waiting for initial values!"
63 << std::endl;
64 std::cerr << std::endl;
65
66 std::cerr << dependent->getQualifiedNameWithType() << " waits for " << node.getQualifiedName()
67 << " from:" << std::endl;
68 auto* depdep2 = dependency;
69 while(_waitMap.find(depdep2) != _waitMap.end()) {
70 auto waitsFor = _awaitedVariables[depdep2];
71 std::cerr << depdep2->getQualifiedNameWithType();
72 if(depdep2 == dependent) {
73 break;
74 }
75 depdep2 = _waitMap[depdep2];
76 std::cerr << " waits for " << waitsFor << " from:" << std::endl;
77 }
78 std::cerr << "." << std::endl;
79
80 std::cerr << std::endl;
81 std::cerr
82 << "Please provide an initial value in the prepare() function of one of the involved ApplicationModules!"
83 << std::endl;
84
85 throw ChimeraTK::logic_error("Circular dependency of ApplicationModules while waiting for initial values");
86 }
87 // Give other threads a chance to add to the wait map
88 lock.unlock();
89 usleep(10000);
90 lock.lock();
91 }
92 }
93
94 /********************************************************************************************************************/
95
96 void CircularDependencyDetector::printWaiters(std::ostream& stream) {
97 if(_waitMap.empty()) {
98 return;
99 }
100 stream << "The following modules are still waiting for initial values:" << std::endl;
101 for(auto& waiters : _waitMap) {
102 stream << waiters.first->getQualifiedNameWithType() << " waits for " << _awaitedVariables[waiters.first]
103 << " from " << waiters.second->getQualifiedNameWithType() << std::endl;
104 }
105 stream << "(end of list)" << std::endl;
106 }
107
108 /********************************************************************************************************************/
109
110 void CircularDependencyDetector::unregisterDependencyWait(VariableNetworkNode& node) {
111 assert(node.getType() == NodeType::Application);
112 std::lock_guard<std::mutex> lock(_mutex);
113 auto* mod = dynamic_cast<Module*>(node.getOwningModule())->findApplicationModule();
114 _waitMap.erase(mod);
115 _awaitedVariables.erase(mod);
116 _awaitedNodes.erase(mod);
117 }
118
119 /********************************************************************************************************************/
120
121 void CircularDependencyDetector::startDetectBlockedModules() {
122 _thread = boost::thread([this] { detectBlockedModules(); });
123 }
124
125 /********************************************************************************************************************/
126
127 void CircularDependencyDetector::detectBlockedModules() {
128 Application::registerThread("CircDepDetector");
129
130 auto& app = Application::getInstance();
131 while(true) {
132 // wait some time to slow down this check
133 boost::this_thread::sleep_for(boost::chrono::seconds(60));
134
135 // check for modules which did not yet reach their mainLoop
136 bool allModulesEnteredMainLoop = true;
137 for(auto* module : app.getSubmoduleListRecursive()) {
138 // Note: We are "abusing" this flag which was introduced for the testable mode. It actually just shows whether
139 // the mainLoop() has been called already (resp. will be called very soon). It is not depending on the
140 // testableMode. FIXME: Rename this flag!
141 if(module->hasReachedTestableMode()) {
142 continue;
143 }
144
145 // Found a module which is still waiting for initial values
146 allModulesEnteredMainLoop = false;
147
148 // require lock against concurrent accesses on the maps
149 std::lock_guard<std::mutex> lock(_mutex);
150
151 // Check if module has registered a dependency wait. If not, situation will either resolve soon or module will
152 // register a dependency wait soon. Both cases can be found in the next iteration.
153 if(_awaitedNodes.find(module) == _awaitedNodes.end()) {
154 continue;
155 }
156
157 auto* appModule = dynamic_cast<ApplicationModule*>(module);
158 if(!appModule) {
159 // only ApplicationModule can have hasReachedTestableMode() == true, so it should not happen
160 logger(Logger::Severity::warning, "CircularDependencyDetector")
161 << "found non-application module: " << module->getQualifiedNameWithType() << std::endl;
162 continue;
163 }
164
165 // Iteratively search for reason the module blocks
166 std::set<Module*> searchedModules;
167 std::function<void(const VariableNetworkNode& node)> iterativeSearch = [&](const VariableNetworkNode& node) {
168 auto visitor = [&](auto proxy) {
169 if constexpr(Model::isApplicationModule(proxy)) {
170 // fed by other ApplicationModule: check if that one is waiting, too
171 auto* feedingAppModule = dynamic_cast<ApplicationModule*>(&proxy.getApplicationModule());
172 if(feedingAppModule->hasReachedTestableMode()) {
173 // the feeding module has started its mainLoop(), but not sent us an initial value
174 // FIXME: There is a race condition, if the situation has right now resolved and the feeding module just
175 // not yet sent the initial value. Ideally we should wait another iteration before printing warnings!
176 if(_modulesWeHaveWarnedAbout.find(feedingAppModule) == _modulesWeHaveWarnedAbout.end()) {
177 _modulesWeHaveWarnedAbout.insert(feedingAppModule);
178 logger(Logger::Severity::warning, "CircularDependencyDetector")
179 << "Note: ApplicationModule " << appModule->getQualifiedNameWithType() << " is waiting for an "
180 << "initial value, because " << feedingAppModule->getQualifiedNameWithType()
181 << " has not yet sent one on " << node.getModel().getFullyQualifiedPath() << std::endl;
182 }
183 return;
184 }
185 if(_awaitedNodes.find(feedingAppModule) == _awaitedNodes.end()) {
186 // The other module is not right now waiting. It will either enter the mainLoop soon, or start
187 // waiting soon. Let's just wait another iteration.
188 return;
189 }
190 // The other module is right now waiting: continue iterative search
191 bool notYetSearched = searchedModules.insert(feedingAppModule).second;
192 if(notYetSearched) {
193 iterativeSearch(_awaitedNodes.at(feedingAppModule));
194 }
195 else {
196 if(_modulesWeHaveWarnedAbout.find(feedingAppModule) == _modulesWeHaveWarnedAbout.end()) {
197 _modulesWeHaveWarnedAbout.insert(feedingAppModule);
198 logger(Logger::Severity::warning, "CircularDependencyDetector")
199 << "Note: ApplicationModule " << appModule->getQualifiedNameWithType()
200 << " is directly or indirectly waiting for an initial value from "
201 << feedingAppModule->getQualifiedNameWithType()
202 << ", which in turn is waiting for an initial value on input "
203 << node.getModel().getFullyQualifiedPath() << std::endl;
204 }
205 }
206 }
207 else if constexpr(Model::isDeviceModule(proxy)) {
208 // fed by device
209 const auto& deviceName = proxy.getAliasOrCdd();
210 if(_devicesWeHaveWarnedAbout.find(deviceName) == _devicesWeHaveWarnedAbout.end()) {
211 _devicesWeHaveWarnedAbout.insert(deviceName);
212 auto myLog = logger(Logger::Severity::warning, "CircularDependencyDetector");
213 myLog << "Note: Still waiting for device " << deviceName << " to come up";
214 auto dm = Application::getInstance()._deviceManagerMap[deviceName];
215 std::unique_lock dmLock(dm->_errorMutex);
216 if(dm->_deviceHasError) {
217 myLog << " (" << std::string(dm->_deviceError._message) << ")";
218 }
219 myLog << "...";
220 }
221 }
222 else {
223 // fed by anything else?
224 logger(Logger::Severity::warning, "CircularDependencyDetector")
225 << "At least one ApplicationModule " << appModule->getQualifiedNameWithType() << " is waiting for an "
226 << "initial value from an unexpected source."
227 << "\n"
228 << "This is probably a BUG in the ChimeraTK framework.";
229 }
230 };
231
232 node.getModel().visit(visitor, Model::keepPvAccess, Model::adjacentInSearch);
233 };
234 iterativeSearch(_awaitedNodes.at(appModule));
235 }
236
237 // if all modules are in the mainLoop, stop this thread
238 if(allModulesEnteredMainLoop) {
239 break;
240 }
241 }
242
243 logger(Logger::Severity::info, "CircularDependencyDetector") << "All application modules are running.";
244
245 // free some memory
246 _waitMap.clear();
247 _awaitedVariables.clear();
248 _awaitedNodes.clear();
249 _modulesWeHaveWarnedAbout.clear();
250 _devicesWeHaveWarnedAbout.clear();
251 _otherThingsWeHaveWarnedAbout.clear();
252 }
253
254 /********************************************************************************************************************/
255
256 void CircularDependencyDetector::terminate() {
257 if(_thread.joinable()) {
258 _thread.interrupt();
259 _thread.join();
260 }
261 }
262
263 /********************************************************************************************************************/
264
265 CircularDependencyDetector::~CircularDependencyDetector() {
266 assert(!_thread.joinable());
267 }
268
269 /********************************************************************************************************************/
270
271} // namespace ChimeraTK::detail
static void registerThread(const std::string &name)
Register the thread in the application system and give it a name.
std::map< std::string, boost::shared_ptr< DeviceManager > > _deviceManagerMap
Map of DeviceManagers.
static Application & getInstance()
Obtain instance of the application.
constexpr ReturnFirstHitWithValue< void > returnFirstHit()
Stop the search after the first hit and return.
Definition Model.h:790
constexpr bool isApplicationModule(const PROPERTY_OR_PROXY &)
Definition Model.h:637
constexpr bool isDeviceModule(const PROPERTY_OR_PROXY &)
Definition Model.h:653
Logger::StreamProxy logger(Logger::Severity severity, std::string context)
Convenience function to obtain the logger stream.
Definition Logger.h:124
module_ module