Changeset 262 for Evidence


Ignore:
Timestamp:
07/26/10 17:53:31 (14 years ago)
Author:
ogrimm
Message:
Removed signaling to invoke ConfigChanged(), now it is run as separate thread. New command '/ResetMessage'
Location:
Evidence
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • Evidence/Alarm.cc

    r255 r262  
    2828
    2929//
    30 // Data handling class
     30// Class declaration
    3131//
    3232class AlarmHandler: public DimClient, public EvidenceServer {
     
    148148        List[i].Level = 0;
    149149        List[i].WarnedLevel = 0;
    150         if (Server != "DIS_DNS") sendCommandNB((Server+"/EXIT").c_str(), (int) 0);
     150        sendCommandNB((Server+"/ResetMessage").c_str(), (int) 0);
    151151  }
    152152 
  • Evidence/Bridge.cc

    r253 r262  
    5050
    5151  public:
    52     Bridge(char *, int);
     52    Bridge();
    5353    ~Bridge();
    5454};
    5555
    56 // Constructor
    57 Bridge::Bridge(char *Name, int Port): EvidenceServer(SERVER_NAME) {
    58 
    59   // Set primary DIM network to subscribe from
    60   DimClient::setDnsNode(Name, Port);
    61 
    62   // Initialise and request notification of configuration changes
     56// Constructor (ConfigChanged() is automatically called at start-up)
     57Bridge::Bridge(): EvidenceServer(SERVER_NAME) {
     58
     59  // Initialise
    6360  ServerList = NULL;
    6461  GetConfig("cmdallow", " ");
    65   ActivateSignal(SIGUSR2);
    66  
    67   ConfigChanged();
    6862}
    6963
     
    9286
    9387  // Check if configuratiomn changed
    94   if (ExcludeString == GetConfig("exclude")) return;
    95   ExcludeString = GetConfig("exclude");
    96  
    97   // Remove all previous subscriptions
    98   Undo();
    99 
     88  string NewExcludeString = GetConfig("exclude");
     89
     90  Lock();
     91  if (ExcludeString != NewExcludeString) ExcludeString.swap(NewExcludeString);
     92  Unlock();
     93  if (ExcludeString == NewExcludeString) return;
     94   
    10095  // Compile regular expressions
    10196  regex_t R;
     97  vector<regex_t> RegEx;
     98
    10299  vector<string> Exclude = Tokenize(ExcludeString, " \t");
    103100  for (int i=0; i<Exclude.size(); i++) {
     
    111108  }
    112109
    113   // Subscribe to top-level server list
     110  // Remove previous data, set new regular expressions and subscribe to top-level server list
     111  Lock();
     112  Undo();
     113  Bridge::RegEx = RegEx;
    114114  ServerList = new DimInfo((char *) "DIS_DNS/SERVER_LIST", NO_LINK, this);
     115  Unlock();
    115116}
    116117
     
    267268        exit(EXIT_FAILURE);
    268269  }
    269          
     270
     271  // Set primary DIM network to subscribe from
     272  DimClient::setDnsNode(argv[1], argc>2 ? atoi(argv[2]) : DEFAULT_PORT);
     273
    270274  // Static ensures calling of destructor by exit()
    271   static Bridge Class(argv[1], argc>2 ? atoi(argv[2]) : DEFAULT_PORT);
     275  static Bridge Class;
    272276 
    273277  // Sleep until signal caught
  • Evidence/Config.cc

    r253 r262  
    1111
    1212  A mutex is used for preventing concurrent access to the configuration data from
    13   the methods rpcHandler() and ConfigChanged().
     13  the methods rpcHandler() and ReadConfig().
    1414         
    1515  Oliver Grimm, July 2010
     
    4848    ~EvidenceConfig();
    4949       
    50         void ConfigChanged();
     50        void ReadConfig();
    5151};
    5252
     
    5656        DimRpc("ConfigRequest", "C", "C"), EvidenceServer(SERVER_NAME) {
    5757
    58   ConfigModified = NULL;        // Will be allocated in ConfigChanged()
     58  ConfigModified = NULL;        // Will be allocated in ReadConfig()
    5959  ConfigContent = NULL;
    6060  FileContent = NULL;
     
    7171
    7272  // Create DIM services
    73   ConfigChanged();
     73  ReadConfig();
    7474}
    7575
     
    106106
    107107// Signalisation of configuration change
    108 void EvidenceConfig::ConfigChanged() {
     108void EvidenceConfig::ReadConfig() {
    109109
    110110  static int ModifyTime;
     
    249249  while (!Config.ExitRequest) {
    250250    if (Notify != -1) {
    251           if (read(Notify, &Event, sizeof(Event)) == sizeof(Event)) Config.ConfigChanged();
     251          if (read(Notify, &Event, sizeof(Event)) == sizeof(Event)) Config.ReadConfig();
    252252        }
    253253    else pause();         
  • Evidence/Doc/Evidence.tex

    r255 r262  
    130130\item Provides a method for configuration requests. If the configuration data is not available, the application terminates with a  message of FATAL severity unless default data is given.
    131131\item Provides a method for safely translating DIM service data into text.
    132 \item Implements the virtual DIM methods \lstinline{exitHandler()}. It can be called through a standard DIM command \lstinline{SvrName/EXIT}, taking a single integer as argument. Upon first invocation, the handler just sets the flag \lstinline{ExitRequest} which should be handled by the application. Upon second invocation, it will call \lstinline{exit()}. A special functionality is given to the argument value 0: it instructs the server to reset its message severity to INFO, without exiting. This is used by the \lstinline{Alarm} server if it receives a command to reset an alarm level, but is also available to the user. The user application can override this handler.
     132\item Implements the virtual DIM methods \lstinline{exitHandler()}. It can be called through a standard DIM command \lstinline{SvrName/EXIT}, taking a single integer as argument. Upon first invocation, the handler just sets the flag \lstinline{ExitRequest} which should be handled by the application. Upon second invocation, it will call \lstinline{exit()}. The user application can override this handler.
     133\item Provides a DIM command \lstinline{SvrName/ResetMessage}. It will set the message service to INFO severity with the information which client issued the command. This can be used to remove a WARN, ERROR or FATAL serverity once the problem has been fixed. The \lstinline{Alarm} server uses this command if it is instructed to reset an alarm level. The command takes no arguments.
    133134\item Implements the virtual DIM methods \lstinline{errorHandler()}. The error handler will issue a message with ERROR severity that contains the DIM error code. The user application can override this handler.
    134135\item Installs signal handler for SIGQUIT (ctrl-backspace), SIGTERM, SIGINT (ctrl-c), and SIGHUP (terminal closed). The signal handler sets first \lstinline{ExitRequest}, and on second invocation calls \lstinline{exit()}. After instantiating the class, the programmer may override the handlers.
     
    157158    void SendToLog(const char *, ...);
    158159    string GetConfig(string, string = string());
    159     void ActivateSignal(int);
     160        virtual void ConfigChanged() {};
    160161    void Lock();
    161162    void Unlock();
     
    163164    static bool ServiceOK(DimInfo *);
    164165    static bool ServiceOK(DimRpcInfo *);
     166        static bool ServiceOK(DimCurrentInfo *);
    165167    static vector<string> Tokenize(const string &, const string & = " ");
    166168
     
    179181\underline{\lstinline{GetConfig()}} issues, on first invocation, a DIM remote procedure call to the configuration server to retrieve the required data and returns it as a string. The second argument gives the data to be returned in case the server is unavailable or cannot provide the requested data. If in this case the second string is empty, the program terminates with a FATAL message. Using the service \lstinline{Config/ModifyTime}, the server keeps track of changes to the configuration file in the background. Upon subsequent requests for the same configuration data, it only issues a remote procedure call again if the file changed in the meantime. If not, the same data already retrieved is returned. This way, this function can be repeatedly called, even at high rate, without generating unnecessary load to the configuration server (as the configuration file does not change frequently).
    180182
    181 \underline{\lstinline{ActivateSignal()}} is used to define a signal that should be emitted to the main thread in case the configuration file changes. See Sect.\,\ref{ConfigHandling} for details. No signal will be emitted if not set by this routine.
    182 
    183 The methods \underline{\lstinline{Lock()}} and \underline{\lstinline{Unlock()}} work on an internal mutex.\footnote{Its type is \lstinline{PTHREAD_MUTEX_ERRORCHECK}. In case an already locked mutex is re-locked, the corresponding system call will therefore return a error and thus avoid dead-locking. Error messages from \lstinline{Lock()} and \lstinline{Unlock()} are written to the console and to the log file. They are not published using \lstinline{Message()} since this method itself uses locking and calling it would result in an infinite recursion.} They are used by \lstinline{GetConfig()} but are also available for the user application to serialize access from multiple threads. If a signal is set by \lstinline{ActivateSignal()}, it is masked before locking and unmasked after unlocking. Calling functions in the locked state should be avoided as it might result in re-locking.
     183The virtual method \underline{\lstinline{ConfigChanged()}} is executed in a separate thread when the configuration file changes. It can be reimplemented by the application. Calls to \lstinline{GetConfig()} from this method will be blocking and thus result in updated configuration data.
     184
     185The methods \underline{\lstinline{Lock()}} and \underline{\lstinline{Unlock()}} work on an internal mutex.\footnote{Its type is \lstinline{PTHREAD_MUTEX_ERRORCHECK}. In case an already locked mutex is re-locked, the corresponding system call will therefore return a error and thus avoid dead-locking. Error messages from \lstinline{Lock()} and \lstinline{Unlock()} are written to the console and to the log file. They are not published using \lstinline{Message()} since this method itself uses locking and calling it would result in an infinite recursion.} They are used by \lstinline{GetConfig()} but are also available for the user application to serialize access from multiple threads. Calling functions in the locked state should be avoided as it might result in re-locking.
    184186
    185187The static method \underline{\lstinline{ToString()}} translates the contents of a DIM service safely into a string that is returned. As no consistency between a service format and the contained data is guaranteed by DIM, precautions are necessary to avoid buffer overruns. The method currently handles the standardized message format \lstinline{"I:1;C"}, arrays of numbers and strings. All other formats are translated into a hex representation. The arguments are the DIM service format, a pointer to the service data and the data size in bytes. It is thread safe as it uses only the arguments and dynamically allocated storage.
    186188
    187 The two variants of the static method \underline{\lstinline{ServiceOK()}} take a pointer to a received service update or result of a remote procedure call (as available in the respective handlers) and safely checks if its contents is identical to the constant \lstinline{NO_LINK}. If so, they return false. If using the same constant in the service declaration, this provides a safe way of being informed if a particular service becomes unavailable. Then, the handler is called once for that service with the data content \lstinline{NO_LINK}.
     189The static methods \underline{\lstinline{ServiceOK()}} take a pointer to a received service update or result of a remote procedure call (as available in the respective handlers) and safely checks if its contents is identical to the constant \lstinline{NO_LINK}. If so, they return false. If using the same constant in the service declaration, this provides a safe way of being informed if a particular service becomes unavailable. Then, the handler is called once for that service with the data content \lstinline{NO_LINK}.
    188190
    189191\underline{\lstinline{Tokenize()}} takes the string from the first argument, tokenizes it using the characters contained the second argument as delimeters, and returns a vector of strings containing all tokens.
     
    358360As a first step in achieving this, the application should not store the obtained configuration data internally, but always re-request it using the method \lstinline{GetConfig()} described in Sect.\,\ref{EvidenceServer-Methods}. This method will only issue a remote procedure call to the \lstinline{Config} server if the configuration file has been modified since the last invocation. So calling this method even at high rate will not load the configuration server at all if the configuraton file is unchanged, but will yield up-to-date information if it did change.
    359361
    360 The remote procedure call is blocking when called from the main thread, and non-blocking, using an \lstinline{rpcInfoHandler()}, when called from any other thread (especially also from the DIM handler thread). Blocking execution means that the remote procedure call will wait until the data has arrived from the server before returning to the application, whereas non-blocking execution will return immediately and invoke a handler later when the data arrived. This procedure is necessary since a blocking remote procedure call from \lstinline{infoHandler()} will result in a dead-lock.
     362The remote procedure call is blocking when called from the main thread or from the method \lstinline{ConfigChanged()} (which runs in a separate thread). It is non-blocking, using an \lstinline{rpcInfoHandler()}, when called from any other thread, especially also from the DIM handler thread. Blocking execution means that the remote procedure call will wait until the data has arrived from the server before returning to the application, whereas non-blocking execution will return immediately and invoke a handler later when the data arrived. This procedure is necessary since a blocking remote procedure call from \lstinline{infoHandler()} will result in a dead-lock.
    361363
    362364In the non-blocking case, the call to \lstinline{GetConfig()} returns still the previous, non-updated data even if the configuration file changed. The result of the non-blocking remote procedure call can only be processed by DIM once the current and all queued handler invocations have finished. When this is done, updated data will be returned by subsequent calls to \lstinline{GetConfig()}.
     
    364366\subsection{\lstinline{ConfigChanged()}}
    365367
    366 An alternative, albeit for the programmer more demanding, procedure for semi-automatic updates on configuration information is to implement the method \lstinline{ConfigChanged()} in the user class. This method can be invoked through a signaling mechanism by the \lstinline{EvidenceServer} class.
    367 
    368 To this end, first \lstinline{ActivateSignal()} has to be called with a signal number that should be used for announcing configuration changes (for example, \lstinline{SIGUSR1}). That signal is send to the main thread when the service \lstinline{Config/ModifyTime} changes. The signal is caught by the internal signal handler of the \lstinline{EvidenceServer} class which in turn calls the method \lstinline{ConfigChanged()}. It is declared as \lstinline{virtual} in the class and defined as an empty function. The user application can override this by declaring and defining the method itself. This method is then executed in the main thread and thus \lstinline{GetConfig()} will use blocking connections to get always up-to-date data.
    369 
    370 However, it must be kept in mind that this routine is running as part of the signal handler. The normal main thread execution was interrupted at an arbitrary point by the signal, thus suitable protection must be employed by the programmer when accessing common data structures. To ease that, the \lstinline{EvidenceServer} class contains the pair of methods \lstinline{Lock()} and \lstinline{Unlock()} that work on an class internal mutex. When the mutex is acquired, also the signal declared by \lstinline{ActivateSignal()} is disabled, thus preventing interruptions in case a configuration update occurs in the locked state. The mutex type is \lstinline{PTHREAD_MUTEX_ERRORCHECK} and therefore includes error checking: no dead-lock will occur if double locking, but the program will terminate with a \lstinline{FATAL} message.   
     368An alternative, albeit for the programmer more demanding, procedure for semi-automatic updates on configuration information is to reimplement the virtual method \lstinline{ConfigChanged()} in the user class. This method is invoked as a separate thread by the \lstinline{EvidenceServer} class whenever the service \lstinline{Config/ModifyTime} changes (and also at program start-up). As it is not running within the DIM handler thread, \lstinline{GetConfig()} will use blocking connections to get immediately up-to-date data when called from \lstinline{ConfigChanged()}.
     369
     370Running in a separate thread requires suitable protection by the programmer when accessing common data structures. To ease that, the \lstinline{EvidenceServer} class contains the pair of methods \lstinline{Lock()} and \lstinline{Unlock()} that work on an class internal mutex. The mutex type is \lstinline{PTHREAD_MUTEX_ERRORCHECK} and therefore includes error checking: no dead-lock will occur if double locking, but the program will terminate with a \lstinline{FATAL} message.   
    371371
    372372
  • Evidence/Evidence.cc

    r255 r262  
    3131//
    3232
    33 // Constructor
    34 EvidenceServer::Config::Config(string Name): DimRpcInfo("ConfigRequest", NO_LINK), Name(Name) {
    35 
    36   // Initialise
     33// Constructor: Subscribe to be informed on configuration file change
     34EvidenceServer::Config::Config():       DimRpcInfo("ConfigRequest", NO_LINK),
     35                                                                        DimCommand((This->Name+"/ResetMessage").c_str(), (char *) "") {
     36
    3737  CurrentItem = string();
    3838  ConfigTimeStamp = 0;
    39   ThreadID = pthread_self();
    40 
    41   // Subscribe to be informed on configuration file change
    4239  Service = new DimInfo("Config/ModifyTime", NO_LINK, this);
    4340}
     
    4946}
    5047
     48// Reset message and severity
     49void EvidenceServer::Config::commandHandler() {
     50
     51  This->Message(INFO, "Message reset by %s (ID %d)", getClientName(), getClientId());
     52}
     53
    5154// Track last modification time of configuration file
    5255void EvidenceServer::Config::infoHandler() {
    5356
     57  pthread_t Thread;
     58  int Ret;
     59
     60  if (!ServiceOK(DimInfo::getInfo())) return;
     61return;
    5462  This->Lock();
    5563  ConfigTimeStamp = getInfo()->getInt();
    5664  This->Unlock();
    5765
    58   if (pthread_kill(ThreadID, This->ConfigSignal) != 0) {
    59         This->Message(WARN, "Could not send signal to main thread");
    60   }
    61 }
    62 
    63 // Receive answer to remote procedure call
     66  // Launch ConfigChanged() as detached thread
     67  if ((Ret = pthread_create(&Thread, NULL, (void * (*)(void *)) EvidenceServer::CallConfigChanged, (void *) NULL)) != 0) {
     68    This->Message(ERROR, "pthread_create() failed in EvidenceServer::Config::infoHandler() (%s)\n", strerror(Ret));
     69  }
     70  else if ((Ret = pthread_detach(Thread)) != 0) {
     71        This->Message(ERROR, "pthread_detach() failed in EvidenceServer::Config::infoHandler() (%s)\n", strerror(Ret));
     72  } 
     73}
     74
     75// Answer to remote procedure call: Update map
    6476void EvidenceServer::Config::rpcInfoHandler(){
    6577
    6678  This->Lock();
    67   // Update map
    68   List[CurrentItem].Value = string(getString(), getSize()-1);
    69   List[CurrentItem].Time = ConfigTimeStamp;
     79  This->List[CurrentItem].Value = string(DimRpcInfo::getString(), DimRpcInfo::getSize()-1);
     80  This->List[CurrentItem].Time = ConfigTimeStamp;
     81
    7082  // Clear to allow new rpc call
    7183  CurrentItem.clear();
     
    7385}
    7486
    75 // Return configuration data if still up to date or empty string otherwise
    76 string EvidenceServer::Config::GetConfig(string Item, string Default) {
     87// Request configuration data possible only when answer to previous request received
     88string EvidenceServer::Config::RequestNB(string Item) {
     89
     90  This->Lock();
     91  if (CurrentItem.empty()) {
     92        CurrentItem = Item;
     93        setData(((char *) Item.c_str()));
     94  }
     95  This->Unlock();
     96}
     97
     98
     99//////////////////////////
     100// EvidenceServer Class //
     101//////////////////////////
     102
     103// Initialise
     104EvidenceServer *EvidenceServer::This = NULL;
     105pthread_mutex_t EvidenceServer::Mutex;
     106set<pthread_t> EvidenceServer::Threads;
     107
     108
     109// Constructor
     110EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) {
     111
     112  // Initialize
     113  MessageService = NULL;
     114  MessageData = NULL;
     115  ExitRequest = false;
     116  This = this;
     117  Threads.insert(pthread_self());
     118
     119  // Initialise mutex
     120  int Ret;
     121  pthread_mutexattr_t Attr;
     122
     123  if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
     124    Message(FATAL, "pthread_mutex_settype() failed (%s)", strerror(Ret));
     125  }
     126  if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) {
     127    Message(FATAL, "pthread_mutex_init() failed (%s)", strerror(Ret));
     128  }
     129
     130  // Catch some signals
     131  signal(SIGQUIT, &SignalHandler);  // CTRL-Backspace
     132  signal(SIGTERM, &SignalHandler);  // Termination signal
     133  signal(SIGINT, &SignalHandler);   // CTRL-C
     134  signal(SIGHUP, &SignalHandler);   // Terminal closed
     135 
     136  // Catch C++ unhandled exceptions
     137  set_terminate(Terminate);
     138
     139  // Message service and initial message
     140  MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0);
     141
     142  string Rev(EVIDENCE_REVISION);
     143  Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__);
     144
     145  // Configuration class
     146  ConfClass = new class Config();
     147
     148  // Start server
     149  start(ServerName.c_str());
     150  addExitHandler(this);
     151}
     152
     153
     154// Destructor: Free memory
     155EvidenceServer::~EvidenceServer() {
     156
     157  Message(INFO, "Server stopped");
     158
     159  delete ConfClass;
     160
     161  int Ret;
     162  if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) {
     163        Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret));
     164  }
     165 
     166  delete MessageService;
     167  delete[] MessageData;
     168}
     169
     170
     171// DIM exit handler
     172void EvidenceServer::exitHandler(int Code) {
     173
     174  Message(INFO, "Exit handler called (DIM exit code %d)", Code);
     175  exit(EXIT_SUCCESS);
     176}
     177
     178// DIM error handler
     179void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
     180
     181  Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
     182}
     183
     184
     185// Set server message (if Severity is FATAL, exit() will be invoked)
     186void EvidenceServer::Message(MessageType Severity, const char *Format, ...) {
     187
     188  static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
     189  static char ErrorString[] = "vasprintf() failed in Message()";
     190  char *Text;
     191 
     192  // Assemble message from application
     193  va_list ArgumentPointer;
     194  va_start(ArgumentPointer, Format);
     195  if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
     196        Text = ErrorString;
     197        Severity = ERROR;
     198  }
     199  va_end(ArgumentPointer);
     200
     201  // Generate new Message structure and free text
     202  struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
     203  NewMsg->Severity = Severity;
     204  strcpy(NewMsg->Text, Text);
     205  if (Text != ErrorString) free(Text);
     206 
     207  // Send message to console and log file
     208  printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text);
     209  SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], NewMsg->Text);
     210
     211  // Update DIM message service
     212  if (MessageService != NULL) {
     213        MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1);
     214  }
     215
     216  // Delete old message
     217  Lock();
     218  delete[] MessageData;
     219  MessageData = NewMsg;
     220  Unlock(); 
     221
     222  // Terminate if severity if FATAL 
     223  if (Severity == FATAL) exit(EXIT_FAILURE);
     224}
     225
     226
     227// Send to central logging server with non-blocking command
     228void EvidenceServer::SendToLog(const char *Format, ...) {
     229
     230  static char ErrorString[] = "vasprintf() failed in SendToLog()";
     231  char *Buffer;
     232  int Ret;
     233
     234  // Evaluate variable argument list
     235  va_list ArgumentPointer;
     236  va_start(ArgumentPointer, Format);
     237  Ret = vasprintf(&Buffer, Format, ArgumentPointer);
     238  va_end(ArgumentPointer);
     239
     240  // Send to logger
     241  if (Ret != -1) {
     242        DimClient::sendCommandNB("DColl/Log", Buffer);
     243        free (Buffer);
     244  }
     245  else DimClient::sendCommandNB("DColl/Log", ErrorString);
     246}
     247
     248
     249// Get configuration data
     250//
     251// Program terminates if data is missing and no default given. Actual configuration
     252// request will be made only if config file has modification since last request.
     253// If called from infoHandler(), a non-blocking request will be made
     254string EvidenceServer::GetConfig(string Item, string Default) {
    77255
    78256  string Result;
    79 
     257  bool Blocking = false;
     258 
    80259  // If up-to-date data in configuration list available, return this
    81   This->Lock();
    82   if ((List.count(Item) > 0) && (List[Item].Time >= ConfigTimeStamp)) Result = List[Item].Value;
    83   This->Unlock();
     260  Lock();
     261  if ((List.count(Item) > 0) && (List[Item].Time >= ConfClass->ConfigTimeStamp)) Result = List[Item].Value;
     262  if (Threads.count(pthread_self()) != 0) Blocking = true;
     263  Unlock();
    84264  if (!Result.empty()) return Result;
    85265
    86   // Blocking configuration request if in main thread
    87   if (pthread_self() == ThreadID) {
     266  // Blocking configuration request
     267  if (Blocking) {
    88268        DimRpcInfo Config((char *) "ConfigRequest", NO_LINK);
    89269        Config.setData((char *) (Name + " " + Item).c_str());
     
    92272        if (!EvidenceServer::ServiceOK(&Config)) {
    93273      if (Default.empty()) {
    94                 This->Message(FATAL, "Configuration server unreachable, can't retrieve '%s'", Item.c_str());
     274                Message(FATAL, "Configuration server unreachable, can't retrieve '%s'", Item.c_str());
    95275          }
    96276          else Result = Default;
     
    103283        // Update configuration map     
    104284        if (!Result.empty()) {
    105           This->Lock();
     285          Lock();
    106286          List[Item].Value = Result;
    107           List[Item].Time = ConfigTimeStamp;
    108           This->Unlock();
     287          List[Item].Time = ConfClass->ConfigTimeStamp;
     288          Unlock();
    109289        }       
    110290  }
    111291
    112   // Non-blocking configuration request from other threads
    113   if (pthread_self() != ThreadID) {
    114         This->Lock();
     292  // Non-blocking configuration request
     293  if (!Blocking) {
     294        Lock();
    115295        if (List.count(Item) > 0) {
    116           // New request possible only when answer to previous request received
    117           if (CurrentItem.empty()) {
    118                 CurrentItem = Item;
    119                 setData(((char *) (Name + " " + Item).c_str()));
    120           }
    121 
    122           // Return current value
     296          ConfClass->RequestNB(Name + " " + Item);
    123297          Result = List[Item].Value;
    124298        }
    125         This->Unlock();
    126   }
     299        Unlock();
     300  }
     301
     302  // Terminate if no configuration information found
     303  if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str());
    127304
    128305  return Result;
     
    130307
    131308
    132 //////////////////////////
    133 // EvidenceServer Class //
    134 //////////////////////////
    135 
    136 // Initialise
    137 int EvidenceServer::ConfigSignal = 0;
    138 EvidenceServer *EvidenceServer::This = NULL;
    139 
    140 // Constructor
    141 EvidenceServer::EvidenceServer(string ServerName): Name(ServerName) {
    142 
    143   // Initialize
    144   MessageService = NULL;
    145   MessageData = NULL;
    146   ExitRequest = false;
    147   This = this;
    148 
    149   // Initialise mutex
    150   int Ret;
    151   pthread_mutexattr_t Attr;
    152 
    153   if ((Ret = pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_ERRORCHECK)) != 0) {
    154     Message(FATAL, "pthread_mutex_settype() failed (%s)", strerror(Ret));
    155   }
    156   if ((Ret = pthread_mutex_init(&Mutex, &Attr)) != 0) {
    157     Message(FATAL, "pthread_mutex_init() failed (%s)", strerror(Ret));
    158   }
    159 
    160   // Catch some signals
    161   signal(SIGQUIT, &SignalHandler);  // CTRL-Backspace
    162   signal(SIGTERM, &SignalHandler);  // Termination signal
    163   signal(SIGINT, &SignalHandler);   // CTRL-C
    164   signal(SIGHUP, &SignalHandler);   // Terminal closed
    165  
    166   // Catch C++ unhandled exceptions
    167   set_terminate(Terminate);
    168 
    169   // Configuration class (must be instantiate after signal handling installed)
    170   ConfClass = new class Config(Name);
    171 
    172   // Message service and initial message
    173   MessageService = new DimService((Name+"/Message").c_str(), (char *) "I:1;C", NULL, 0);
    174 
    175   string Rev(EVIDENCE_REVISION);
    176   Message(INFO, "Server started (%s, compiled %s %s)", (Rev.substr(1, Rev.size()-3)).c_str(),__DATE__, __TIME__);
    177 
    178   // Start server
    179   start(ServerName.c_str());
    180   addExitHandler(this);
    181 }
    182 
    183 // Destructor: Free memory
    184 EvidenceServer::~EvidenceServer() {
    185 
    186   Message(INFO, "Server stopped");
    187 
    188   delete ConfClass;
    189 
    190   int Ret;
    191   if ((Ret = pthread_mutex_destroy(&Mutex)) != 0) {
    192         Message(ERROR, "pthread_mutex_destroy() failed (%s)", strerror(Ret));
    193   }
    194  
    195   delete MessageService;
    196   delete[] MessageData; 
    197 }
    198 
    199 // DIM exit handler
    200 void EvidenceServer::exitHandler(int Code) {
    201 
    202   if (Code == 0) Message(INFO, "Message cleared by %s (ID %d)", getClientName(), getClientId());
    203   else {
    204         Message(INFO, "Exit handler called (DIM exit code %d)", Code);
    205         exit(EXIT_SUCCESS);
    206   }
    207 }
    208 
    209 // DIM error handler
    210 void EvidenceServer::errorHandler(int Severity, int Code, char *Text) {
    211 
    212   Message(ERROR, "%s (DIM error code %d, DIM severity %d)\n", Text, Code, Severity);
    213 }
    214 
    215 // Set server message (if Severity is FATAL, exit() will be invoked)
    216 void EvidenceServer::Message(MessageType Severity, const char *Format, ...) {
    217 
    218   static const char* StateString[] = {"Info", "Warn", "Error", "Fatal"};
    219   static char ErrorString[] = "vasprintf() failed in Message()";
    220   char *Text;
    221  
    222   // Assemble message from application
    223   va_list ArgumentPointer;
    224   va_start(ArgumentPointer, Format);
    225   if (vasprintf(&Text, Format, ArgumentPointer) == -1) {
    226         Text = ErrorString;
    227         Severity = ERROR;
    228   }
    229   va_end(ArgumentPointer);
    230 
    231   // Generate new Message structure and free text
    232   struct Message *NewMsg = (struct Message *) new char [sizeof(struct Message)+strlen(Text)+1];
    233   NewMsg->Severity = Severity;
    234   strcpy(NewMsg->Text, Text);
    235   if (Text != ErrorString) free(Text);
    236  
    237   // Send message to console and log file
    238   printf("%s (%s): %s\n", MessageService->getName(), StateString[Severity], NewMsg->Text);
    239   SendToLog("%s (%s): %s", MessageService->getName(), StateString[Severity], NewMsg->Text);
    240 
    241   // Update DIM message service
    242   if (MessageService != NULL) {
    243         MessageService->updateService(NewMsg, sizeof(struct Message)+strlen(NewMsg->Text)+1);
    244   }
    245 
    246   // Delete old message
    247   Lock();
    248   delete[] MessageData;
    249   MessageData = NewMsg;
    250   Unlock(); 
    251 
    252   // Terminate if severity if FATAL 
    253   if (Severity == FATAL) exit(EXIT_FAILURE);
    254 }
    255 
    256 
    257 // Send to central logging server with non-blocking command
    258 void EvidenceServer::SendToLog(const char *Format, ...) {
    259 
    260   static char ErrorString[] = "vasprintf() failed in SendToLog()";
    261   char *Buffer;
    262   int Ret;
    263 
    264   // Evaluate variable argument list
    265   va_list ArgumentPointer;
    266   va_start(ArgumentPointer, Format);
    267   Ret = vasprintf(&Buffer, Format, ArgumentPointer);
    268   va_end(ArgumentPointer);
    269 
    270   // Send to logger
    271   if (Ret != -1) {
    272         DimClient::sendCommandNB("DColl/Log", Buffer);
    273         free (Buffer);
    274   }
    275   else DimClient::sendCommandNB("DColl/Log", ErrorString);
    276 }
    277 
    278 
    279 // Get configuration data
    280 //
    281 // Program terminates if data is missing and no default given. Actual configuration
    282 // request will be made only if config file has modification since last request.
    283 // If called from infoHandler(), a non-blocking request will be made
    284 string EvidenceServer::GetConfig(string Item, string Default) {
    285 
    286   string Result = ConfClass->GetConfig(Item, Default);
    287   if (Result.empty()) Message(FATAL, "Missing configuration data '%s'", Item.c_str());
    288   return Result;
    289 }
    290 
    291 
    292 // Set signal emitted when configuraton file changes, signal handler calls ConfigChanged()
    293 void EvidenceServer::ActivateSignal(int Signal) {
    294 
    295   struct sigaction S;
    296 
    297   ConfigSignal = Signal;
    298   S.sa_handler = &SignalHandler;
    299   S.sa_flags = SA_RESTART;
    300   sigaction(Signal, &S, NULL);
    301 }
    302 
    303 
    304309// Locking and unlocking functions.
    305 // Signal blocked before locking (pthread_mutex_lock() not asynch-signal safe).
    306310// Message() is not used to avoid infinite recursion
    307311void EvidenceServer::Lock() {
    308312
    309313  int Ret;
    310   sigset_t Set;
    311 
    312   if (ConfigSignal != 0) {
    313         Ret = abs(sigemptyset(&Set));
    314         Ret += abs(sigaddset(&Set, ConfigSignal));
    315         Ret += abs(pthread_sigmask(SIG_BLOCK, &Set, NULL));
    316 
    317         if (Ret != 0) {
    318           printf("Signal masking failed in Lock()");
    319           SendToLog("Signal masking failed in Lock()");
    320           exit(EXIT_FAILURE);
    321         }
    322   }
    323 
    324   if ((Ret = pthread_mutex_lock(&Mutex)) != 0) {
     314
     315  if ((Ret = pthread_mutex_lock(&EvidenceServer::Mutex)) != 0) {
    325316        printf("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
    326317        SendToLog("pthread_mutex_lock() failed in Lock() (%s)", strerror(Ret));
     
    332323
    333324  int Ret;
    334   sigset_t Set;
    335 
    336   if ((Ret = pthread_mutex_unlock(&Mutex)) != 0) {
     325
     326  if ((Ret = pthread_mutex_unlock(&EvidenceServer::Mutex)) != 0) {
    337327        printf("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
    338328        SendToLog("pthread_mutex_unlock() failed in Unlock() (%s)", strerror(Ret));
    339329        exit(EXIT_FAILURE);
    340   }
    341  
    342   if (ConfigSignal != 0) {
    343         Ret = abs(sigemptyset(&Set));
    344         Ret += abs(sigaddset(&Set, ConfigSignal));
    345         Ret += abs(pthread_sigmask(SIG_UNBLOCK, &Set, NULL));
    346 
    347         if (Ret != 0) {
    348           printf("Signal unmasking failed in Unlock()");
    349           SendToLog("Signal unmasking failed in Unlock()");
    350           exit(EXIT_FAILURE);
    351         }
    352   }
     330  } 
    353331}
    354332
    355333
    356334// ====== Static methods ======
     335
     336// Stub to call ConfigChanged() method of class as separate thread
     337// Thread set is used to determine if blocking or non-blocking rpc is to be used
     338void EvidenceServer::CallConfigChanged() {
     339
     340  EvidenceServer::Lock();
     341  EvidenceServer::Threads.insert(pthread_self());
     342  EvidenceServer::Unlock();
     343
     344  This->ConfigChanged();
     345 
     346  EvidenceServer::Lock();
     347  EvidenceServer::Threads.erase(pthread_self());
     348  EvidenceServer::Unlock();
     349}
     350
    357351
    358352// Signal handler (causes pause() and other syscalls to return)
     
    360354
    361355  static bool Called = false;
    362 
    363   // If signal indicates configuration change, invoke call-back
    364   if (Signal == EvidenceServer::ConfigSignal) {
    365     This->ConfigChanged();
    366         return;
    367   }
    368356
    369357  // At first invocation just request exit
     
    378366  exit(EXIT_FAILURE);
    379367}
     368
    380369
    381370// C++ exception handler (derived from gcc __verbose_terminate_handler())
     
    492481}
    493482
     483bool EvidenceServer::ServiceOK(DimCurrentInfo *Item) {
     484
     485  return !((Item->getSize() == strlen(NO_LINK)+1) && 
     486          (memcmp(Item->getData(), NO_LINK, Item->getSize()) == 0));
     487}
     488
    494489
    495490// Tokenize std::string using given delimeter list
  • Evidence/Evidence.h

    r253 r262  
    1010#include <vector>
    1111#include <map>
     12#include <set>
    1213
    1314#include <exception>
     
    2021#define EVIDENCE_REVISION "$Revision$"
    2122
     23void ConfigChanged();
     24
    2225// Class declation of Evidence server
    2326class EvidenceServer: public DimServer {
    2427
    2528        // Internal class for configuration requests
    26         class Config: public DimClient, public DimRpcInfo {
     29        class Config: public DimInfo, public DimCommand, public DimRpcInfo {
    2730
    28                 std::string Name;
    2931                DimInfo *Service;
    30                 int ConfigTimeStamp;
    3132                std::string CurrentItem;
    32                 pthread_t ThreadID;
    3333
    34                 struct Item {
    35                   std::string Value;
    36                   int Time;
    37                 };
    38                 std::map<std::string, struct Item> List;
    39 
     34                void commandHandler();
    4035                void infoHandler();
    4136                void rpcInfoHandler();
    4237
    4338          public:
    44                 Config(std::string);
     39                Config();
    4540                ~Config();
    4641
    47                 std::string GetConfig(std::string, std::string);
     42                int ConfigTimeStamp;
     43                std::string RequestNB(std::string);
    4844        };
     45
     46        struct Item {
     47          std::string Value;
     48          int Time;
     49        };
     50        std::map<std::string, struct Item> List;
    4951
    5052        struct Message {
     
    5759        struct Message *MessageData;
    5860        class Config *ConfClass;
    59         static int ConfigSignal;                // static since accessed in signal handler
    60         pthread_mutex_t Mutex;
     61        static pthread_mutex_t Mutex;
     62        static std::set<pthread_t> Threads;
    6163        static EvidenceServer *This;
    6264
     
    6567        virtual void errorHandler(int, int, char *);
    6668        virtual void exitHandler(int);
    67         virtual void ConfigChanged() {};
     69    static void CallConfigChanged();    // static for phread_create()
    6870
    6971  public:
     
    7476
    7577        void Message(MessageType, const char *, ...);
    76         void SendToLog(const char *, ...);
     78        static void SendToLog(const char *, ...);
    7779        std::string GetConfig(std::string, std::string = std::string());
    78         void ActivateSignal(int);
    79         void Lock();
    80         void Unlock();
     80        virtual void ConfigChanged() {};
     81        static void Lock();
     82        static void Unlock();
    8183        static std::string ToString(char *, void *, int);
    8284        static bool ServiceOK(DimInfo *);
    8385        static bool ServiceOK(DimRpcInfo *);
     86        static bool ServiceOK(DimCurrentInfo *);
    8487        static std::vector<std::string> Tokenize(const std::string &, const std::string & = " ");
    8588
  • Evidence/readme.txt

    r255 r262  
    282823/6/2010       GetConfig() returns std::string. Non-blocking configuration request in case
    2929                        GetConfig() not called from main thread. Access to configuration information
    30                         internally mutex protected. With ActivateSignal() a signal can be set that is send
    31                         to main thread upon configuration file change, the build-in signal handler then
    32                         invokes the (virtual) method ConfigChanged().
     30                        internally mutex protected.
    333124/6/2010       Workaround for erroneous /SERVICE_LIST updates. Added static tokenize method to
    3432                        Evidence class.
     
    464421/7/2010       Lock()/Unlock() do not report errors via Message(), but print to console and use
    4745                        SendToLog(). That avoids a recursion problem since Message() also uses locking.
    48                         The general exitHandler() will react in a special way to code 0: it will reset
    49                         the message severity. This feature is used by Alarm if it receives a command to
    50                         reset an alarm level.
     4626/7/2010       General command '/ResetMessage' will reset message text and severity. This feature is
     47                        used by Alarm if it receives a command to reset an alarm level.
     48                        ConfigChanged() is called as separate thread when configuration file changes. Thread ID
     49                        is checked in GetConfig() and also from this thread it will make blocking requests.
Note: See TracChangeset for help on using the changeset viewer.