mirror of https://github.com/ARMmbed/mbed-os.git
				
				
				
			Merge pull request #10225 from kjbracey-arm/memorypool_blocking_alloc
Mail/MemoryPool: blocking allocpull/10385/head
						commit
						b1e48db242
					
				| 
						 | 
					@ -20,6 +20,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace utest::v1;
 | 
					using namespace utest::v1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define THREAD_STACK_SIZE 512
 | 
				
			||||||
 | 
					#define TEST_TIMEOUT 50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Enum used to select block allocation method. */
 | 
					/* Enum used to select block allocation method. */
 | 
				
			||||||
typedef enum {
 | 
					typedef enum {
 | 
				
			||||||
    ALLOC, CALLOC
 | 
					    ALLOC, CALLOC
 | 
				
			||||||
| 
						 | 
					@ -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.
 | 
					/* Robustness checks for free() function.
 | 
				
			||||||
 * Function under test is called with invalid parameters.
 | 
					 * Function under test is called with invalid parameters.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
| 
						 | 
					@ -569,6 +646,9 @@ Case cases[] = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Case("Test: fail (out of free blocks).", test_mem_pool_alloc_fail_wrapper<int, 3>),
 | 
					    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 - NULL).", free_block_invalid_parameter_null),
 | 
				
			||||||
    Case("Test: free() - robust (free called with invalid param).", free_block_invalid_parameter)
 | 
					    Case("Test: free() - robust (free called with invalid param).", free_block_invalid_parameter)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										77
									
								
								rtos/Mail.h
								
								
								
								
							
							
						
						
									
										77
									
								
								rtos/Mail.h
								
								
								
								
							| 
						 | 
					@ -31,6 +31,7 @@
 | 
				
			||||||
#include "mbed_rtos_storage.h"
 | 
					#include "mbed_rtos_storage.h"
 | 
				
			||||||
#include "mbed_rtos1_types.h"
 | 
					#include "mbed_rtos1_types.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "platform/mbed_toolchain.h"
 | 
				
			||||||
#include "platform/NonCopyable.h"
 | 
					#include "platform/NonCopyable.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#ifndef MBED_NO_GLOBAL_USING_DIRECTIVE
 | 
					#ifndef MBED_NO_GLOBAL_USING_DIRECTIVE
 | 
				
			||||||
| 
						 | 
					@ -90,32 +91,94 @@ public:
 | 
				
			||||||
        return _queue.full();
 | 
					        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.
 | 
					     * @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.
 | 
				
			||||||
 | 
					     * @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();
 | 
					        return _pool.alloc();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Allocate a memory block of type T, and set memory block to zero.
 | 
					    /** Allocate a memory block of type T, optionally blocking.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param   millisec  Not used.
 | 
					     * @param   millisec  Timeout value, or osWaitForever.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return  Pointer to memory block that you can fill with mail or NULL in case error.
 | 
					     * @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.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    T *calloc(uint32_t millisec = 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 (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 if the millisec parameter is set to 0.
 | 
				
			||||||
 | 
					     * @note If blocking is required, use Mail::calloc_for or Mail::calloc_until
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    T *calloc(MBED_UNUSED uint32_t millisec = 0)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return _pool.calloc();
 | 
					        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.
 | 
					    /** Put a mail in the queue.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param   mptr  Memory block previously allocated with Mail::alloc or Mail::calloc.
 | 
					     * @param   mptr  Memory block previously allocated with Mail::alloc or Mail::calloc.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -75,7 +75,7 @@ public:
 | 
				
			||||||
        osMemoryPoolDelete(_id);
 | 
					        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.
 | 
					      @return  address of the allocated memory block or NULL in case of no memory available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      @note You may call this function from ISR context.
 | 
					      @note You may call this function from ISR context.
 | 
				
			||||||
| 
						 | 
					@ -85,14 +85,83 @@ public:
 | 
				
			||||||
        return (T *)osMemoryPoolAlloc(_id, 0);
 | 
					        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.
 | 
					      @return  address of the allocated memory block or NULL in case of no memory available.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      @note You may call this function from ISR context.
 | 
					      @note You may call this function from ISR context.
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    T *calloc(void)
 | 
					    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      @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) {
 | 
					        if (item != NULL) {
 | 
				
			||||||
            memset(item, 0, sizeof(T));
 | 
					            memset(item, 0, sizeof(T));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -109,7 +178,7 @@ public:
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    osStatus free(T *block)
 | 
					    osStatus free(T *block)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return osMemoryPoolFree(_id, (void *)block);
 | 
					        return osMemoryPoolFree(_id, block);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue