Patrón Reactor

123456
EspecificaciónModeladoVerificaciónImplementaciónVerificaciónAnálisis
PARA QUÉQUÉCÓMO
¿Cuál es el problema?¿Qué tiene que hacer el sistema?¿El modelo cumple la especificación?¿Cómo hace lo que tiene que hacer? (modelo refinado)¿La implementación cumple la especificación?¿Se cumplen los plazos en el caso peor?
LTL, CTLFSM / xFSMnuXmvfsm.h / fsm.cnuXmvAnálisis del tiempo
(model checking)Ejecutivo cíclico / FreeRTOS / Reactor(model checking)de respuesta

Para empezar
Lee los artículos de Douglas C. Schmidt sobre el patrón parte 1 parte2

Ejemplo de implementación
Descarga la implementación mínima de ejemplo con ESP32.

El patrón Reactor es una forma de implementar sistemas reactivos desacoplando las tareas. En principio el patrón no exige que la implementación utilice multitarea cooperativa, pero lo facilita enormemente. Nosotros lo utilizaremos como una forma de utilizar multitarea cooperativa sin renunciar a la multitarea con prioridades fijas y desalojo.

El reactor en FreeRTOS

En esta asignatura utilizaremos una implementación mínima, donde solo se consideran eventos temporales. No es difícil extenderla para incluir otro tipo de eventos empleando notificaciones de FreeRTOS desde los propios manejadores de interrupción, pero no lo haremos para mantener la implementación lo más legible posible.

La interfaz corresponde fielmente a la idea general del patrón Reactor:

reactor.h
1#ifndef REACTOR_H
2#define REACTOR_H
3
4#include <freertos/FreeRTOS.h>
5#include <freertos/task.h>
6
7struct event_handler_t;
8typedef void (*eh_func_t) (struct event_handler_t*);
9
10struct event_handler_t {
11  int prio;
12  TickType_t next_activation;
13  eh_func_t run;
14};
15typedef struct event_handler_t EventHandler;
16
17void event_handler_init (EventHandler* eh, int prio, eh_func_t run);
18void event_handler_run (EventHandler* eh);
19
20void reactor_init (void);
21void reactor_add_handler (EventHandler* eh);
22void reactor_handle_events (void);
23
24#endif

Ejemplo de utilización

Veamos un ejemplo muy sencillo. Definiremos una tarea periódica que en este caso simplemente cambia el estado de una salida digital, para provocar el parpadeo de una baliza. Esta tarea se incluirá en un Reactor. Es decir, se ejecutará de forma cooperativa. Las tareas en un Reactor se modelan como event handlers. Un event handler tiene una función para procesar los eventos de esa tarea y una prioridad. Por ejemplo, para añadir una tarea baliza con prioridad 4 en el reactor utilizaremos:

1EventHandler eh1;
2event_handler_init (&eh1, 4, baliza);

La función baliza podría implementarse como sigue:

1void baliza (EventHandler* self)
2{
3    static TickType_t period = pdMS_TO_TICKS(500);
4    static int value = 0;
5
6    value = !value;
7    gpio_set_level(BALIZA, value);
8    if (value) printf (".\n");
9    
10    self->next_activation += period;
11}

Nótese que la función recibe un puntero a la estructura EventHandler. Esto se puede usar para almacenar datos compartidos como hacemos en las máquinas de estados extendidas.

El Reactor puede quedarse en la tarea principal (app_main) o puede crearse en otra tarea de FreeRTOS se crea mediante una llamada a xTaskCreate pasando un puntero a una función como ésta:

1void reactor_run (void* ignore)
2{
3    EventHandler eh1;
4    reactor_init ();
5
6    event_handler_init (&eh1, 1, baliza);
7    reactor_add_handler (&eh1);
8
9    for (;;) {
10        reactor_handle_events ();
11    }
12}

Código completo de la implementación

main.c
1#include <driver/gpio.h>
2#include <stdio.h>
3#include "reactor.h"
4
5#define BALIZA 8
6
7static void
8baliza (struct event_handler_t* this)
9{
10    static TickType_t period = pdMS_TO_TICKS(500);
11    static int value = 0;
12
13    value = !value;
14    gpio_set_level(BALIZA, value);
15    if (value) printf (".\n");
16    
17    this->next_activation += period;
18}
19
20static void
21reactor_run (void* ignore)
22{
23    EventHandler eh1;
24    reactor_init ();
25
26    event_handler_init (&eh1, 1, baliza);
27    reactor_add_handler (&eh1);
28
29    for (;;) {
30        reactor_handle_events ();
31    }
32}
33
34void app_main() 
35{
36    gpio_set_direction(BALIZA, GPIO_MODE_OUTPUT);
37
38    TaskHandle_t task;
39    xTaskCreate (reactor_run, "reactor", 2048, NULL, 1, &task);
40}

Detalles de implementación

La implementación es deliberadamente simple para eliminar la complejidad relativa al manejo de varias fuentes de eventos.

reactor.c
1#include "reactor.h"
2
3typedef struct reactor_t {
4  EventHandler* ehs[10];
5  int n_ehs;
6} Reactor;
7
8static Reactor r;
9
10void
11event_handler_init (EventHandler* eh, int prio, eh_func_t run)
12{
13  eh->prio = prio;
14  eh->next_activation = xTaskGetTickCount();
15  eh->run = run;
16}
17
18void
19event_handler_run (EventHandler* eh)
20{
21  eh->run (eh);
22}
23
24void
25reactor_init (void)
26{
27  r.n_ehs = 0;
28}
29
30int
31compare_prio (const void* a, const void* b)
32{
33  EventHandler* eh1 = *(EventHandler**) a;
34  EventHandler* eh2 = *(EventHandler**) b;
35  if (eh1->prio > eh2->prio)
36    return -1;
37  if (eh1->prio < eh2->prio)
38    return 1;
39  return 0;
40}
41
42void
43reactor_add_handler (EventHandler* eh)
44{
45  r.ehs[r.n_ehs++] = eh;
46  qsort (r.ehs, r.n_ehs, sizeof (EventHandler*), compare_prio);
47}
48
49static TickType_t
50reactor_next_timeout (void)
51{
52  static TickType_t next;
53  TickType_t now = xTaskGetTickCount();
54  int i;
55
56  next = now + pdMS_TO_TICKS(60*60*1000);
57  if (! r.n_ehs) return next;
58
59  for (i = 0; i < r.n_ehs; ++i) {
60    EventHandler* eh = r.ehs[i];
61    if (eh->next_activation < next)
62      next = eh->next_activation;
63  }
64  if (next < now)
65    next = now;
66  return next;
67}
68
69void
70reactor_handle_events (void)
71{
72  int i;
73  TickType_t timeout, now;
74  TickType_t next_activation = reactor_next_timeout();
75
76  now = xTaskGetTickCount();
77  timeout = next_activation - now;
78  if (timeout > 0)
79    vTaskDelay (timeout);
80
81  now = xTaskGetTickCount();
82  for (i = 0; i < r.n_ehs; ++i) {
83    EventHandler* eh = r.ehs[i];
84    if (eh->next_activation < now) {
85      event_handler_run (eh);
86    }
87  }
88}