@h4yn0nnym0u5e would you then advocate for having a user call SomeLibrary.loop() inside their main program loop, plus anywhere they’d wait, instead of hooking into yield (whatever the mechanism)? (Eg. QNEthernet)
AudioMemory()
is a case in point).SomeLibrary::poll()
(I've quietly renamed it and made it a static method there...) instead of linking into the EventResponder yield() list is probably the way to go. I'll try it out in the audio library branches I'm tinkering with to see how it goes. But I would 100% be open to other approaches, especially if they make the user's life simpler.SomeLibrary::usePolling()
in setup()
, and SomeLibrary::poll()
in loop()
...Clearly if you're creating/using a co-operative OS you know to keep to the "one task, one piece of hardware" rule, so you're OK. I guess you could get into issues if you use QNEthernet with SPI-based Ethernet hardware plus, say, a SPI display, because then you'd have to know SPI usesyield()
(which the naïve user doesn't because it's not documented). And then your task has to run both the display and Ethernet, which is messy.
If you're a library writer wanting to take advantage of facilities built intocores
, then limitations need to be documented much better than they currently are.EventResponder
is nearly 6 years old, yet still documented as "an experimental API". The documentation forattach()
states "Calls from yield() allow use of Arduino libraries, String, Serial, etc." - as I've discovered, this is simply not true in the general case: you can't safely use serial, Wire, SPI, SD, ADC, USB ...
I'm still not sure what would be a minimal, useful, non-breaking set of changes to vanilla Teensyduino (putting slightly aside super-users like @joepasquariello, while still trying to make sure their existing codebase is unlikely to break - I would never advocate that!).
I think the issue is that libraries can do this, not necessarily user code, meaning there’s less control. Your point stands, however, for library authors, especially; they need to take extra care and add extra documentation.I don't think Teensyduino requires any changes at all because as it is, it works as designed.
The problem in this case seems to lie with hooking extra things into yield via EventResponder without appropriate safety checks. If you're going to do that you have to be prepared for the consequences, similar to accessing a non-threadsafe resource from two separate threads - hence why EventResponder can be extended to add any extra functionality required.
I think the issue is that libraries can do this, not necessarily user code, meaning there’s less control. Your point stands, however, for library authors, especially; they need to take extra care and add extra documentation.
My view of yield() is that it could be called at any time from anywhere, including multiple times. Or were you referring to yield() calls being called in a reentrant manner, I.e. someone calls yield(), and then somewhere along the line, inside that yield() call, another yield() call is made?Yes, I agree with you and @jmarsh, the responsibility is on the programmer, but if EventResponder was not called from yield(), at least the users would not face the issue of calls to yield from additional places each time they add the use of another library that uses yield().
I mean it's the responsibility of whoever is writing the EventResponders that are getting hooked in. Library authors shouldn't need to document whether they call yield() or not (they may not even be aware of it if it happens in some platform dependent code).I think the issue is that libraries can do this, not necessarily user code, meaning there’s less control. Your point stands, however, for library authors, especially; they need to take extra care and add extra documentation.
That doesn't make sense. If a library uses yield(), surely that counts as an additional place that it can be called from.if EventResponder was not called from yield(), at least the users would not face the issue of calls to yield from additional places each time they add the use of another library that uses yield().
That case is harmless since there's a static flag used to detect recursive calls.Or were you referring to yield() calls being called in a reentrant manner, I.e. someone calls yield(), and then somewhere along the line, inside that yield() call, another yield() call is made?
That case is harmless since there's a static flag used to detect recursive calls.
Yes, I was referring to EventResponders, not uses of yield().I mean it's the responsibility of whoever is writing the EventResponders that are getting hooked in. Library authors shouldn't need to document whether they call yield() or not (they may not even be aware of it if it happens in some platform dependent code).
Of course. But if multiple external devices use a single SPI peripheral via libraries which deeply embed the peripheral access, how do you propose to keep those from tripping over one another? If one yields, and does not provide information that the SPI peripheral is busy, the other may try to execute and cause mayhem.The hardware resource that is shared and requires protection is the SPI peripheral, not the external devices.
And I'd be more than happy to do so. But there is no documentation, and no information provided by the yield call as to whether, in any specific instance, I need to temper what I do. The net result is I cannot use EventResponder yield mode at all, in any circumstances other than the very simplest i.e. tell the user of my library that they don't have SD card access any more.Calls to yield() are found in all Arduino cores and many libraries, and that fact has to temper what you do if you override it with something else, such as EventResponder.
I'm trying very hard not to advocate any breaking changes. I think that's true of the changes I outlined in post #50, but happy to be challenged on that point. I am also happy to extend EventResponder to add an option for a trigger response function that's explicitly called from loop().This would be a breaking change, but I think it's the right thing to do. Modify EventResponder to not override yield(), and instead just have a function that must be called from loop().
And as a writer of an EventResponder-hooked library, I'm happy to take on the responsibility. But why on earth are other library writers absolved of the responsibility to document the code they write? If they use yield() they should document it, along with any other library dependencies so at least if there's platform-dependent code the user can eventually trace the full dependency tree.I mean it's the responsibility of whoever is writing the EventResponders that are getting hooked in. Library authors shouldn't need to document whether they call yield() or not (they may not even be aware of it if it happens in some platform dependent code).
Indeed. And as things stand, we have to deal with that. But is it so unreasonable to hope that non-breaking improvements can be made such that anything that hooks yield() can at least get some information as to the state of the system when the hook code is entered? It can then decide either to execute or wait for the next opportunity.That doesn't make sense. If a library uses yield(), surely that counts as an additional place that it can be called from.
Even delay() calls yield(), so it really is necessary to assume it can be called from anywhere at any time.
Sorry, forgot I was going to add this to my response...The problem in this case seems to lie with hooking extra things into yield via EventResponder without appropriate safety checks. If you're going to do that you have to be prepared for the consequences, similar to accessing a non-threadsafe resource from two separate threads - hence why EventResponder can be extended to add any extra functionality required.
default_yield_from(YieldSource::Loop)
, and delay() which calls default_yield_from(YieldSource::Delay)
. EventResponder won't be called quite as often, but no-one should be depending on any specific time-frame anyway.No. That is a misconception. yield() is a common OS-style API function that is free to be called by any code, and anything connected to an EventResponder should be treated with the same respect as an ISR - in fact this would probably be more "safe" since there's no guarantee yield() will actually get called between audio updates (how would more audio data be loaded in the case that the main thread is busy inside a large loop?), although obviously that will still suffer the same problem if the user is accessing the SD card outside the ISR.My library currently doesn't keep a list of them, because I didn't foresee the need to do so - it only hooks into yield(), after all, and that only happens at loop() end, delay() and calling yield() directly, right?
No. That is a misconception. yield() is a common OS-style API function that is free to be called by any code, and anything connected to an EventResponder should be treated with the same respect as an ISR - in fact this would probably be more "safe" since there's no guarantee yield() will actually get called between audio updates (how would more audio data be loaded in the case that the main thread is busy inside a large loop?), although obviously that will still suffer the same problem if the user is accessing the SD card outside the ISR.
With regards to masking all of your EventResponders - all you need to do is put a single static variable in your derived class and toggle it on and off to notify all of the derived objects that the SD library is currently unusable.
QNETHERNET_DO_LOOP_IN_YIELD
configuration macro. I'd love all'y'all's thoughts on it, including what's in the README. Suggestions and improvements welcome. (Simply search the source for that word.) (@jmarsh, @h4yn0nnym0u5e, @joepasquariello)As noted by @shawn, you’ve edited what I said in a slightly misleading manner. I did think that, I now know better…No. That is a misconception. yield() is a common OS-style API function that is free to be called by any code, and anything connected to an EventResponder should be treated with the same respect as an ISR - in fact this would probably be more "safe" since there's no guarantee yield() will actually get called between audio updates (how would more audio data be loaded in the case that the main thread is busy inside a large loop?), although obviously that will still suffer the same problem if the user is accessing the SD card outside the ISR.
With regards to masking all of your EventResponders - all you need to do is put a single static variable in your derived class and toggle it on and off to notify all of the derived objects that the SD library is currently unusable.
yield()
acts as the ISR return. The EventResponder triggers are like lower priority ISR flags which only get serviced on yield()
. Either way, access to the card is happening at two levels, and this is a no-no (although the audio library has been doing exactly that for ages…).loop()
will starve the streaming audio. I documented the fact, and not ideal. You can sprinkle yield()
calls around if you must, or let loop()
exit regularly.yield()
will execute them. No flag in EventResponder to prevent that.However … there is a flag to preventOh, for the simplicity of a static flag to say “SD unsafe”! By the time you set it, events are already pending (set in the audio update ISR), andyield()
will execute them. No flag in EventResponder to prevent that.
yield()
from running EventResponder. It’s probably not intended for libraries to use directly, and blocking all response execution is definitely overkill, like putting in a critical section when there’s only one other task you don’t want scheduled. But having that option while the experimental EventResponder API is being improved is … worth considering.