Making sure a deleted task's OS/clib resources have been freed

@richard-damon - If I understand correctly, whether IO is task-specific is controlled by a newlib build option (see below). The copy below is from an NXP project. Rob’s setup has the same undefined control.

/* Define to move the stdio stream FILE objects out of struct _reent and make
   them global.  The stdio stream pointers of struct _reent are initialized to
   point to the global stdio FILE stream objects. */

The double negatives are hurting my brain.

If _WANT_REENT_GLOBAL_STDIO_STREAMS is not "#undef"ed (and of course assuming ST didn’t define it somewhere else), then does that mean that by default each task, under newlib, has it’s own IO stream? Seems a bit pointless in a tiny embedded system where they will all fall over each other as they leave the UART/SWV/RTT whatever anyway.

But even if there is a point, Shirley deleting said buffer when the task exits is a kind of obvious thing to do, unless this harks back to the “what’s a thread?” days of newlib?

I believe so.
Note these defines are created and used during the newlib build…

To close this one off, having spent a day working through all my tests compensating for the 1468/1032 byte leak in the STM32F4/newlib case, I’ve just run the same tests on NRF52/newlib and they show no such leakage.

I conclude from this that the problem lies with however ST built their newlib, so nothing for you guys to worry about at all; another ST gotcha to note @dnadler.

Thanks again for all your remarkably swift support.

Rob - Can you check if the NXP newlib.h defines _WANT_REENT_GLOBAL_STDIO_STREAMS ??

It’s using GNU Tools ARM Embedded (9 2019-q4-major), newlib.h and _newlib_version.h atttached (2.1 KB).

_WANT_REENT_GLOBAL_STDIO_STREAMS is not being undefined so if the #define is being generated by the newlib build process it is probably being defined. If you see what I mean… :-).

I just posted a new thread ( ht tps:// memory-leak-using-snprintf-with-floats/13259/2 )and was directed here.

I think I’m having the same issue, and was hoping that someone may have a resolution nearly a year later.

Does it make more sense to switch to this thread, or stick with my own?

I can confirm that the issue goes away if I change #define configUSE_NEWLIB_REENTRANT from 1 to 0, but I think that may have other undesired consequences, and isn’t a fix.

We looked into this a bit and found that when newlib is compiled with _LITE_EXIT (which is the case of the newlib binary provided by STMCubeIDE) the stdio streams are not closed when calling _reclaim_reent(). So if newlib was configured in such a way that it will dynamically allocate memory for the stdio streams, this memory will leak (this will happen when _REENT_SMALL is enabled, _REENT_GLOBAL_STDIO_STREAMS NOT enabled and _UNBUF_STREAM_OPT NOT enabled - maybe some more config that could affect dynamic allocation as well). So if you got this config you should be able to remove the memory leak by just closing the stdio streams when the FreeRTOS task is deleted. In our porting layer we have added a workaround when deleting a task that looks like this:

#if defined(__NEWLIB__) && defined(_LITE_EXIT) && defined(_REENT_SMALL) && \
    if (stdout) {
    if (stderr) {
    if (stdin) {

But there is another thing that should be mentioned regarding STM32 newlib. With the newlib config above, FILE pointers (among other things used by the stdio streams) will be placed in a dynamically allocated global pool. If there are no FILE pointer currently free in this pool, newlib will allocate a new one. This means that the first time you call, for instance, printf() from a task newlib will likely allocate some FILE pointers. When you later on delete the FreeRTOS task these FILE pointers will not be deallocated, instead they are marked as “free” (that is if you call fclose like the suggested patch) so that they can be re-used for some other task. I.e. this might look like a memory leak, but if you then add a second task and call printf() you will see that this time newlib will reuse the FILE pointers created by the first task and no new memory should be allocated.

1 Like

Great information - thanks for taking the time to post.