Reactduino / Reactduino

An asynchronous programming library for creating non-blocking applications on the Arduino platform.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

delayed tasks should be freed instead of disabled when done

EricPobot opened this issue · comments

Since there is no mean to re-activate an executed delayed task, it should be freed instead of simply disabled (Reactduino.cpp:54) when executing its callback.

Otherwise, if not done by the app, this can lead to exhausting the maximum number of tasks if repeatedly using delay(), since expired ones are left in the tasks table, consuming slots without need.

commented

i have same problem, how to solve it?

Although I opened the issue, maybe the current strategy is not that bad after all. It allows to reuse the same reaction to restart a new delay, without having the overhead of reallocating a new one, which could potentially fail if there is no slot left. Recycling the same delay reaction requires updating its param1 and param2 fields, and switching it to the enabled state when the delay needs to be restarted. Adding a couple of helper methods would be nice to wrap this operation in a friendly way.

Anyway, if you prefer disabling it, the code is quite simple to modify, so you can fork the repository and create a version customized to your need. You can even imagine adding an option when creating the reaction, telling which strategy to use when the delay is elapsed.

Another problem with changing to make it auto-free at the moment is that it would break backwards compatibility.

If it was changed to auto-free and someone upgraded their existing code, they would then potentially end up accidentally free'ing the wrong reactions (if new ones were allocated between the delay ending and the additional free occurring).

I probably will make this change for a later version, but I think I probably need a bit more usage and feedback on the current API - then we can bundle a series of these sorts of changes into one release with an upgrade guide.

Hi Andrew,

Your comment makes sense.

As suggested in my last message, maybe the auto-free is not needed if some convenience API would permit reusing a delay reaction, with the possibility to modify the timeout value or simply restart the same delay again.

I'll give it a look as soon as I could find some time for it. I confess not having done Arduino development since a while, be it with Reactduino or without ;)

I suppose that might worth like this?

reaction app.delay(uint32_t t, react_callback cb);

void app.redelay(uint32_t t, reaction r);

I think this could work, my only concern would be that it means your first delay has to be initialised differently to all your future delays. Which might make the API a bit awkward to use? I think it would probably be useful to consider how a real world example might look in code.

The use case I can think about is reusing the same delay for some other operation or a delay with different settings.

As far as I remember, once a delay reaction is expired, it stays there in a disabled state. There is no way to make it useful again, since re-enabling it will have no effect, the start time not being updated. Thus the only possible scenario is to free the reaction and to create a new one. This was the motivation of suggesting to free it anyway since it could not be reused as-is.

Maybe I'm wrong and missed a point somewhere.

One option would be to add a restart method, appliable to delay reactions only, and which would take care of updating the start time param and enabling the reaction back.

I think what I mean to say, is that it might be a bit clunky from an API perspective.

If we have an example program that blinks an LED after a button is pressed. The example below would show the code with "auto-free":

#include <Reactduino.h>

#define BUTTON 10

Reactduino app([] () {
  pinMode(BUTTON, INPUT);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  app.onPinRising(BUTTON, [] () {
    digitalWrite(LED_BUILTIN, HIGH);

    app.delay(1000, [] () {
      digitalWrite(LED_BUILTIN, LOW);
    ]);
  });
});

With "redelay", the code would look like this:

#include <Reactduino.h>

#define BUTTON 10

Reactduino app([] () {
  pinMode(BUTTON, INPUT);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  app.onPinRising(BUTTON, [] () {
    static reaction led_off = INVALID_REACTION;

    digitalWrite(LED_BUILTIN, HIGH);

    if (led_off == INVALID_REACTION) {
      led_off = app.delay(1000, [] () {
        digitalWrite(LED_BUILTIN, LOW);
      ]);
    } else {
      app.redelay(1000, led_off);
    }
  });
});

I'm a bit torn. As the latter example would be more efficient (though I'm not sure how much performance difference), but I feel like the first example is more expressive and simpler.

Actually, thinking about the above example in a bit more detail - I don't think we can offer "auto-free" at all.

The problem arises because if Reactduino free's a reaction internally, this makes it unsafe for a user to enable() or disable() that reaction. For example, if a user tried to disable a delay - they would need to guarantee that the delay had not been triggered otherwise they might accidentally disable a new reaction that they had not intended to.

I'm tending now towards the redelay approach.

I agree with the fact that "redelay" is a bit unnatural.

Since there is no room for securing reaction ids (they are simple indices in the table) there is a risk of applying a method to a different one of a different type. But it should be the same is you free a reaction explicitly and add a new one afterward that could reuse the same slot, hence the same id.

As I suggested, what if delay reactions didn't exist anymore as explicit ones, but were instead managed internally as the processing of a couple of delay dedicated functions? One can imagine a separate reaction list reserved for them (it can be limited to just a few like the timers are), and both lists are chained when looking for reactions to be executed?