otsdaq_utilities  v2_03_00
ConsoleSupervisor.cc
1 #include "otsdaq-utilities/Console/ConsoleSupervisor.h"
2 #include <xdaq/NamespaceURI.h>
3 #include "otsdaq-core/CgiDataUtilities/CgiDataUtilities.h"
4 #include "otsdaq-core/Macros/CoutMacros.h"
5 #include "otsdaq-core/MessageFacility/MessageFacility.h"
6 #include "otsdaq-core/NetworkUtilities/ReceiverSocket.h"
7 #include "otsdaq-core/XmlUtilities/HttpXmlDocument.h"
8 
9 #include <dirent.h> //for DIR
10 #include <sys/stat.h> //for mkdir
11 #include <fstream>
12 #include <iostream>
13 #include <string>
14 #include <thread> //for std::thread
15 
16 using namespace ots;
17 
18 // UDP Message Format:
19 // UDPMESSAGE|TIMESTAMP|SEQNUM|HOSTNAME|HOSTADDR|SEVERITY|CATEGORY|APPLICATION|PID|ITERATION|MODULE|(FILE|LINE)|MESSAGE
20 // FILE and LINE are only printed for s67+
21 
22 XDAQ_INSTANTIATOR_IMPL(ConsoleSupervisor)
23 
24 #define USER_CONSOLE_PREF_PATH \
25  std::string(getenv("SERVICE_DATA_PATH")) + "/ConsolePreferences/"
26 #define USERS_PREFERENCES_FILETYPE "pref"
27 
28 #define QUIET_CFG_FILE \
29  std::string(getenv("USER_DATA")) + \
30  "/MessageFacilityConfigurations/" \
31  "QuietForwarder.cfg"
32 
33 #define CONSOLE_SPECIAL_ERROR std::string("||0|||Error|Console|-1||ConsoleSupervisor|")
34 #define CONSOLE_SPECIAL_WARNING \
35  std::string("||0|||Warning|Console|-1||ConsoleSupervisor|")
36 
37 #undef __MF_SUBJECT__
38 #define __MF_SUBJECT__ "Console"
39 
40 //========================================================================================================================
41 ConsoleSupervisor::ConsoleSupervisor(xdaq::ApplicationStub* stub)
42  : CoreSupervisorBase(stub), writePointer_(0), messageCount_(0)
43 {
44  __SUP_COUT__ << "Constructor started." << __E__;
45 
46  INIT_MF("ConsoleSupervisor");
47 
48  // attempt to make directory structure (just in case)
49  mkdir(((std::string)USER_CONSOLE_PREF_PATH).c_str(), 0755);
50 
51  init();
52 
53  __SUP_COUT__ << "Constructor complete." << __E__;
54 }
55 
56 //========================================================================================================================
57 ConsoleSupervisor::~ConsoleSupervisor(void) { destroy(); }
58 //========================================================================================================================
59 void ConsoleSupervisor::init(void)
60 {
61  // start mf msg listener
62  std::thread(
63  [](ConsoleSupervisor* cs) {
64  ConsoleSupervisor::messageFacilityReceiverWorkLoop(cs);
65  },
66  this)
67  .detach();
68 } // end init()
69 
70 //========================================================================================================================
71 void ConsoleSupervisor::destroy(void)
72 {
73  // called by destructor
74 } // end destroy()
75 
76 //========================================================================================================================
77 // messageFacilityReceiverWorkLoop ~~
78 // Thread for printing Message Facility messages without decorations
79 // Note: Uses std::mutex to avoid conflict with reading thread.
80 void ConsoleSupervisor::messageFacilityReceiverWorkLoop(ConsoleSupervisor* cs)
81 {
82  __COUT__ << std::endl;
83 
84  std::string configFile = QUIET_CFG_FILE;
85  FILE* fp = fopen(configFile.c_str(), "r");
86  if(!fp)
87  {
88  __SS__ << "File with port info could not be loaded: " << QUIET_CFG_FILE
89  << std::endl;
90  __COUT__ << "\n" << ss.str();
91  __SS_THROW__;
92  }
93  char tmp[100];
94  fgets(tmp, 100, fp); // receive port (ignore)
95  fgets(tmp, 100, fp); // destination port *** used here ***
96  int myport;
97  sscanf(tmp, "%*s %d", &myport);
98 
99  fgets(tmp, 100, fp); // destination ip *** used here ***
100  char myip[100];
101  sscanf(tmp, "%*s %s", myip);
102  fclose(fp);
103 
104  ReceiverSocket rsock(myip, myport); // Take Port from Configuration
105  try
106  {
107  rsock.initialize();
108  }
109  catch(...)
110  {
111  // lockout the messages array for the remainder of the scope
112  // this guarantees the reading thread can safely access the messages
113  std::lock_guard<std::mutex> lock(cs->messageMutex_);
114 
115  // NOTE: if we do not want this to be fatal, do not throw here, just print out
116 
117  if(1) // generate special message and throw for failed socket
118  {
119  __SS__ << "FATAL Console error. Could not initialize socket on port "
120  << myport
121  << ". Perhaps the port is already in use? Check for multiple stale "
122  "instances of otsdaq processes, or notify admins."
123  << " Multiple instances of otsdaq on the same node should be "
124  "possible, but port numbers must be unique."
125  << std::endl;
126  __SS_THROW__;
127  }
128 
129  // generate special message to indicate failed socket
130  __SS__ << "FATAL Console error. Could not initialize socket on port " << myport
131  << ". Perhaps it is already in use? Exiting Console receive loop."
132  << std::endl;
133  __COUT__ << ss.str();
134 
135  cs->messages_[cs->writePointer_].set(CONSOLE_SPECIAL_ERROR + ss.str(),
136  cs->messageCount_++);
137 
138  if(++cs->writePointer_ == cs->messages_.size()) // handle wrap-around
139  cs->writePointer_ = 0;
140 
141  return;
142  }
143 
144  std::string buffer;
145  int i = 0;
146  int heartbeatCount = 0;
147  int selfGeneratedMessageCount = 0;
148 
149  std::map<unsigned int, unsigned int>
150  sourceLastSequenceID; // map from sourceID to
151  // lastSequenceID to
152  // identify missed messages
153  long long newSourceId;
154  unsigned int newSequenceId;
155 
156  while(1)
157  {
158  // if receive succeeds display message
159 
160  if(rsock.receive(
161  buffer, 1 /*timeoutSeconds*/, 0 /*timeoutUSeconds*/, false /*verbose*/) !=
162  -1)
163  {
164  if(i != 200)
165  {
166  __COUT__ << "Console has first message." << std::endl;
167  i = 200; // mark so things are good for all time. (this indicates things
168  // are configured to be sent here)
169 
170  __MOUT__ << "DEBUG messages look like this." << std::endl;
171  __MOUT_INFO__ << "INFO messages look like this." << std::endl;
172  __MOUT_WARN__ << "WARNING messages look like this." << std::endl;
173  __MOUT_ERR__ << "ERROR messages look like this." << std::endl;
174 
175  // //to debug special packets
176  // __SS__ << "???";
177  // cs->messages_[cs->writePointer_].set(CONSOLE_SPECIAL_ERROR
178  //+ ss.str(),
179  // cs->messageCount_++);
180  //
181  // if(++cs->writePointer_ == cs->messages_.size()) //handle
182  // wrap-around cs->writePointer_ = 0;
183  }
184 
185  if(selfGeneratedMessageCount)
186  --selfGeneratedMessageCount; // decrement internal message count
187  else
188  heartbeatCount =
189  0; // reset heartbeat if external messages are coming through
190 
191  //__COUT__ << buffer << std::endl;
192 
193  // lockout the messages array for the remainder of the scope
194  // this guarantees the reading thread can safely access the messages
195  std::lock_guard<std::mutex> lock(cs->messageMutex_);
196 
197  cs->messages_[cs->writePointer_].set(buffer, cs->messageCount_++);
198 
199  // check if sequence ID is out of order
200  newSourceId = cs->messages_[cs->writePointer_].getSourceIDAsNumber();
201  newSequenceId = cs->messages_[cs->writePointer_].getSequenceIDAsNumber();
202 
203  //__COUT__ << "newSourceId: " << newSourceId << std::endl;
204  //__COUT__ << "newSequenceId: " << newSequenceId << std::endl;
205 
206  if(newSourceId != -1 &&
207  sourceLastSequenceID.find(newSourceId) !=
208  sourceLastSequenceID.end() && // ensure not first packet received
209  ((newSequenceId == 0 && sourceLastSequenceID[newSourceId] !=
210  (unsigned int)-1) || // wrap around case
211  newSequenceId !=
212  sourceLastSequenceID[newSourceId] + 1)) // normal sequence case
213  {
214  // missed some messages!
215  __SS__ << "Missed packets from "
216  << cs->messages_[cs->writePointer_].getSource()
217  << "! Sequence IDs " << sourceLastSequenceID[newSourceId] << " to "
218  << newSequenceId << "." << std::endl;
219  std::cout << ss.str();
220 
221  if(++cs->writePointer_ == cs->messages_.size()) // handle wrap-around
222  cs->writePointer_ = 0;
223 
224  // generate special message to indicate missed packets
225  cs->messages_[cs->writePointer_].set(CONSOLE_SPECIAL_WARNING + ss.str(),
226  cs->messageCount_++);
227  }
228 
229  // save the new last sequence ID
230  sourceLastSequenceID[newSourceId] = newSequenceId;
231 
232  if(++cs->writePointer_ == cs->messages_.size()) // handle wrap-around
233  cs->writePointer_ = 0;
234  }
235  else
236  {
237  if(i < 120) // if nothing received for 120 seconds, then something is wrong
238  // with Console configuration
239  ++i;
240 
241  sleep(1); // sleep one second, if timeout
242 
243  // every 60 heartbeatCount (2 seconds each = 1 sleep and 1 timeout) print a
244  // heartbeat message
245  if(i != 200 || // show first message, if not already a message
246  (heartbeatCount < 60 * 5 &&
247  heartbeatCount % 60 == 59)) // every ~2 min for first 5 messages
248  {
249  ++selfGeneratedMessageCount; // increment internal message count
250  __MOUT__ << "Console is alive and waiting... (if no messages, next "
251  "heartbeat is in approximately two minutes)"
252  << std::endl;
253  }
254  else if(heartbeatCount % (60 * 30) == 59) // approx every hour
255  {
256  ++selfGeneratedMessageCount; // increment internal message count
257  __MOUT__ << "Console is alive and waiting a long time... (if no "
258  "messages, next heartbeat is in approximately one hour)"
259  << std::endl;
260  }
261 
262  ++heartbeatCount;
263  }
264 
265  // if nothing received for 2 minutes seconds, then something is wrong with Console
266  // configuration after 5 seconds there is a self-send. Which will at least
267  // confirm configuration. OR if 5 generated messages and never cleared.. then
268  // the forwarding is not working.
269  if(i == 120 || selfGeneratedMessageCount == 5)
270  {
271  __COUT__ << "No messages received at Console Supervisor. Exiting Console "
272  "messageFacilityReceiverWorkLoop"
273  << std::endl;
274  break; // assume something wrong, and break loop
275  }
276  }
277 
278 } // end messageFacilityReceiverWorkLoop()
279 
280 //========================================================================================================================
281 void ConsoleSupervisor::defaultPage(xgi::Input* in, xgi::Output* out)
282 {
283  __SUP_COUT__ << "ApplicationDescriptor LID="
284  << getApplicationDescriptor()->getLocalId() << std::endl;
285  *out << "<!DOCTYPE HTML><html lang='en'><frameset col='100%' row='100%'><frame "
286  "src='/WebPath/html/Console.html?urn="
287  << getApplicationDescriptor()->getLocalId() << "'></frameset></html>";
288 } // end defaultPage()
289 
290 //========================================================================================================================
291 // forceSupervisorPropertyValues
292 // override to force supervisor property values (and ignore user settings)
293 void ConsoleSupervisor::forceSupervisorPropertyValues()
294 {
295  CorePropertySupervisorBase::setSupervisorProperty(
296  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
297  "GetConsoleMsgs");
298  // CorePropertySupervisorBase::setSupervisorProperty(CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.NeedUsernameRequestTypes,
299  // "SaveUserPreferences | LoadUserPreferences");
300 } // end forceSupervisorPropertyValues()
301 
302 //========================================================================================================================
303 // Request
304 // Handles Web Interface requests to Console supervisor.
305 // Does not refresh cookie for automatic update checks.
306 void ConsoleSupervisor::request(const std::string& requestType,
307  cgicc::Cgicc& cgiIn,
308  HttpXmlDocument& xmlOut,
309  const WebUsers::RequestUserInfo& userInfo)
310 {
311  //__SUP_COUT__ << "requestType " << requestType << std::endl;
312 
313  // Commands:
314  // GetConsoleMsgs
315  // SaveUserPreferences
316  // LoadUserPreferences
317 
318  // Note: to report to logbook admin status use
319  // xmlOut.addTextElementToData(XML_ADMIN_STATUS,refreshTempStr_);
320 
321  if(requestType == "GetConsoleMsgs")
322  {
323  // lindex of -1 means first time and user just gets update lcount and lindex
324  std::string lastUpdateCountStr = CgiDataUtilities::postData(cgiIn, "lcount");
325  std::string lastUpdateIndexStr = CgiDataUtilities::postData(cgiIn, "lindex");
326 
327  if(lastUpdateCountStr == "" || lastUpdateIndexStr == "")
328  {
329  __SUP_COUT_ERR__ << "Invalid Parameters! lastUpdateCount="
330  << lastUpdateCountStr
331  << ", lastUpdateIndex=" << lastUpdateIndexStr << std::endl;
332  xmlOut.addTextElementToData("Error",
333  "Error - Invalid parameters for GetConsoleMsgs.");
334  return;
335  }
336 
337  clock_t lastUpdateCount;
338  sscanf(lastUpdateCountStr.c_str(), "%ld", &lastUpdateCount);
339 
340  unsigned int lastUpdateIndex;
341  sscanf(lastUpdateIndexStr.c_str(), "%u", &lastUpdateIndex);
342  // __SUP_COUT__ << "lastUpdateCount=" << lastUpdateCount <<
343  // ", lastUpdateIndex=" << lastUpdateIndex << std::endl;
344 
345  insertMessageRefresh(&xmlOut, lastUpdateCount, lastUpdateIndex);
346  }
347  else if(requestType == "SaveUserPreferences")
348  {
349  int colorIndex = CgiDataUtilities::postDataAsInt(cgiIn, "colorIndex");
350  int showSideBar = CgiDataUtilities::postDataAsInt(cgiIn, "showSideBar");
351  int noWrap = CgiDataUtilities::postDataAsInt(cgiIn, "noWrap");
352  int messageOnly = CgiDataUtilities::postDataAsInt(cgiIn, "messageOnly");
353  int hideLineNumers = CgiDataUtilities::postDataAsInt(cgiIn, "hideLineNumers");
354 
355  __SUP_COUT__ << "requestType " << requestType << std::endl;
356  __SUP_COUT__ << "colorIndex: " << colorIndex << std::endl;
357  __SUP_COUT__ << "showSideBar: " << showSideBar << std::endl;
358  __SUP_COUT__ << "noWrap: " << noWrap << std::endl;
359  __SUP_COUT__ << "messageOnly: " << messageOnly << std::endl;
360  __SUP_COUT__ << "hideLineNumers: " << hideLineNumers << std::endl;
361 
362  if(userInfo.username_ == "") // should never happen?
363  {
364  __SUP_COUT_ERR__ << "Invalid user found! user=" << userInfo.username_
365  << std::endl;
366  xmlOut.addTextElementToData("Error",
367  "Error - InvauserInfo.username_user found.");
368  return;
369  }
370 
371  std::string fn = (std::string)USER_CONSOLE_PREF_PATH + userInfo.username_ + "." +
372  (std::string)USERS_PREFERENCES_FILETYPE;
373 
374  __SUP_COUT__ << "Save preferences: " << fn << std::endl;
375  FILE* fp = fopen(fn.c_str(), "w");
376  if(!fp)
377  {
378  __SS__;
379  __THROW__(ss.str() + "Could not open file: " + fn);
380  }
381  fprintf(fp, "colorIndex %d\n", colorIndex);
382  fprintf(fp, "showSideBar %d\n", showSideBar);
383  fprintf(fp, "noWrap %d\n", noWrap);
384  fprintf(fp, "messageOnly %d\n", messageOnly);
385  fprintf(fp, "hideLineNumers %d\n", hideLineNumers);
386  fclose(fp);
387  }
388  else if(requestType == "LoadUserPreferences")
389  {
390  __SUP_COUT__ << "requestType " << requestType << std::endl;
391 
392  unsigned int colorIndex, showSideBar, noWrap, messageOnly, hideLineNumers;
393 
394  if(userInfo.username_ == "") // should never happen?
395  {
396  __SUP_COUT_ERR__ << "Invalid user found! user=" << userInfo.username_
397  << std::endl;
398  xmlOut.addTextElementToData("Error", "Error - Invalid user found.");
399  return;
400  }
401 
402  std::string fn = (std::string)USER_CONSOLE_PREF_PATH + userInfo.username_ + "." +
403  (std::string)USERS_PREFERENCES_FILETYPE;
404 
405  __SUP_COUT__ << "Load preferences: " << fn << std::endl;
406 
407  FILE* fp = fopen(fn.c_str(), "r");
408  if(!fp)
409  {
410  // return defaults
411  __SUP_COUT__ << "Returning defaults." << std::endl;
412  xmlOut.addTextElementToData("colorIndex", "0");
413  xmlOut.addTextElementToData("showSideBar", "0");
414  xmlOut.addTextElementToData("noWrap", "1");
415  xmlOut.addTextElementToData("messageOnly", "0");
416  xmlOut.addTextElementToData("hideLineNumers", "1");
417  return;
418  }
419  fscanf(fp, "%*s %u", &colorIndex);
420  fscanf(fp, "%*s %u", &showSideBar);
421  fscanf(fp, "%*s %u", &noWrap);
422  fscanf(fp, "%*s %u", &messageOnly);
423  fscanf(fp, "%*s %u", &hideLineNumers);
424  fclose(fp);
425  __SUP_COUT__ << "colorIndex: " << colorIndex << std::endl;
426  __SUP_COUT__ << "showSideBar: " << showSideBar << std::endl;
427  __SUP_COUT__ << "noWrap: " << noWrap << std::endl;
428  __SUP_COUT__ << "messageOnly: " << messageOnly << std::endl;
429  __SUP_COUT__ << "hideLineNumers: " << hideLineNumers << std::endl;
430 
431  char tmpStr[20];
432  sprintf(tmpStr, "%u", colorIndex);
433  xmlOut.addTextElementToData("colorIndex", tmpStr);
434  sprintf(tmpStr, "%u", showSideBar);
435  xmlOut.addTextElementToData("showSideBar", tmpStr);
436  sprintf(tmpStr, "%u", noWrap);
437  xmlOut.addTextElementToData("noWrap", tmpStr);
438  sprintf(tmpStr, "%u", messageOnly);
439  xmlOut.addTextElementToData("messageOnly", tmpStr);
440  sprintf(tmpStr, "%u", hideLineNumers);
441  xmlOut.addTextElementToData("hideLineNumers", tmpStr);
442  }
443  else
444  {
445  __SUP_SS__ << "requestType Request, " << requestType << ", not recognized."
446  << __E__;
447  __SUP_SS_THROW__;
448  }
449 } // end request()
450 
451 //========================================================================================================================
452 // ConsoleSupervisor::insertMessageRefresh()
453 // if lastUpdateClock is current, return nothing
454 // else return new messages
455 // (note: lastUpdateIndex==(unsigned int)-1 first time and returns as much as possible//
456 // nothing but lastUpdateClock)
457 //
458 // format of xml:
459 //
460 // <last_update_count/>
461 // <last_update_index/>
462 // <messages>
463 // <message_FIELDNAME*/>
464 //"Level"
465 //"Label"
466 //"Source"
467 //"Msg"
468 //"Time"
469 //"Count"
470 // </messages>
471 //
472 // NOTE: Uses std::mutex to avoid conflict with writing thread. (this is the reading
473 // thread)
474 void ConsoleSupervisor::insertMessageRefresh(HttpXmlDocument* xmlOut,
475  const time_t lastUpdateCount,
476  const unsigned int lastUpdateIndex)
477 {
478  //__SUP_COUT__ << std::endl;
479 
480  // validate lastUpdateIndex
481  if(lastUpdateIndex > messages_.size() && lastUpdateIndex != (unsigned int)-1)
482  {
483  __SS__ << "Invalid lastUpdateIndex: " << lastUpdateIndex
484  << " messagesArray size = " << messages_.size() << std::endl;
485  __SS_THROW__;
486  }
487 
488  // lockout the messages array for the remainder of the scope
489  // this guarantees the reading thread can safely access the messages
490  std::lock_guard<std::mutex> lock(messageMutex_);
491 
492  // newest available read pointer is defined as always one behind writePointer_
493  refreshReadPointer_ = (writePointer_ + messages_.size() - 1) % messages_.size();
494 
495  sprintf(refreshTempStr_, "%lu", messages_[refreshReadPointer_].getCount());
496  xmlOut->addTextElementToData("last_update_count", refreshTempStr_);
497  sprintf(refreshTempStr_, "%u", refreshReadPointer_);
498  xmlOut->addTextElementToData("last_update_index", refreshTempStr_);
499 
500  if(!messages_[refreshReadPointer_].getTime()) // if no data, then no data
501  return;
502 
503  // else, send all messages since last_update_count, from
504  // last_update_index(refreshReadPointer_) on
505  if(lastUpdateIndex != (unsigned int)-1 && // if not first time, and index-count are
506  // valid then start at next message
507  messages_[lastUpdateIndex].getCount() == lastUpdateCount)
508  refreshReadPointer_ = (lastUpdateIndex + 1) % messages_.size();
509  else if(messages_[writePointer_]
510  .getTime()) // check that writePointer_ message has
511  // been initialized, therefore has wrapped
512  // around at least once already
513  {
514  // This means we have had many messages and that some were missed since last
515  // update (give as many messages as we can!)
516  xmlOut->addTextElementToData("message_overflow", "1");
517  __SUP_COUT__ << "Overflow was detected!" << std::endl;
518  refreshReadPointer_ = (writePointer_ + 1) % messages_.size();
519  }
520  else // user does not have valid index, and writePointer_ has not wrapped around, so
521  // give all new messages
522  refreshReadPointer_ = 0;
523 
524  // __SUP_COUT__ << "refreshReadPointer_: " << refreshReadPointer_ << std::endl;
525  // __SUP_COUT__ << "lastUpdateCount: " << lastUpdateCount << std::endl;
526  // __SUP_COUT__ << "writePointer_: " << writePointer_ << std::endl;
527 
528  // return anything from refreshReadPointer_ to writePointer_
529  // all should have a clock greater than lastUpdateClock
530 
531  refreshParent_ = xmlOut->addTextElementToData("messages", "");
532 
533  bool requestOutOfSync = false;
534  std::string requestOutOfSyncMsg;
535  // output oldest to new (from refreshReadPointer_ to writePointer_-1, inclusive)
536  for(/*refreshReadPointer_=<first index to read>*/;
537  refreshReadPointer_ != writePointer_;
538  refreshReadPointer_ = (refreshReadPointer_ + 1) % messages_.size())
539  {
540  if(messages_[refreshReadPointer_].getCount() < lastUpdateCount)
541  {
542  if(!requestOutOfSync) // record out of sync message once only
543  {
544  requestOutOfSync = true;
545  __SS__ << "Request is out of sync! Message count should be more recent "
546  "than update clock! "
547  << messages_[refreshReadPointer_].getCount() << " < "
548  << lastUpdateCount << std::endl;
549  requestOutOfSyncMsg = ss.str();
550  }
551  // assume these messages are new (due to a system restart)
552  // continue;
553  }
554 
555  // for all fields, give value
556  for(refreshIndex_ = 0;
557  refreshIndex_ < messages_[refreshReadPointer_].fields.size();
558  ++refreshIndex_)
559  xmlOut->addTextElementToParent(
560  "message_" +
561  messages_[refreshReadPointer_].fields[refreshIndex_].fieldName,
562  messages_[refreshReadPointer_].getField(refreshIndex_),
563  refreshParent_);
564 
565  // give timestamp also
566  sprintf(refreshTempStr_, "%lu", messages_[refreshReadPointer_].getTime());
567  xmlOut->addTextElementToParent("message_Time", refreshTempStr_, refreshParent_);
568  // give clock also
569  sprintf(refreshTempStr_, "%lu", messages_[refreshReadPointer_].getCount());
570  xmlOut->addTextElementToParent("message_Count", refreshTempStr_, refreshParent_);
571  }
572 
573  if(requestOutOfSync) // if request was out of sync, show message
574  __SUP_COUT__ << requestOutOfSyncMsg;
575 }