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 Minimum resolution (in milliseconds) supported by the time manager
37 const Millisecond anna::timex::Engine::minResolution(10);
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_resolution < (Millisecond)100) {
80 LOGWARNING(Logger::warning(functions::asString("Resolutions under 100 milliseconds (%d in this case) slightly increase the CPU usage", a_resolution.getValue()), ANNA_FILE_LOCATION));
83 if(a_maxTimeout <= a_resolution)
84 throw RuntimeException(functions::asString("Max-Timeout must be greater than %d milliseconds", a_resolution.getValue()), ANNA_FILE_LOCATION);
86 a_maxQuantum = a_maxTimeout / a_resolution;
88 while((a_maxQuantum * a_resolution) <= a_maxTimeout) a_maxQuantum ++;
90 a_timeTable = new Quantum [a_maxQuantum];
91 Communicator* communicator = app::functions::component <Communicator> (ANNA_FILE_LOCATION);
92 communicator->attach(a_tickConsumer = new TickConsumer(this));
94 * Esto siempre se ejecutará en un thread aparte aunque la librería haya sido generada en modo ST,
95 * porque así evitamos tener que generar el pulso de reloj mediante una alarma.
97 a_tickProducer = new TickProducer(this, a_tickConsumer->getfdWrite());
99 pthread_attr_init(&attr);
102 if((errorCode = pthread_create(&a_threadProducer, &attr, TickProducer::exec, a_tickProducer)) != 0)
103 throw RuntimeException(std::string("timex::Engine::do_initialize"), errorCode, ANNA_FILE_LOCATION);
106 string msg("Time controller | Max Timeout: ");
107 msg += functions::asString(a_maxTimeout);
108 msg += " | Resolution: ";
109 msg += functions::asString(a_resolution);
110 msg += " | Max Quantum: ";
111 msg += functions::asString(a_maxQuantum);
113 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
117 // Reimplementado de app::Component
118 void anna::timex::Engine::do_cloneParent()
123 * Se invoca desde app::Application::clone -> app::Component::do_cloneChild (ojo EN EL NUEVO PROCESO).
124 * Instala la senhal de tick en el proceso, ya que la alarma no se hereda directamente.
126 void anna::timex::Engine::do_cloneChild()
127 throw(RuntimeException) {
130 //----------------------------------------------------------------------------
131 // No para los hilos de generacion, sino que evita que se escriban los bytes
133 //----------------------------------------------------------------------------
134 void anna::timex::Engine::pause()
135 throw(RuntimeException) {
136 Guard guard(this, "timex::Engine (pause)");
138 if(a_tickProducer->isInPause() == false) {
140 string msg("timex::Engine::pause | Pending TimeEvents: ");
141 msg += functions::asString((const int) a_directory.size());
142 Logger::warning(msg, ANNA_FILE_LOCATION);
144 a_tickProducer->setIsInPause(true);
148 void anna::timex::Engine::play()
149 throw(RuntimeException) {
150 Guard guard(this, "timex::Engine (play)");
152 if(a_tickProducer->isInPause() == true) {
154 string msg("timex::Engine::play | Pending TimeEvents: ");
155 msg += functions::asString((const int) a_directory.size());
156 Logger::warning(msg, ANNA_FILE_LOCATION);
158 a_tickProducer->setIsInPause(false);
162 void anna::timex::Engine::activate(timex::TimeEvent* timeEvent)
163 throw(RuntimeException) {
164 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "activate", ANNA_FILE_LOCATION));
166 if(a_maxQuantum == 0)
167 throw RuntimeException("Engine::initialize was not called", ANNA_FILE_LOCATION);
169 if(timeEvent == NULL)
170 throw RuntimeException("Cannot activate a NULL TimeEvent", ANNA_FILE_LOCATION);
172 if(timeEvent->a_controller != NULL) {
173 string msg(timeEvent->asString());
174 msg += " | Already activated";
175 throw RuntimeException(msg, ANNA_FILE_LOCATION);
178 const Millisecond & timeout(timeEvent->getTimeout());
180 if(timeout > a_maxTimeout || timeout < a_resolution) {
181 string msg("Invalid TimeEvent timeout | Max Timeout: ");
182 msg += functions::asString(a_maxTimeout);
183 msg += " | Min timeout (resolution): ";
184 msg += functions::asString(a_resolution);
186 msg += timeEvent->asString();
187 throw RuntimeException(msg, ANNA_FILE_LOCATION);
190 Guard guard(this, "timex::Engine (activate)");
192 if(a_tickProducer->isInPause() == true) {
193 string msg("Cannot activate TimeEvents with timex::Engine in pause | ");
194 msg += timeEvent->asString();
195 throw RuntimeException(msg, ANNA_FILE_LOCATION);
198 const TimeEvent::Id id(timeEvent->getId());
200 if(a_directory.find(id) != a_directory.end())
201 throw RuntimeException(functions::asString("Id %ld (0x%x) already in use", id, id), ANNA_FILE_LOCATION);
203 int iq = getQuantum(timeout);
204 Quantum* quantum = &a_timeTable [iq];
206 timeEvent->a_controller = this;
207 a_directory.insert(Directory::value_type(id, quantum));
209 if(quantum == a_expiredQuantum) {
210 a_delayedQuantum.push_back(timeEvent);
213 quantum->push_back(timeEvent);
218 string msg("timex::Engine::activate | ");
219 msg += timeEvent->asString();
220 msg += " | Current quantum: ";
221 msg += functions::asString(a_currentQuantum);
222 msg += " | Quantum programmed: ";
223 msg += functions::asString(iq);
225 msg += functions::asHexString(anna_ptrnumber_cast(quantum));
226 msg += functions::asText(") | Delayed: ", delayed);
227 Logger::debug(msg, ANNA_FILE_LOCATION);
231 anna::timex::TimeEvent* anna::timex::Engine::getTimeEvent(const timex::TimeEvent::Id eventTimeId)
233 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "getTimeEvent", ANNA_FILE_LOCATION));
234 Directory::iterator iid;
235 TimeEvent* result(NULL);
238 Guard guard(this, "timex::Engine (getTimeEvent)");
240 if((iid = a_directory.find(eventTimeId)) == a_directory.end())
243 quantum = iid->second;
245 for(Quantum::iterator ii = quantum->begin(), maxii = quantum->end(); ii != maxii; ii ++) {
246 if((aux = *ii)->getId() == eventTimeId) {
255 void anna::timex::Engine::cancel(timex::TimeEvent* timeEvent)
256 throw(RuntimeException) {
257 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "cancel", ANNA_FILE_LOCATION));
259 if(timeEvent == NULL)
260 throw RuntimeException("Cannot cancel a NULL TimeEvent", ANNA_FILE_LOCATION);
262 if(timeEvent->a_controller == NULL) {
264 string msg(timeEvent->asString());
265 msg += " | Not activated";
266 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
271 Directory::iterator iid;
273 Guard guard(this, "timex::Engine (cancel)");
275 if((iid = a_directory.find(timeEvent->getId())) == a_directory.end())
278 if((quantum = iid->second) == a_expiredQuantum) {
280 string msg("timex::Engine::cancel | ");
281 msg += timeEvent->asString();
282 msg += " | Processing already programmed";
283 Logger::warning(msg, ANNA_FILE_LOCATION);
288 a_directory.erase(iid);
289 quantum->erase(find(quantum->begin(), quantum->end(), timeEvent));
290 timeEvent->a_controller = NULL;
292 string msg("timex::Engine::cancel | ");
293 msg += timeEvent->asString();
294 msg += " | Current quantum: ";
295 msg += functions::asString(a_currentQuantum);
296 msg += " | Quantum programmed: ";
297 msg += functions::asHexString(anna_ptrnumber_cast(quantum));
298 Logger::debug(msg, ANNA_FILE_LOCATION);
300 notifyRelease(timeEvent);
303 void anna::timex::Engine::do_stop()
305 LOGMETHOD(TraceMethod tm("timex::Engine", "do_stop", ANNA_FILE_LOCATION));
306 Quantum::iterator ii, maxii;
307 Guard guard(this, "timex::Engine (do_stop)");
308 app::functions::component <Communicator> (ANNA_FILE_LOCATION)->detach(a_tickConsumer);
311 a_tickProducer->requestStop();
314 TimeEvent* timeEvent;
316 for(int iq = 0; iq < a_maxQuantum; iq ++) {
317 Quantum& quantum(a_timeTable [iq]);
319 for(ii = quantum.begin(), maxii = quantum.end(); ii != maxii; ii ++) {
320 timeEvent = Engine::timeEvent(ii);
323 timeEvent->a_controller = NULL;
325 notifyRelease(timeEvent);
326 } catch(Exception& ex) {
335 void anna::timex::Engine::kill()
337 Guard guard(this, "timex::Engine (kill)");
338 app::functions::component <Communicator> (ANNA_FILE_LOCATION)->detach(a_tickConsumer);
341 a_tickProducer->requestStop();
343 string msg("timex::Engine::kill | ");
344 msg += functions::asString(" | Pending TimeEvents: %d", a_directory.size());
345 Logger::critical(msg, ANNA_FILE_LOCATION);
347 for(int iq = 0; iq < a_maxQuantum; iq ++)
348 a_timeTable [iq].clear();
350 a_delayedQuantum.clear();
354 //----------------------------------------------------------------------------------
355 // (1) Podr� ser que al invocar al m�odo 'expire' de una evento se halla originado
356 // la cancelacin de algn otro evento. As�que si el id de evento ya no esta en
357 // en el directorio => da igual, se ignora ya que luego borraremos todos los
358 // eventos del Quantum.
359 // (2) Si mientas estamos caducando este Quantum hay que activar transacciones que
360 // debe ir sobre el => se guardan en un vector temporal y se copian al final
361 // del proceso, con lo que evita totalmente la posiblidad de perdida.
362 // (3) Si el temporizador ha sido reactivado no tiene que liberarlo.
363 //----------------------------------------------------------------------------------
364 void anna::timex::Engine::tick()
365 throw(RuntimeException) {
366 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "tick", ANNA_FILE_LOCATION));
368 if (Logger::isActive (Logger::Local6) == true && a_directory.size () > 0) {
369 string msg ("Tick | Quantum: ");
370 msg += functions::asString (a_currentQuantum);
372 msg += functions::asString (functions::millisecond ());
374 msg += functions::asString (a_directory.size ());
375 Logger::write (Logger::Local6, msg, ANNA_FILE_LOCATION);
378 Guard guard(this, "timex::Engine (tick)");
381 a_expiredQuantum = &a_timeTable [a_currentQuantum];
382 TimeEvent* timeEvent;
383 Directory::iterator iid;
384 quantum_iterator ii, maxii;
387 for(ii = a_expiredQuantum->begin(), maxii = a_expiredQuantum->end(); ii != maxii; ii ++) {
388 timeEvent = Engine::timeEvent(ii);
390 if((iid = a_directory.find(timeEvent->getId())) == a_directory.end()) // (1)
393 a_directory.erase(iid);
395 if(Logger::isActive(Logger::Debug)) {
396 string msg(timeEvent->asString());
397 msg += " | Quantum programmed: ";
398 msg += functions::asHexString(anna_ptrnumber_cast(a_expiredQuantum));
400 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
403 timeEvent->a_controller = NULL;
405 * Si tiene NO tiene observer asociado, seguramente se auto-liberará en el expire
406 * Si tiene observer, se invocará al método correspondiente.
408 observed = (timeEvent->a_observer != NULL);
411 timeEvent->expire(this);
413 } catch(Exception& ex) {
417 // Si tiene observer => el expire NO puede haber liberado el TimeEvent
418 if(observed == true) {
419 if(timeEvent->a_controller == NULL) // (3)
420 timeEvent->a_observer->release(timeEvent);
424 a_expiredQuantum->clear();
426 if(a_delayedQuantum.size() > 0) { // (2)
427 for(ii = a_delayedQuantum.begin(), maxii = a_delayedQuantum.end(); ii != maxii; ii ++)
428 a_expiredQuantum->push_back(Engine::timeEvent(ii));
430 a_delayedQuantum.clear();
433 a_expiredQuantum = NULL;
435 if(++ a_currentQuantum >= a_maxQuantum)
436 a_currentQuantum = 0;
438 eventEndQuantum(counter);
441 void anna::timex::Engine::notifyRelease(timex::TimeEvent* timeEvent)
443 if(timeEvent->a_observer != NULL)
444 timeEvent->a_observer->release(timeEvent);
447 string anna::timex::Engine::asString() const
449 string msg("timex::Engine { ");
450 msg += app::Component::asString();
451 msg += " | Max Timeout: ";
452 msg += functions::asString(a_maxTimeout);
453 msg += " ms | Resolution: ";
454 msg += functions::asString(a_resolution);
455 msg += " ms | Programming: ";
456 msg += functions::asString(a_maxQuantum);
457 msg += " quantums | Pending TimeEvents: ";
458 msg += functions::asString((const int) a_directory.size());
462 xml::Node* anna::timex::Engine::asXML(xml::Node* parent) const
464 parent = Component::asXML(parent);
465 xml::Node* result = parent->createChild("timex.Engine");
466 result->createAttribute("MaxTimeout", a_maxTimeout);
467 result->createAttribute("Resolution", a_resolution);
468 result->createAttribute("N", a_directory.size());
469 result->createAttribute("Pause", functions::asString(a_tickProducer->isInPause()));
470 xml::Node* node = result->createChild("Quantum");
471 node->createAttribute("Current", a_currentQuantum);
472 node->createAttribute("Max", a_maxQuantum);