Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mail/MemoryPool: blocking alloc #10225

Merged
merged 2 commits into from Apr 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 80 additions & 0 deletions TESTS/mbedmicro-rtos-mbed/MemoryPool/main.cpp
Expand Up @@ -20,6 +20,9 @@

using namespace utest::v1;

#define THREAD_STACK_SIZE 512
#define TEST_TIMEOUT 50

/* Enum used to select block allocation method. */
typedef enum {
ALLOC, CALLOC
Expand Down Expand Up @@ -450,6 +453,80 @@ void test_mem_pool_free_realloc_first_complex(AllocType atype)
}
}

/* Test alloc timeout
*
* Given a pool with one slot for int data
* When a thread tries to allocate two blocks with @ TEST_TIMEOUT timeout
* Then first operation succeeds immediately and second fails at the correct time.
*/
void test_mem_pool_timeout()
{
MemoryPool<int, 1> mem_pool;

Timer timer;
timer.start();

int *item = mem_pool.alloc_for(TEST_TIMEOUT);
TEST_ASSERT_NOT_NULL(item);
TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, 0, timer.read_us());

item = mem_pool.alloc_for(TEST_TIMEOUT);
TEST_ASSERT_NULL(item);
TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, TEST_TIMEOUT * 1000, timer.read_us());

uint64_t end_time = Kernel::get_ms_count() + TEST_TIMEOUT;
item = mem_pool.alloc_until(end_time);
TEST_ASSERT_NULL(item);
TEST_ASSERT_UINT64_WITHIN(TEST_TIMEOUT * 100, end_time, Kernel::get_ms_count());
}

namespace {
struct free_capture {
MemoryPool<int, 1> *pool;
int *item;
};
}

static void free_int_item(free_capture *to_free)
{
ThisThread::sleep_for(TEST_TIMEOUT);

osStatus status = to_free->pool->free(to_free->item);
TEST_ASSERT_EQUAL(osOK, status);
}

/** Test alloc wait forever
*
* Given two threads A & B and a pool with one slot for int data
* When thread A allocs a block from the pool and tries to alloc a second one with @a osWaitForever timeout
* Then thread waits for a block to become free in the pool
* When thread B frees the first block from the pool
* Then thread A successfully allocs a block from the pool
*/
void test_mem_pool_waitforever()
{
Thread t(osPriorityNormal, THREAD_STACK_SIZE);
MemoryPool<int, 1> pool;

Timer timer;
timer.start();

int *item = pool.alloc_for(osWaitForever);
TEST_ASSERT_NOT_NULL(item);
TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, 0, timer.read_us());

struct free_capture to_free;
to_free.pool = &pool;
to_free.item = item;
t.start(callback(free_int_item, &to_free));

item = pool.alloc_for(osWaitForever);
TEST_ASSERT_EQUAL(item, to_free.item);
TEST_ASSERT_UINT32_WITHIN(TEST_TIMEOUT * 100, TEST_TIMEOUT * 1000, timer.read_us());

t.join();
}

/* Robustness checks for free() function.
* Function under test is called with invalid parameters.
*
Expand Down Expand Up @@ -569,6 +646,9 @@ Case cases[] = {

Case("Test: fail (out of free blocks).", test_mem_pool_alloc_fail_wrapper<int, 3>),

Case("Test: timeout", test_mem_pool_timeout),
Case("Test: wait forever", test_mem_pool_waitforever),

Case("Test: free() - robust (free called with invalid param - NULL).", free_block_invalid_parameter_null),
Case("Test: free() - robust (free called with invalid param).", free_block_invalid_parameter)
};
Expand Down
75 changes: 69 additions & 6 deletions rtos/Mail.h
Expand Up @@ -31,6 +31,7 @@
#include "mbed_rtos_storage.h"
#include "mbed_rtos1_types.h"

#include "platform/mbed_toolchain.h"
#include "platform/NonCopyable.h"

#ifndef MBED_NO_GLOBAL_USING_DIRECTIVE
Expand Down Expand Up @@ -90,32 +91,94 @@ class Mail : private mbed::NonCopyable<Mail<T, queue_sz> > {
return _queue.full();
}

/** Allocate a memory block of type T.
/** Allocate a memory block of type T, without blocking.
*
* @param millisec Not used.
* @param millisec Not used (see note).
*
* @return Pointer to memory block that you can fill with mail or NULL in case error.
*
* @note You may call this function from ISR context.
* @note If blocking is required, use Mail::alloc_for or Mail::alloc_until
*/
T *alloc(uint32_t millisec = 0)
T *alloc(MBED_UNUSED uint32_t millisec = 0)
{
return _pool.alloc();
}

/** Allocate a memory block of type T, optionally blocking.
*
* @param millisec Timeout value, or osWaitForever.
*
* @return Pointer to memory block that you can fill with mail or NULL in case error.
*
* @note You may call this function from ISR context if the millisec parameter is set to 0.
*/
T *alloc_for(uint32_t millisec)
{
return _pool.alloc_for(millisec);
}

/** Allocate a memory block of type T, blocking.
*
* @param millisec Absolute timeout time, referenced to Kernel::get_ms_count().
*
* @return Pointer to memory block that you can fill with mail or NULL in case error.
*
* @note You cannot call this function from ISR context.
* @note the underlying RTOS may have a limit to the maximum wait time
* due to internal 32-bit computations, but this is guaranteed to work if the
* wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded,
* the wait will time out earlier than specified.
*/
T *alloc_until(uint64_t millisec)
{
return _pool.alloc_until(millisec);
}

/** Allocate a memory block of type T, and set memory block to zero.
*
* @param millisec Not used.
* @param millisec Not used (see note).
*
* @return Pointer to memory block that you can fill with mail or NULL in case error.
*
* @note You may call this function from ISR context.
* @note You may call this function from ISR context if the millisec parameter is set to 0.
* @note If blocking is required, use Mail::calloc_for or Mail::calloc_until
*/
T *calloc(uint32_t millisec = 0)
T *calloc(MBED_UNUSED uint32_t millisec = 0)
{
return _pool.calloc();
}

/** Allocate a memory block of type T, optionally blocking, and set memory block to zero.
*
* @param millisec Timeout value, or osWaitForever.
*
* @return Pointer to memory block that you can fill with mail or NULL in case error.
*
* @note You may call this function from ISR context if the millisec parameter is set to 0.
*/
T *calloc_for(uint32_t millisec)
{
return _pool.calloc_for(millisec);
}

/** Allocate a memory block of type T, blocking, and set memory block to zero.
*
* @param millisec Absolute timeout time, referenced to Kernel::get_ms_count().
*
* @return Pointer to memory block that you can fill with mail or NULL in case error.
*
* @note You cannot call this function from ISR context.
* @note the underlying RTOS may have a limit to the maximum wait time
* due to internal 32-bit computations, but this is guaranteed to work if the
* wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded,
* the wait will time out earlier than specified.
*/
T *calloc_until(uint64_t millisec)
{
return _pool.calloc_until(millisec);
}

/** Put a mail in the queue.
*
* @param mptr Memory block previously allocated with Mail::alloc or Mail::calloc.
Expand Down
77 changes: 73 additions & 4 deletions rtos/MemoryPool.h
Expand Up @@ -76,7 +76,7 @@ class MemoryPool : private mbed::NonCopyable<MemoryPool<T, pool_sz> > {
osMemoryPoolDelete(_id);
}

/** Allocate a memory block of type T from a memory pool.
/** Allocate a memory block from a memory pool, without blocking.
@return address of the allocated memory block or NULL in case of no memory available.

@note You may call this function from ISR context.
Expand All @@ -86,14 +86,83 @@ class MemoryPool : private mbed::NonCopyable<MemoryPool<T, pool_sz> > {
return (T *)osMemoryPoolAlloc(_id, 0);
}

/** Allocate a memory block of type T from a memory pool and set memory block to zero.
/** Allocate a memory block from a memory pool, optionally blocking.
@param millisec timeout value (osWaitForever to wait forever)
@return address of the allocated memory block or NULL in case of no memory available.

@note You may call this function from ISR context if the millisec parameter is set to 0.
*/
T *alloc_for(uint32_t millisec)
{
return (T *)osMemoryPoolAlloc(_id, millisec);
}

/** Allocate a memory block from a memory pool, blocking.
@param millisec absolute timeout time, referenced to Kernel::get_ms_count().
@return address of the allocated memory block or NULL in case of no memory available.

@note You cannot call this function from ISR context.
@note the underlying RTOS may have a limit to the maximum wait time
due to internal 32-bit computations, but this is guaranteed to work if the
wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded,
the wait will time out earlier than specified.
*/
T *alloc_until(uint64_t millisec)
{
uint64_t now = Kernel::get_ms_count();
uint32_t delay;
if (now >= millisec) {
delay = 0;
} else if (millisec - now >= osWaitForever) {
delay = osWaitForever - 1;
} else {
delay = millisec - now;
}
return alloc_for(delay);
}

/** Allocate a memory block from a memory pool, without blocking, and set memory block to zero.
@return address of the allocated memory block or NULL in case of no memory available.

@note You may call this function from ISR context.
*/
T *calloc(void)
{
T *item = (T *)osMemoryPoolAlloc(_id, 0);
T *item = alloc();
if (item != NULL) {
memset(item, 0, sizeof(T));
}
return item;
}

/** Allocate a memory block from a memory pool, optionally blocking, and set memory block to zero.
@param millisec timeout value (osWaitForever to wait forever)
@return address of the allocated memory block or NULL in case of no memory available.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to add the description of millisec parameter here as well
@param millisec timeout value (osWaitForever to wait forever)


@note You may call this function from ISR context if the millisec parameter is set to 0.
*/
T *calloc_for(uint32_t millisec)
{
T *item = alloc_for(millisec);
if (item != NULL) {
memset(item, 0, sizeof(T));
}
return item;
}

/** Allocate a memory block from a memory pool, blocking, and set memory block to zero.
@param millisec absolute timeout time, referenced to Kernel::get_ms_count().
@return address of the allocated memory block or NULL in case of no memory available.

@note You cannot call this function from ISR context.
@note the underlying RTOS may have a limit to the maximum wait time
due to internal 32-bit computations, but this is guaranteed to work if the
wait is <= 0x7fffffff milliseconds (~24 days). If the limit is exceeded,
the wait will time out earlier than specified.
*/
T *calloc_until(uint64_t millisec)
{
T *item = alloc_until(millisec);
if (item != NULL) {
memset(item, 0, sizeof(T));
}
Expand All @@ -110,7 +179,7 @@ class MemoryPool : private mbed::NonCopyable<MemoryPool<T, pool_sz> > {
*/
osStatus free(T *block)
{
return osMemoryPoolFree(_id, (void *)block);
return osMemoryPoolFree(_id, block);
}

private:
Expand Down