1 // ANNA - Anna is Not Nothingness Anymore //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
11 #include <anna/core/functions.hpp>
12 #include <anna/core/tracing/TraceMethod.hpp>
13 #include <anna/core/tracing/TraceFunction.hpp>
14 #include <anna/core/mt/Thread.hpp>
16 #include <anna/app/functions.hpp>
18 #include <anna/xml/Node.hpp>
19 #include <anna/xml/Attribute.hpp>
21 #include <anna/comm/Communicator.hpp>
23 #include <anna/timex/Engine.hpp>
24 #include <anna/timex/internal/TickConsumer.hpp>
25 #include <anna/timex/internal/TickProducer.hpp>
26 #include <anna/timex/TimeEventObserver.hpp>
27 #include <anna/timex/internal/sccs.hpp>
31 using namespace anna::comm;
34 Resolucion minima (en milisegundos) soportada por el controlador de tiempos.
37 const Millisecond anna::timex::Engine::minResolution(100);
40 anna::timex::Engine::Engine(const Millisecond & maxTimeout, const Millisecond & resolution) :
41 app::Component(getClassName()),
44 a_maxTimeout(maxTimeout),
45 a_resolution(resolution),
49 a_expiredQuantum(NULL),
51 timex::sccs::activate();
54 anna::timex::Engine::~Engine() {
55 delete a_tickConsumer;
56 delete [] a_timeTable;
59 //--------------------------------------------------------------------------------------------
60 // Inicializa la configuracin.
62 // (2) En Solaris el ualarm tiene una deriva constante que parece ser que dependen de la
63 // arquitectura de la m�uina. Para calcular esta deriva y poder corregirla en los
64 // posteriores ualarm vamos a calcular la diferencia entre el tiempo esperado y el momento
65 // en que realmente llega la seal de ualarm.
66 //--------------------------------------------------------------------------------------------
67 void anna::timex::Engine::do_initialize()
68 throw(RuntimeException) {
69 LOGMETHOD(TraceMethod tm("timex::Engine", "do_initialize", ANNA_FILE_LOCATION));
71 if(a_maxQuantum > 0) {
72 Logger::write(Logger::Warning, "Time controller was previously started", ANNA_FILE_LOCATION);
76 if(a_resolution < minResolution)
77 throw RuntimeException(functions::asString("Resolution must be greater than %d milliseconds", minResolution.getValue()), ANNA_FILE_LOCATION);
79 if(a_maxTimeout <= a_resolution)
80 throw RuntimeException(functions::asString("Max-Timeout must be greater than %d milliseconds", a_resolution.getValue()), ANNA_FILE_LOCATION);
82 a_maxQuantum = a_maxTimeout / a_resolution;
84 while((a_maxQuantum * a_resolution) <= a_maxTimeout) a_maxQuantum ++;
86 a_timeTable = new Quantum [a_maxQuantum];
87 Communicator* communicator = app::functions::component <Communicator> (ANNA_FILE_LOCATION);
88 communicator->attach(a_tickConsumer = new TickConsumer(this));
90 * Esto siempre se ejecutará en un thread aparte aunque la librería haya sido generada en modo ST,
91 * porque así evitamos tener que generar el pulso de reloj mediante una alarma.
93 a_tickProducer = new TickProducer(this, a_tickConsumer->getfdWrite());
95 pthread_attr_init(&attr);
98 if((errorCode = pthread_create(&a_threadProducer, &attr, TickProducer::exec, a_tickProducer)) != 0)
99 throw RuntimeException(std::string("timex::Engine::do_initialize"), errorCode, ANNA_FILE_LOCATION);
102 string msg("Time controller | Max Timeout: ");
103 msg += functions::asString(a_maxTimeout);
104 msg += " | Resolution: ";
105 msg += functions::asString(a_resolution);
106 msg += " | Max Quantum: ";
107 msg += functions::asString(a_maxQuantum);
109 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
113 // Reimplementado de app::Component
114 void anna::timex::Engine::do_cloneParent()
119 * Se invoca desde app::Application::clone -> app::Component::do_cloneChild (ojo EN EL NUEVO PROCESO).
120 * Instala la senhal de tick en el proceso, ya que la alarma no se hereda directamente.
122 void anna::timex::Engine::do_cloneChild()
123 throw(RuntimeException) {
126 //----------------------------------------------------------------------------
127 // No para los hilos de generacion, sino que evita que se escriban los bytes
129 //----------------------------------------------------------------------------
130 void anna::timex::Engine::pause()
131 throw(RuntimeException) {
132 Guard guard(this, "timex::Engine (pause)");
134 if(a_tickProducer->isInPause() == false) {
136 string msg("timex::Engine::pause | Pending TimeEvents: ");
137 msg += functions::asString((const int) a_directory.size());
138 Logger::warning(msg, ANNA_FILE_LOCATION);
140 a_tickProducer->setIsInPause(true);
144 void anna::timex::Engine::play()
145 throw(RuntimeException) {
146 Guard guard(this, "timex::Engine (play)");
148 if(a_tickProducer->isInPause() == true) {
150 string msg("timex::Engine::play | Pending TimeEvents: ");
151 msg += functions::asString((const int) a_directory.size());
152 Logger::warning(msg, ANNA_FILE_LOCATION);
154 a_tickProducer->setIsInPause(false);
158 void anna::timex::Engine::activate(timex::TimeEvent* timeEvent)
159 throw(RuntimeException) {
160 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "activate", ANNA_FILE_LOCATION));
162 if(a_maxQuantum == 0)
163 throw RuntimeException("Engine::initialize was not called", ANNA_FILE_LOCATION);
165 if(timeEvent == NULL)
166 throw RuntimeException("Cannot activate a NULL TimeEvent", ANNA_FILE_LOCATION);
168 if(timeEvent->a_controller != NULL) {
169 string msg(timeEvent->asString());
170 msg += " | Already activated";
171 throw RuntimeException(msg, ANNA_FILE_LOCATION);
174 const Millisecond & timeout(timeEvent->getTimeout());
176 if(timeout > a_maxTimeout || timeout < a_resolution) {
177 string msg("Invalid TimeEvent timeout | Max Timeout: ");
178 msg += functions::asString(a_maxTimeout);
179 msg += " | Min timeout (resolution): ";
180 msg += functions::asString(a_resolution);
182 msg += timeEvent->asString();
183 throw RuntimeException(msg, ANNA_FILE_LOCATION);
186 Guard guard(this, "timex::Engine (activate)");
188 if(a_tickProducer->isInPause() == true) {
189 string msg("Cannot activate TimeEvents with timex::Engine in pause | ");
190 msg += timeEvent->asString();
191 throw RuntimeException(msg, ANNA_FILE_LOCATION);
194 const TimeEvent::Id id(timeEvent->getId());
196 if(a_directory.find(id) != a_directory.end())
197 throw RuntimeException(functions::asString("Id %ld (0x%x) already in use", id, id), ANNA_FILE_LOCATION);
199 int iq = getQuantum(timeout);
200 Quantum* quantum = &a_timeTable [iq];
202 timeEvent->a_controller = this;
203 a_directory.insert(Directory::value_type(id, quantum));
205 if(quantum == a_expiredQuantum) {
206 a_delayedQuantum.push_back(timeEvent);
209 quantum->push_back(timeEvent);
214 string msg("timex::Engine::activate | ");
215 msg += timeEvent->asString();
216 msg += " | Current quantum: ";
217 msg += functions::asString(a_currentQuantum);
218 msg += " | Quantum programmed: ";
219 msg += functions::asString(iq);
221 msg += functions::asHexString(anna_ptrnumber_cast(quantum));
222 msg += functions::asText(") | Delayed: ", delayed);
223 Logger::debug(msg, ANNA_FILE_LOCATION);
227 anna::timex::TimeEvent* anna::timex::Engine::getTimeEvent(const timex::TimeEvent::Id eventTimeId)
229 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "getTimeEvent", ANNA_FILE_LOCATION));
230 Directory::iterator iid;
231 TimeEvent* result(NULL);
234 Guard guard(this, "timex::Engine (getTimeEvent)");
236 if((iid = a_directory.find(eventTimeId)) == a_directory.end())
239 quantum = iid->second;
241 for(Quantum::iterator ii = quantum->begin(), maxii = quantum->end(); ii != maxii; ii ++) {
242 if((aux = *ii)->getId() == eventTimeId) {
251 void anna::timex::Engine::cancel(timex::TimeEvent* timeEvent)
252 throw(RuntimeException) {
253 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "cancel", ANNA_FILE_LOCATION));
255 if(timeEvent == NULL)
256 throw RuntimeException("Cannot cancel a NULL TimeEvent", ANNA_FILE_LOCATION);
258 if(timeEvent->a_controller == NULL) {
260 string msg(timeEvent->asString());
261 msg += " | Not activated";
262 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
267 Directory::iterator iid;
269 Guard guard(this, "timex::Engine (cancel)");
271 if((iid = a_directory.find(timeEvent->getId())) == a_directory.end())
274 if((quantum = iid->second) == a_expiredQuantum) {
276 string msg("timex::Engine::cancel | ");
277 msg += timeEvent->asString();
278 msg += " | Processing already programmed";
279 Logger::warning(msg, ANNA_FILE_LOCATION);
284 a_directory.erase(iid);
285 quantum->erase(find(quantum->begin(), quantum->end(), timeEvent));
286 timeEvent->a_controller = NULL;
288 string msg("timex::Engine::cancel | ");
289 msg += timeEvent->asString();
290 msg += " | Current quantum: ";
291 msg += functions::asString(a_currentQuantum);
292 msg += " | Quantum programmed: ";
293 msg += functions::asHexString(anna_ptrnumber_cast(quantum));
294 Logger::debug(msg, ANNA_FILE_LOCATION);
296 notifyRelease(timeEvent);
299 void anna::timex::Engine::do_stop()
301 LOGMETHOD(TraceMethod tm("timex::Engine", "do_stop", ANNA_FILE_LOCATION));
302 Quantum::iterator ii, maxii;
303 Guard guard(this, "timex::Engine (do_stop)");
304 app::functions::component <Communicator> (ANNA_FILE_LOCATION)->detach(a_tickConsumer);
307 a_tickProducer->requestStop();
310 TimeEvent* timeEvent;
312 for(int iq = 0; iq < a_maxQuantum; iq ++) {
313 Quantum& quantum(a_timeTable [iq]);
315 for(ii = quantum.begin(), maxii = quantum.end(); ii != maxii; ii ++) {
316 timeEvent = Engine::timeEvent(ii);
319 timeEvent->a_controller = NULL;
321 notifyRelease(timeEvent);
322 } catch(Exception& ex) {
331 void anna::timex::Engine::kill()
333 Guard guard(this, "timex::Engine (kill)");
334 app::functions::component <Communicator> (ANNA_FILE_LOCATION)->detach(a_tickConsumer);
337 a_tickProducer->requestStop();
339 string msg("timex::Engine::kill | ");
340 msg += functions::asString(" | Pending TimeEvents: %d", a_directory.size());
341 Logger::critical(msg, ANNA_FILE_LOCATION);
343 for(int iq = 0; iq < a_maxQuantum; iq ++)
344 a_timeTable [iq].clear();
346 a_delayedQuantum.clear();
350 //----------------------------------------------------------------------------------
351 // (1) Podr� ser que al invocar al m�odo 'expire' de una evento se halla originado
352 // la cancelacin de algn otro evento. As�que si el id de evento ya no esta en
353 // en el directorio => da igual, se ignora ya que luego borraremos todos los
354 // eventos del Quantum.
355 // (2) Si mientas estamos caducando este Quantum hay que activar transacciones que
356 // debe ir sobre el => se guardan en un vector temporal y se copian al final
357 // del proceso, con lo que evita totalmente la posiblidad de perdida.
358 // (3) Si el temporizador ha sido reactivado no tiene que liberarlo.
359 //----------------------------------------------------------------------------------
360 void anna::timex::Engine::tick()
361 throw(RuntimeException) {
362 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "tick", ANNA_FILE_LOCATION));
364 if (Logger::isActive (Logger::Local6) == true && a_directory.size () > 0) {
365 string msg ("Tick | Quantum: ");
366 msg += functions::asString (a_currentQuantum);
368 msg += functions::asString (functions::millisecond ());
370 msg += functions::asString (a_directory.size ());
371 Logger::write (Logger::Local6, msg, ANNA_FILE_LOCATION);
374 Guard guard(this, "timex::Engine (tick)");
377 a_expiredQuantum = &a_timeTable [a_currentQuantum];
378 TimeEvent* timeEvent;
379 Directory::iterator iid;
380 quantum_iterator ii, maxii;
383 for(ii = a_expiredQuantum->begin(), maxii = a_expiredQuantum->end(); ii != maxii; ii ++) {
384 timeEvent = Engine::timeEvent(ii);
386 if((iid = a_directory.find(timeEvent->getId())) == a_directory.end()) // (1)
389 a_directory.erase(iid);
391 if(Logger::isActive(Logger::Debug)) {
392 string msg(timeEvent->asString());
393 msg += " | Quantum programmed: ";
394 msg += functions::asHexString(anna_ptrnumber_cast(a_expiredQuantum));
396 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
399 timeEvent->a_controller = NULL;
401 * Si tiene NO tiene observer asociado, seguramente se auto-liberará en el expire
402 * Si tiene observer, se invocará al método correspondiente.
404 observed = (timeEvent->a_observer != NULL);
407 timeEvent->expire(this);
409 } catch(Exception& ex) {
413 // Si tiene observer => el expire NO puede haber liberado el TimeEvent
414 if(observed == true) {
415 if(timeEvent->a_controller == NULL) // (3)
416 timeEvent->a_observer->release(timeEvent);
420 a_expiredQuantum->clear();
422 if(a_delayedQuantum.size() > 0) { // (2)
423 for(ii = a_delayedQuantum.begin(), maxii = a_delayedQuantum.end(); ii != maxii; ii ++)
424 a_expiredQuantum->push_back(Engine::timeEvent(ii));
426 a_delayedQuantum.clear();
429 a_expiredQuantum = NULL;
431 if(++ a_currentQuantum >= a_maxQuantum)
432 a_currentQuantum = 0;
434 eventEndQuantum(counter);
437 void anna::timex::Engine::notifyRelease(timex::TimeEvent* timeEvent)
439 if(timeEvent->a_observer != NULL)
440 timeEvent->a_observer->release(timeEvent);
443 string anna::timex::Engine::asString() const
445 string msg("timex::Engine { ");
446 msg += app::Component::asString();
447 msg += " | Max Timeout: ";
448 msg += functions::asString(a_maxTimeout);
449 msg += " ms | Resolution: ";
450 msg += functions::asString(a_resolution);
451 msg += " ms | Programming: ";
452 msg += functions::asString(a_maxQuantum);
453 msg += " quantums | Pending TimeEvents: ";
454 msg += functions::asString((const int) a_directory.size());
458 xml::Node* anna::timex::Engine::asXML(xml::Node* parent) const
460 parent = Component::asXML(parent);
461 xml::Node* result = parent->createChild("timex.Engine");
462 result->createAttribute("MaxTimeout", a_maxTimeout);
463 result->createAttribute("Resolution", a_resolution);
464 result->createAttribute("N", a_directory.size());
465 result->createAttribute("Pause", functions::asString(a_tickProducer->isInPause()));
466 xml::Node* node = result->createChild("Quantum");
467 node->createAttribute("Current", a_currentQuantum);
468 node->createAttribute("Max", a_maxQuantum);