1 // ANNA - Anna is Not Nothingness Anymore
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
5 // http://redmine.teslayout.com/projects/anna-suite
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
11 // * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 // * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
17 // * Neither the name of the copyright holder nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 // Authors: eduardo.ramos.testillano@gmail.com
34 // cisco.tierra@gmail.com
39 #include <anna/core/functions.hpp>
40 #include <anna/core/tracing/TraceMethod.hpp>
41 #include <anna/core/tracing/TraceFunction.hpp>
42 #include <anna/core/mt/Thread.hpp>
44 #include <anna/app/functions.hpp>
46 #include <anna/xml/Node.hpp>
47 #include <anna/xml/Attribute.hpp>
49 #include <anna/comm/Communicator.hpp>
51 #include <anna/timex/Engine.hpp>
52 #include <anna/timex/internal/TickConsumer.hpp>
53 #include <anna/timex/internal/TickProducer.hpp>
54 #include <anna/timex/TimeEventObserver.hpp>
55 #include <anna/timex/internal/sccs.hpp>
59 using namespace anna::comm;
62 Resolucion minima (en milisegundos) soportada por el controlador de tiempos.
65 const Millisecond anna::timex::Engine::minResolution(100);
68 anna::timex::Engine::Engine(const Millisecond & maxTimeout, const Millisecond & resolution) :
69 app::Component(getClassName()),
72 a_maxTimeout(maxTimeout),
73 a_resolution(resolution),
77 a_expiredQuantum(NULL),
79 timex::sccs::activate();
82 anna::timex::Engine::~Engine() {
83 delete a_tickConsumer;
84 delete [] a_timeTable;
87 //--------------------------------------------------------------------------------------------
88 // Inicializa la configuracin.
90 // (2) En Solaris el ualarm tiene una deriva constante que parece ser que dependen de la
91 // arquitectura de la m�uina. Para calcular esta deriva y poder corregirla en los
92 // posteriores ualarm vamos a calcular la diferencia entre el tiempo esperado y el momento
93 // en que realmente llega la seal de ualarm.
94 //--------------------------------------------------------------------------------------------
95 void anna::timex::Engine::do_initialize()
96 throw(RuntimeException) {
97 LOGMETHOD(TraceMethod tm("timex::Engine", "do_initialize", ANNA_FILE_LOCATION));
99 if(a_maxQuantum > 0) {
100 Logger::write(Logger::Warning, "Time controller was previously started", ANNA_FILE_LOCATION);
104 if(a_resolution < minResolution)
105 throw RuntimeException(functions::asString("Resolution must be greater than %d milliseconds", minResolution.getValue()), ANNA_FILE_LOCATION);
107 if(a_maxTimeout <= a_resolution)
108 throw RuntimeException(functions::asString("Max-Timeout must be greater than %d milliseconds", a_resolution.getValue()), ANNA_FILE_LOCATION);
110 a_maxQuantum = a_maxTimeout / a_resolution;
112 while((a_maxQuantum * a_resolution) <= a_maxTimeout) a_maxQuantum ++;
114 a_timeTable = new Quantum [a_maxQuantum];
115 Communicator* communicator = app::functions::component <Communicator> (ANNA_FILE_LOCATION);
116 communicator->attach(a_tickConsumer = new TickConsumer(this));
118 * Esto siempre se ejecutará en un thread aparte aunque la librería haya sido generada en modo ST,
119 * porque así evitamos tener que generar el pulso de reloj mediante una alarma.
121 a_tickProducer = new TickProducer(this, a_tickConsumer->getfdWrite());
123 pthread_attr_init(&attr);
126 if((errorCode = pthread_create(&a_threadProducer, &attr, TickProducer::exec, a_tickProducer)) != 0)
127 throw RuntimeException(std::string("timex::Engine::do_initialize"), errorCode, ANNA_FILE_LOCATION);
130 string msg("Time controller | Max Timeout: ");
131 msg += functions::asString(a_maxTimeout);
132 msg += " | Resolution: ";
133 msg += functions::asString(a_resolution);
134 msg += " | Max Quantum: ";
135 msg += functions::asString(a_maxQuantum);
137 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
141 // Reimplementado de app::Component
142 void anna::timex::Engine::do_cloneParent()
147 * Se invoca desde app::Application::clone -> app::Component::do_cloneChild (ojo EN EL NUEVO PROCESO).
148 * Instala la senhal de tick en el proceso, ya que la alarma no se hereda directamente.
150 void anna::timex::Engine::do_cloneChild()
151 throw(RuntimeException) {
154 //----------------------------------------------------------------------------
155 // No para los hilos de generacion, sino que evita que se escriban los bytes
157 //----------------------------------------------------------------------------
158 void anna::timex::Engine::pause()
159 throw(RuntimeException) {
160 Guard guard(this, "timex::Engine (pause)");
162 if(a_tickProducer->isInPause() == false) {
164 string msg("timex::Engine::pause | Pending TimeEvents: ");
165 msg += functions::asString((const int) a_directory.size());
166 Logger::warning(msg, ANNA_FILE_LOCATION);
168 a_tickProducer->setIsInPause(true);
172 void anna::timex::Engine::play()
173 throw(RuntimeException) {
174 Guard guard(this, "timex::Engine (play)");
176 if(a_tickProducer->isInPause() == true) {
178 string msg("timex::Engine::play | Pending TimeEvents: ");
179 msg += functions::asString((const int) a_directory.size());
180 Logger::warning(msg, ANNA_FILE_LOCATION);
182 a_tickProducer->setIsInPause(false);
186 void anna::timex::Engine::activate(timex::TimeEvent* timeEvent)
187 throw(RuntimeException) {
188 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "activate", ANNA_FILE_LOCATION));
190 if(a_maxQuantum == 0)
191 throw RuntimeException("Engine::initialize was not called", ANNA_FILE_LOCATION);
193 if(timeEvent == NULL)
194 throw RuntimeException("Cannot activate a NULL TimeEvent", ANNA_FILE_LOCATION);
196 if(timeEvent->a_controller != NULL) {
197 string msg(timeEvent->asString());
198 msg += " | Already activated";
199 throw RuntimeException(msg, ANNA_FILE_LOCATION);
202 const Millisecond & timeout(timeEvent->getTimeout());
204 if(timeout > a_maxTimeout || timeout < a_resolution) {
205 string msg("Invalid TimeEvent timeout | Max Timeout: ");
206 msg += functions::asString(a_maxTimeout);
207 msg += " | Min timeout (resolution): ";
208 msg += functions::asString(a_resolution);
210 msg += timeEvent->asString();
211 throw RuntimeException(msg, ANNA_FILE_LOCATION);
214 Guard guard(this, "timex::Engine (activate)");
216 if(a_tickProducer->isInPause() == true) {
217 string msg("Cannot activate TimeEvents with timex::Engine in pause | ");
218 msg += timeEvent->asString();
219 throw RuntimeException(msg, ANNA_FILE_LOCATION);
222 const TimeEvent::Id id(timeEvent->getId());
224 if(a_directory.find(id) != a_directory.end())
225 throw RuntimeException(functions::asString("Id %ld (0x%x) already in use", id, id), ANNA_FILE_LOCATION);
227 int iq = getQuantum(timeout);
228 Quantum* quantum = &a_timeTable [iq];
230 timeEvent->a_controller = this;
231 a_directory.insert(Directory::value_type(id, quantum));
233 if(quantum == a_expiredQuantum) {
234 a_delayedQuantum.push_back(timeEvent);
237 quantum->push_back(timeEvent);
242 string msg("timex::Engine::activate | ");
243 msg += timeEvent->asString();
244 msg += " | Current quantum: ";
245 msg += functions::asString(a_currentQuantum);
246 msg += " | Quantum programmed: ";
247 msg += functions::asString(iq);
249 msg += functions::asHexString(anna_ptrnumber_cast(quantum));
250 msg += functions::asText(") | Delayed: ", delayed);
251 Logger::debug(msg, ANNA_FILE_LOCATION);
255 anna::timex::TimeEvent* anna::timex::Engine::getTimeEvent(const timex::TimeEvent::Id eventTimeId)
257 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "getTimeEvent", ANNA_FILE_LOCATION));
258 Directory::iterator iid;
259 TimeEvent* result(NULL);
262 Guard guard(this, "timex::Engine (getTimeEvent)");
264 if((iid = a_directory.find(eventTimeId)) == a_directory.end())
267 quantum = iid->second;
269 for(Quantum::iterator ii = quantum->begin(), maxii = quantum->end(); ii != maxii; ii ++) {
270 if((aux = *ii)->getId() == eventTimeId) {
279 void anna::timex::Engine::cancel(timex::TimeEvent* timeEvent)
280 throw(RuntimeException) {
281 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "cancel", ANNA_FILE_LOCATION));
283 if(timeEvent == NULL)
284 throw RuntimeException("Cannot cancel a NULL TimeEvent", ANNA_FILE_LOCATION);
286 if(timeEvent->a_controller == NULL) {
288 string msg(timeEvent->asString());
289 msg += " | Not activated";
290 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
295 Directory::iterator iid;
297 Guard guard(this, "timex::Engine (cancel)");
299 if((iid = a_directory.find(timeEvent->getId())) == a_directory.end())
302 if((quantum = iid->second) == a_expiredQuantum) {
304 string msg("timex::Engine::cancel | ");
305 msg += timeEvent->asString();
306 msg += " | Processing already programmed";
307 Logger::warning(msg, ANNA_FILE_LOCATION);
312 a_directory.erase(iid);
313 quantum->erase(find(quantum->begin(), quantum->end(), timeEvent));
314 timeEvent->a_controller = NULL;
316 string msg("timex::Engine::cancel | ");
317 msg += timeEvent->asString();
318 msg += " | Current quantum: ";
319 msg += functions::asString(a_currentQuantum);
320 msg += " | Quantum programmed: ";
321 msg += functions::asHexString(anna_ptrnumber_cast(quantum));
322 Logger::debug(msg, ANNA_FILE_LOCATION);
324 notifyRelease(timeEvent);
327 void anna::timex::Engine::do_stop()
329 LOGMETHOD(TraceMethod tm("timex::Engine", "do_stop", ANNA_FILE_LOCATION));
330 Quantum::iterator ii, maxii;
331 Guard guard(this, "timex::Engine (do_stop)");
332 app::functions::component <Communicator> (ANNA_FILE_LOCATION)->detach(a_tickConsumer);
335 a_tickProducer->requestStop();
338 TimeEvent* timeEvent;
340 for(int iq = 0; iq < a_maxQuantum; iq ++) {
341 Quantum& quantum(a_timeTable [iq]);
343 for(ii = quantum.begin(), maxii = quantum.end(); ii != maxii; ii ++) {
344 timeEvent = Engine::timeEvent(ii);
347 timeEvent->a_controller = NULL;
349 notifyRelease(timeEvent);
350 } catch(Exception& ex) {
359 void anna::timex::Engine::kill()
361 Guard guard(this, "timex::Engine (kill)");
362 app::functions::component <Communicator> (ANNA_FILE_LOCATION)->detach(a_tickConsumer);
365 a_tickProducer->requestStop();
367 string msg("timex::Engine::kill | ");
368 msg += functions::asString(" | Pending TimeEvents: %d", a_directory.size());
369 Logger::critical(msg, ANNA_FILE_LOCATION);
371 for(int iq = 0; iq < a_maxQuantum; iq ++)
372 a_timeTable [iq].clear();
374 a_delayedQuantum.clear();
378 //----------------------------------------------------------------------------------
379 // (1) Podr� ser que al invocar al m�odo 'expire' de una evento se halla originado
380 // la cancelacin de algn otro evento. As�que si el id de evento ya no esta en
381 // en el directorio => da igual, se ignora ya que luego borraremos todos los
382 // eventos del Quantum.
383 // (2) Si mientas estamos caducando este Quantum hay que activar transacciones que
384 // debe ir sobre el => se guardan en un vector temporal y se copian al final
385 // del proceso, con lo que evita totalmente la posiblidad de perdida.
386 // (3) Si el temporizador ha sido reactivado no tiene que liberarlo.
387 //----------------------------------------------------------------------------------
388 void anna::timex::Engine::tick()
389 throw(RuntimeException) {
390 LOGMETHOD(TraceMethod tm(Logger::Local7, "timex::Engine", "tick", ANNA_FILE_LOCATION));
392 if (Logger::isActive (Logger::Local6) == true && a_directory.size () > 0) {
393 string msg ("Tick | Quantum: ");
394 msg += functions::asString (a_currentQuantum);
396 msg += functions::asString (functions::millisecond ());
398 msg += functions::asString (a_directory.size ());
399 Logger::write (Logger::Local6, msg, ANNA_FILE_LOCATION);
402 Guard guard(this, "timex::Engine (tick)");
405 a_expiredQuantum = &a_timeTable [a_currentQuantum];
406 TimeEvent* timeEvent;
407 Directory::iterator iid;
408 quantum_iterator ii, maxii;
411 for(ii = a_expiredQuantum->begin(), maxii = a_expiredQuantum->end(); ii != maxii; ii ++) {
412 timeEvent = Engine::timeEvent(ii);
414 if((iid = a_directory.find(timeEvent->getId())) == a_directory.end()) // (1)
417 a_directory.erase(iid);
419 if(Logger::isActive(Logger::Debug)) {
420 string msg(timeEvent->asString());
421 msg += " | Quantum programmed: ";
422 msg += functions::asHexString(anna_ptrnumber_cast(a_expiredQuantum));
424 Logger::write(Logger::Debug, msg, ANNA_FILE_LOCATION);
427 timeEvent->a_controller = NULL;
429 * Si tiene NO tiene observer asociado, seguramente se auto-liberará en el expire
430 * Si tiene observer, se invocará al método correspondiente.
432 observed = (timeEvent->a_observer != NULL);
435 timeEvent->expire(this);
437 } catch(Exception& ex) {
441 // Si tiene observer => el expire NO puede haber liberado el TimeEvent
442 if(observed == true) {
443 if(timeEvent->a_controller == NULL) // (3)
444 timeEvent->a_observer->release(timeEvent);
448 a_expiredQuantum->clear();
450 if(a_delayedQuantum.size() > 0) { // (2)
451 for(ii = a_delayedQuantum.begin(), maxii = a_delayedQuantum.end(); ii != maxii; ii ++)
452 a_expiredQuantum->push_back(Engine::timeEvent(ii));
454 a_delayedQuantum.clear();
457 a_expiredQuantum = NULL;
459 if(++ a_currentQuantum >= a_maxQuantum)
460 a_currentQuantum = 0;
462 eventEndQuantum(counter);
465 void anna::timex::Engine::notifyRelease(timex::TimeEvent* timeEvent)
467 if(timeEvent->a_observer != NULL)
468 timeEvent->a_observer->release(timeEvent);
471 string anna::timex::Engine::asString() const
473 string msg("timex::Engine { ");
474 msg += app::Component::asString();
475 msg += " | Max Timeout: ";
476 msg += functions::asString(a_maxTimeout);
477 msg += " ms | Resolution: ";
478 msg += functions::asString(a_resolution);
479 msg += " ms | Programming: ";
480 msg += functions::asString(a_maxQuantum);
481 msg += " quantums | Pending TimeEvents: ";
482 msg += functions::asString((const int) a_directory.size());
486 xml::Node* anna::timex::Engine::asXML(xml::Node* parent) const
488 parent = Component::asXML(parent);
489 xml::Node* result = parent->createChild("timex.Engine");
490 result->createAttribute("MaxTimeout", a_maxTimeout);
491 result->createAttribute("Resolution", a_resolution);
492 result->createAttribute("N", a_directory.size());
493 result->createAttribute("Pause", functions::asString(a_tickProducer->isInPause()));
494 xml::Node* node = result->createChild("Quantum");
495 node->createAttribute("Current", a_currentQuantum);
496 node->createAttribute("Max", a_maxQuantum);