2 * Amazon FreeRTOS Common V1.0.0
\r
3 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
\r
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of
\r
6 * this software and associated documentation files (the "Software"), to deal in
\r
7 * the Software without restriction, including without limitation the rights to
\r
8 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
\r
9 * the Software, and to permit persons to whom the Software is furnished to do so,
\r
10 * subject to the following conditions:
\r
12 * The above copyright notice and this permission notice shall be included in all
\r
13 * copies or substantial portions of the Software.
\r
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
\r
17 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
\r
18 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
\r
19 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
\r
20 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
22 * http://aws.amazon.com/freertos
\r
23 * http://www.FreeRTOS.org
\r
26 * @file iot_taskpool.c
\r
27 * @brief Implements the task pool functions in iot_taskpool.h
\r
30 /* Kernel includes. */
\r
31 #include "FreeRTOS.h"
\r
34 /* IoT libraries includes. */
\r
35 #include "iot_config.h"
\r
37 /* Standard includes. */
\r
38 #include <stdbool.h>
\r
44 #if !defined( configSUPPORT_STATIC_ALLOCATION ) || ( configSUPPORT_STATIC_ALLOCATION != 1 )
\r
45 #error configSUPPORT_STATIC_ALLOCATION must be set to 1 in FreeRTOSConfig.h to build this file.
\r
48 /* Task pool internal include. */
\r
49 #include "private/iot_taskpool_internal.h"
\r
52 * @brief Maximum semaphore value for wait operations.
\r
54 #define TASKPOOL_MAX_SEM_VALUE 0xFFFF
\r
57 * @brief Reschedule delay in milliseconds for deferred jobs.
\r
59 #define TASKPOOL_JOB_RESCHEDULE_DELAY_MS ( 10ULL )
\r
61 /* ---------------------------------------------------------------------------------- */
\r
64 * Doxygen should ignore this section.
\r
66 * @brief The system task pool handle for all libraries to use.
\r
67 * User application can use the system task pool as well knowing that the usage will be shared with
\r
68 * the system libraries as well. The system task pool needs to be initialized before any library is used or
\r
69 * before any code that posts jobs to the task pool runs.
\r
71 static _taskPool_t _IotSystemTaskPool = { .dispatchQueue = IOT_DEQUEUE_INITIALIZER };
\r
73 /* -------------- Convenience functions to create/recycle/destroy jobs -------------- */
\r
76 * @brief Initializes one instance of a Task pool cache.
\r
78 * @param[in] pCache The pre-allocated instance of the cache to initialize.
\r
80 static void _initJobsCache( _taskPoolCache_t * const pCache );
\r
83 * @brief Initialize a job.
\r
85 * @param[in] pJob The job to initialize.
\r
86 * @param[in] userCallback The user callback for the job.
\r
87 * @param[in] pUserContext The context tp be passed to the callback.
\r
88 * @param[in] isStatic A flag to indicate whether the job is statically or synamically allocated.
\r
90 static void _initializeJob( _taskPoolJob_t * const pJob,
\r
91 IotTaskPoolRoutine_t userCallback,
\r
92 void * pUserContext,
\r
96 * @brief Extracts and initializes one instance of a job from the cache or, if there is none available, it allocates and initialized a new one.
\r
98 * @param[in] pCache The instance of the cache to extract the job from.
\r
100 static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache );
\r
103 * Recycles one instance of a job into the cache or, if the cache is full, it destroys it.
\r
105 * @param[in] pCache The instance of the cache to recycle the job into.
\r
106 * @param[in] pJob The job to recycle.
\r
109 static void _recycleJob( _taskPoolCache_t * const pCache,
\r
110 _taskPoolJob_t * const pJob );
\r
113 * Destroys one instance of a job.
\r
115 * @param[in] pJob The job to destroy.
\r
118 static void _destroyJob( _taskPoolJob_t * const pJob );
\r
120 /* -------------- The worker thread procedure for a task pool thread -------------- */
\r
123 * The procedure for a task pool worker thread.
\r
125 * @param[in] pUserContext The user context.
\r
128 static void _taskPoolWorker( void * pUserContext );
\r
130 /* -------------- Convenience functions to handle timer events -------------- */
\r
133 * Comparer for the time list.
\r
135 * param[in] pTimerEventLink1 The link to the first timer event.
\r
136 * param[in] pTimerEventLink1 The link to the first timer event.
\r
138 static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1,
\r
139 const IotLink_t * const pTimerEventLink2 );
\r
142 * Reschedules the timer for handling deferred jobs to the next timeout.
\r
144 * param[in] timer The timer to reschedule.
\r
145 * param[in] pFirstTimerEvent The timer event that carries the timeout and job inforamtion.
\r
147 static void _rescheduleDeferredJobsTimer( TimerHandle_t const timer,
\r
148 _taskPoolTimerEvent_t * const pFirstTimerEvent );
\r
151 * The task pool timer procedure for scheduling deferred jobs.
\r
153 * param[in] timer The timer to handle.
\r
155 static void _timerCallback( TimerHandle_t xTimer );
\r
157 /* -------------- Convenience functions to create/initialize/destroy the task pool -------------- */
\r
160 * Parameter validation for a task pool initialization.
\r
162 * @param[in] pInfo The initialization information for the task pool.
\r
165 static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo );
\r
168 * Initializes a pre-allocated instance of a task pool.
\r
170 * @param[in] pTaskPool The pre-allocated instance of the task pool to initialize.
\r
173 static void _initTaskPoolControlStructures( _taskPool_t * const pTaskPool );
\r
176 * Initializes a pre-allocated instance of a task pool.
\r
178 * @param[in] pInfo The initialization information for the task pool.
\r
179 * @param[out] pTaskPool A pointer to the task pool data structure to initialize.
\r
182 static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo,
\r
183 _taskPool_t * const pTaskPool );
\r
186 * Destroys one instance of a task pool.
\r
188 * @param[in] pTaskPool The task pool to destroy.
\r
191 static void _destroyTaskPool( _taskPool_t * const pTaskPool );
\r
194 * Set the exit condition.
\r
196 * @param[in] pTaskPool The task pool to destroy.
\r
197 * @param[in] threads The number of threads active in the task pool at shutdown time.
\r
200 static void _signalShutdown( _taskPool_t * const pTaskPool,
\r
201 uint32_t threads );
\r
204 * Places a job in the dispatch queue.
\r
206 * @param[in] pTaskPool The task pool to scheduel the job with.
\r
207 * @param[in] pJob The job to schedule.
\r
208 * @param[in] flags The job flags.
\r
211 static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool,
\r
212 _taskPoolJob_t * const pJob );
\r
214 * Matches a deferred job in the timer queue with its timer event wrapper.
\r
216 * @param[in] pLink A pointer to the timer event link in the timer queue.
\r
217 * @param[in] pMatch A pointer to the job to match.
\r
220 static bool _matchJobByPointer( const IotLink_t * const pLink,
\r
224 * Tries to cancel a job.
\r
226 * @param[in] pTaskPool The task pool to cancel an operation against.
\r
227 * @param[in] pJob The job to cancel.
\r
228 * @param[out] pStatus The status of the job at the time of cancellation.
\r
231 static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool,
\r
232 _taskPoolJob_t * const pJob,
\r
233 IotTaskPoolJobStatus_t * const pStatus );
\r
235 /* ---------------------------------------------------------------------------------------------- */
\r
238 * The full IoT Task Pool Library has many use cases, including Linux
\r
239 * development. Â Typical FreeRTOS use cases do not require the full
\r
240 * functionality so optimised version is provided specifically for use with
\r
241 * FreeRTOS. Unlike the full version, this optimised version:
\r
242 * + Only supports a single task pool (system task pool) at a time.
\r
243 * + Does not auto-scale by dynamically adding more tasks if the number of
\r
244 * + tasks in the pool becomes exhausted. Â The number of tasks in the pool
\r
245 * are fixed at compile time. See the task pool configuration options for
\r
246 * more information.
\r
247 * + Cannot be shut down - it exists for the lifetime of the application.
\r
249 * As such this file does not implement the following API functions:
\r
250 * + IotTaskPool_GetSystemTaskPool()
\r
251 * + IotTaskPool_Create()
\r
252 * + IotTaskPool_Destroy()
\r
253 * + IotTaskPool_SetMaxThreads()
\r
255 * Users who are interested in the functionality of the full IoT Task Pool
\r
256 * library can us it in place of this optimised version.
\r
259 IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo )
\r
261 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
263 /* At this time the task pool cannot be created before the scheduler has
\r
264 started because the function attempts to block on synchronization
\r
265 primitives (although I'm not sure why). */
\r
266 configASSERT( xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED );
\r
268 /* Guard against multiple attempts to create the system task pool in case
\r
269 this function is called by more than one library initialization routine. */
\r
270 if( _IotSystemTaskPool.running == false )
\r
272 /* Parameter checking. */
\r
273 TASKPOOL_ON_ERROR_GOTO_CLEANUP( _performTaskPoolParameterValidation( pInfo ) );
\r
275 /* Create the system task pool pool. */
\r
276 TASKPOOL_SET_AND_GOTO_CLEANUP( _createTaskPool( pInfo, &_IotSystemTaskPool ) );
\r
279 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
281 /*-----------------------------------------------------------*/
\r
283 IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback,
\r
284 void * pUserContext,
\r
285 IotTaskPoolJobStorage_t * const pJobStorage,
\r
286 IotTaskPoolJob_t * const ppJob )
\r
288 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
290 /* Parameter checking. */
\r
291 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback );
\r
292 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobStorage );
\r
293 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob );
\r
295 /* Build a job around the user-provided storage. */
\r
296 _initializeJob( ( _taskPoolJob_t * ) pJobStorage, userCallback, pUserContext, true );
\r
298 *ppJob = ( IotTaskPoolJob_t ) pJobStorage;
\r
300 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
303 /*-----------------------------------------------------------*/
\r
305 IotTaskPoolError_t IotTaskPool_CreateRecyclableJob( IotTaskPool_t taskPoolHandle,
\r
306 IotTaskPoolRoutine_t userCallback,
\r
307 void * pUserContext,
\r
308 IotTaskPoolJob_t * const ppJob )
\r
310 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
312 _taskPool_t * const pTaskPool = &_IotSystemTaskPool;
\r
313 _taskPoolJob_t * pTempJob = NULL;
\r
315 /* This lean version of the task pool only supports the task pool created
\r
316 by this library (the system task pool). NULL means use the system task
\r
317 pool - no other values are allowed. Use the full implementation of this
\r
318 library if you want multiple task pools (there is more than one task in
\r
320 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
322 /* Avoid compiler warnings about unused parameters if configASSERT() is not
\r
324 ( void ) taskPoolHandle;
\r
326 /* Parameter checking. */
\r
327 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( userCallback );
\r
328 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( ppJob );
\r
330 taskENTER_CRITICAL();
\r
332 /* Bail out early if this task pool is shutting down. */
\r
333 pTempJob = _fetchOrAllocateJob( &pTaskPool->jobsCache );
\r
335 taskEXIT_CRITICAL();
\r
337 if( pTempJob == NULL )
\r
339 IotLogInfo( "Failed to allocate a job." );
\r
341 TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY );
\r
344 _initializeJob( pTempJob, userCallback, pUserContext, false );
\r
348 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
351 /*-----------------------------------------------------------*/
\r
353 IotTaskPoolError_t IotTaskPool_DestroyRecyclableJob( IotTaskPool_t taskPoolHandle,
\r
354 IotTaskPoolJob_t pJobHandle )
\r
356 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
358 _taskPoolJob_t * pJob = ( _taskPoolJob_t * ) pJobHandle;
\r
360 /* This lean version of the task pool only supports the task pool created
\r
361 by this library (the system task pool). NULL means use the system task
\r
362 pool - no other values are allowed. Use the full implementation of this
\r
363 library if you want multiple task pools (there is more than one task in
\r
365 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
367 /* Avoid compiler warnings about unused parameters if configASSERT() is not
\r
369 ( void ) taskPoolHandle;
\r
371 /* Parameter checking. */
\r
372 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJobHandle );
\r
374 IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false );
\r
376 _destroyJob( pJob );
\r
378 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
381 /*-----------------------------------------------------------*/
\r
383 IotTaskPoolError_t IotTaskPool_RecycleJob( IotTaskPool_t taskPoolHandle,
\r
384 IotTaskPoolJob_t pJob )
\r
386 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
388 _taskPool_t * pTaskPool = ( _taskPool_t * ) &_IotSystemTaskPool;
\r
390 /* This lean version of the task pool only supports the task pool created
\r
391 by this library (the system task pool). NULL means use the system task
\r
392 pool - no other values are allowed. Use the full implementation of this
\r
393 library if you want multiple task pools (there is more than one task in
\r
395 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
397 /* Ensure unused parameters do not cause compiler warnings in case
\r
398 configASSERT() is not defined. */
\r
399 ( void ) taskPoolHandle;
\r
401 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob );
\r
403 taskENTER_CRITICAL();
\r
405 IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false );
\r
407 _recycleJob( &pTaskPool->jobsCache, pJob );
\r
409 taskEXIT_CRITICAL();
\r
411 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
414 /*-----------------------------------------------------------*/
\r
416 IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPoolHandle,
\r
417 IotTaskPoolJob_t pJob,
\r
420 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
422 _taskPool_t * const pTaskPool = &_IotSystemTaskPool;
\r
424 /* Task pool must have been created. */
\r
425 configASSERT( pTaskPool->running != false );
\r
427 /* This lean version of the task pool only supports the task pool created
\r
428 by this library (the system task pool). NULL means use the system task
\r
429 pool - no other values are allowed. Use the full implementation of this
\r
430 library if you want multiple task pools (there is more than one task in
\r
432 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
434 /* Avoid compiler warnings about unused parameters if configASSERT() is not
\r
436 ( void ) taskPoolHandle;
\r
438 /* Parameter checking. */
\r
439 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pJob );
\r
440 TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( ( flags != 0UL ) && ( flags != IOT_TASKPOOL_JOB_HIGH_PRIORITY ) );
\r
442 taskENTER_CRITICAL(); //_RB_ Critical section is too long - does the whole thing need to be protected?
\r
444 _scheduleInternal( pTaskPool, pJob );
\r
446 taskEXIT_CRITICAL();
\r
448 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
451 /*-----------------------------------------------------------*/
\r
453 IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle,
\r
454 IotTaskPoolJob_t job,
\r
457 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
459 _taskPool_t * pTaskPool = &_IotSystemTaskPool;
\r
461 /* This lean version of the task pool only supports the task pool created
\r
462 by this library (the system task pool). NULL means use the system task
\r
463 pool - no other values are allowed. Use the full implementation of this
\r
464 library if you want multiple task pools (there is more than one task in
\r
466 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
468 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( job );
\r
470 if( timeMs == 0UL )
\r
472 TASKPOOL_SET_AND_GOTO_CLEANUP( IotTaskPool_Schedule( pTaskPool, job, 0 ) );
\r
475 taskENTER_CRITICAL();
\r
477 _taskPoolTimerEvent_t * pTimerEvent = IotTaskPool_MallocTimerEvent( sizeof( _taskPoolTimerEvent_t ) );
\r
479 if( pTimerEvent == NULL )
\r
481 taskEXIT_CRITICAL();
\r
483 TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY );
\r
486 IotLink_t * pTimerEventLink;
\r
488 TickType_t now = xTaskGetTickCount();
\r
490 pTimerEvent->link.pNext = NULL;
\r
491 pTimerEvent->link.pPrevious = NULL;
\r
492 pTimerEvent->expirationTime = now + pdMS_TO_TICKS( timeMs );
\r
493 pTimerEvent->job = job; //_RB_ Think up to here can be outside the critical section.
\r
495 /* Append the timer event to the timer list. */
\r
496 IotListDouble_InsertSorted( &pTaskPool->timerEventsList, &pTimerEvent->link, _timerEventCompare );
\r
498 /* Update the job status to 'scheduled'. */
\r
499 job->status = IOT_TASKPOOL_STATUS_DEFERRED;
\r
501 /* Peek the first event in the timer event list. There must be at least one,
\r
502 * since we just inserted it. */
\r
503 pTimerEventLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList );
\r
504 IotTaskPool_Assert( pTimerEventLink != NULL );
\r
506 /* If the event we inserted is at the front of the queue, then
\r
507 * we need to reschedule the underlying timer. */
\r
508 if( pTimerEventLink == &pTimerEvent->link )
\r
510 pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link );
\r
512 _rescheduleDeferredJobsTimer( pTaskPool->timer, pTimerEvent );
\r
515 taskEXIT_CRITICAL();
\r
517 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
520 /*-----------------------------------------------------------*/
\r
522 IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPoolHandle,
\r
523 IotTaskPoolJob_t job,
\r
524 IotTaskPoolJobStatus_t * const pStatus )
\r
526 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
528 /* This lean version of the task pool only supports the task pool created
\r
529 by this library (the system task pool). NULL means use the system task
\r
530 pool - no other values are allowed. Use the full implementation of this
\r
531 library if you want multiple task pools (there is more than one task in
\r
533 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
534 ( void ) taskPoolHandle;
\r
536 /* Parameter checking. */
\r
537 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( job );
\r
538 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pStatus );
\r
539 *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED;
\r
541 taskENTER_CRITICAL();
\r
543 *pStatus = job->status;
\r
545 taskEXIT_CRITICAL();
\r
547 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
550 /*-----------------------------------------------------------*/
\r
552 IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle,
\r
553 IotTaskPoolJob_t job,
\r
554 IotTaskPoolJobStatus_t * const pStatus )
\r
556 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
558 _taskPool_t * const pTaskPool = &_IotSystemTaskPool;
\r
560 /* This lean version of the task pool only supports the task pool created
\r
561 by this library (the system task pool). NULL means use the system task
\r
562 pool - no other values are allowed. Use the full implementation of this
\r
563 library if you want multiple task pools (there is more than one task in
\r
565 configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) );
\r
566 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( job );
\r
568 if( pStatus != NULL )
\r
570 *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED;
\r
573 taskENTER_CRITICAL();
\r
575 status = _tryCancelInternal( pTaskPool, job, pStatus );
\r
577 taskEXIT_CRITICAL();
\r
579 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
582 /*-----------------------------------------------------------*/
\r
584 IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t pJob )
\r
586 return ( IotTaskPoolJobStorage_t * ) pJob;
\r
589 /*-----------------------------------------------------------*/
\r
591 const char * IotTaskPool_strerror( IotTaskPoolError_t status )
\r
593 const char * pMessage = NULL;
\r
597 case IOT_TASKPOOL_SUCCESS:
\r
598 pMessage = "SUCCESS";
\r
601 case IOT_TASKPOOL_BAD_PARAMETER:
\r
602 pMessage = "BAD PARAMETER";
\r
605 case IOT_TASKPOOL_ILLEGAL_OPERATION:
\r
606 pMessage = "ILLEGAL OPERATION";
\r
609 case IOT_TASKPOOL_NO_MEMORY:
\r
610 pMessage = "NO MEMORY";
\r
613 case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS:
\r
614 pMessage = "SHUTDOWN IN PROGRESS";
\r
617 case IOT_TASKPOOL_CANCEL_FAILED:
\r
618 pMessage = "CANCEL FAILED";
\r
622 pMessage = "INVALID STATUS";
\r
629 /* ---------------------------------------------------------------------------------------------- */
\r
630 /* ---------------------------------------------------------------------------------------------- */
\r
631 /* ---------------------------------------------------------------------------------------------- */
\r
633 static IotTaskPoolError_t _performTaskPoolParameterValidation( const IotTaskPoolInfo_t * const pInfo )
\r
635 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
637 /* Check input values for consistency. */
\r
638 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pInfo );
\r
639 TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads > pInfo->maxThreads );
\r
640 TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->minThreads < 1UL );
\r
641 TASKPOOL_ON_ARG_ERROR_GOTO_CLEANUP( pInfo->maxThreads < 1UL );
\r
643 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
646 static void _initTaskPoolControlStructures( _taskPool_t * const pTaskPool )
\r
648 /* Initialize a job data structures that require no de-initialization.
\r
649 * All other data structures carry a value of 'NULL' before initailization.
\r
651 IotDeQueue_Create( &pTaskPool->dispatchQueue );
\r
652 IotListDouble_Create( &pTaskPool->timerEventsList );
\r
654 _initJobsCache( &pTaskPool->jobsCache );
\r
656 /* Initialize the semaphore for waiting for incoming work. Cannot fail as
\r
657 statically allocated. */
\r
658 pTaskPool->dispatchSignal = xSemaphoreCreateCountingStatic( TASKPOOL_MAX_SEM_VALUE, 0, &pTaskPool->dispatchSignalBuffer );
\r
661 static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo,
\r
662 _taskPool_t * const pTaskPool )
\r
664 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
666 uint32_t threadsCreated = 0; /* Although initialised before use removing the initialiser here results in compiler warnings. */
\r
667 char cTaskName[ 10 ];
\r
669 /* Check input values for consistency. */
\r
670 TASKPOOL_ON_NULL_ARG_GOTO_CLEANUP( pTaskPool );
\r
672 /* Zero out all data structures. */
\r
673 memset( ( void * ) pTaskPool, 0x00, sizeof( _taskPool_t ) );
\r
675 /* Initialize all internal data structure prior to creating all threads. */
\r
676 _initTaskPoolControlStructures( pTaskPool );
\r
678 /* Create the timer for a new connection. */
\r
679 pTaskPool->timer = xTimerCreate( NULL, portMAX_DELAY, pdFALSE, ( void * ) pTaskPool, _timerCallback );
\r
681 if( pTaskPool->timer == NULL )
\r
683 IotLogError( "Failed to create timer for task pool." );
\r
685 TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY );
\r
688 /* The task pool will initialize the minimum number of threads requested by the user upon start.
\r
689 Note this tailored version of the task pool does not autoscale, but fixes the number of tasks
\r
690 in the pool to the originally specified minimum, and the specified maximum value is ignored. */
\r
691 /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */
\r
692 for( threadsCreated = 0; threadsCreated < pInfo->minThreads; )
\r
694 TaskHandle_t task = NULL;
\r
696 /* Generate a unique name for the task. */
\r
697 snprintf( cTaskName, sizeof( cTaskName ), "pool%d", ( int ) threadsCreated );
\r
699 BaseType_t res = xTaskCreate( _taskPoolWorker,
\r
701 pInfo->stackSize / sizeof( portSTACK_TYPE ), /* xTaskCreate() expects the stack size to be specified in words. */
\r
706 /* Create one thread. */
\r
707 if( res == pdFALSE ) //_RB_ would not be needed if tasks are created statically.
\r
709 IotLogError( "Could not create worker thread! Exiting..." );
\r
711 /* If creating one thread fails, set error condition and exit the loop. */
\r
712 TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_NO_MEMORY );
\r
715 /* Upon successful thread creation, increase the number of active threads. */
\r
716 pTaskPool->activeThreads++;
\r
717 IotTaskPool_Assert( task != NULL );
\r
722 TASKPOOL_FUNCTION_CLEANUP();
\r
724 /* In case of failure, wait on the created threads to exit. */
\r
725 if( TASKPOOL_FAILED( status ) )
\r
727 /* Set the exit condition for the newly created threads. */
\r
728 _signalShutdown( pTaskPool, threadsCreated );
\r
729 _destroyTaskPool( pTaskPool );
\r
732 pTaskPool->running = true;
\r
734 TASKPOOL_FUNCTION_CLEANUP_END();
\r
737 /*-----------------------------------------------------------*/
\r
739 static void _destroyTaskPool( _taskPool_t * const pTaskPool )
\r
741 if( pTaskPool->timer != NULL )
\r
743 xTimerDelete( pTaskPool->timer, 0 );
\r
746 if( pTaskPool->dispatchSignal != NULL )
\r
748 vSemaphoreDelete( pTaskPool->dispatchSignal );
\r
752 /* ---------------------------------------------------------------------------------------------- */
\r
754 static void _taskPoolWorker( void * pUserContext )
\r
756 IotTaskPool_Assert( pUserContext != NULL );
\r
758 IotTaskPoolRoutine_t userCallback = NULL;
\r
759 bool running = true;
\r
761 /* Extract pTaskPool pointer from context. */
\r
762 _taskPool_t * pTaskPool = ( _taskPool_t * ) pUserContext;
\r
764 /* OUTER LOOP: it controls the lifetiem of the worker thread: exit condition for a worker thread
\r
765 * is setting maxThreads to zero. A worker thread is running until the maximum number of allowed
\r
766 * threads is not zero and the active threads are less than the maximum number of allowed threads.
\r
770 IotLink_t * pFirst = NULL;
\r
771 _taskPoolJob_t * pJob = NULL;
\r
773 /* Wait on incoming notifications... */
\r
774 xSemaphoreTake( pTaskPool->dispatchSignal, portMAX_DELAY );
\r
776 /* Acquire the lock to check the exit condition, and release the lock if the exit condition is verified,
\r
777 * or before waiting for incoming notifications.
\r
779 taskENTER_CRITICAL();
\r
781 /* Dequeue the first job in FIFO order. */
\r
782 pFirst = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue );
\r
784 /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */
\r
785 if( pFirst != NULL )
\r
787 /* Extract the job from its link. */
\r
788 pJob = IotLink_Container( _taskPoolJob_t, pFirst, link );
\r
790 /* Update status to 'executing'. */
\r
791 pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; /*_RB_ Should this be 'executing'? */
\r
792 userCallback = pJob->userCallback;
\r
795 taskEXIT_CRITICAL();
\r
797 /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */
\r
798 while( pJob != NULL )
\r
800 /* Process the job by invoking the associated callback with the user context.
\r
801 * This task pool thread will not be available until the user callback returns.
\r
804 IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false );
\r
805 IotTaskPool_Assert( userCallback != NULL );
\r
807 userCallback( pTaskPool, pJob, pJob->pUserContext );
\r
809 /* This job is finished, clear its pointer. */
\r
811 userCallback = NULL;
\r
813 /* If this thread exceeded the quota, then let it terminate. */
\r
814 if( running == false )
\r
816 /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */
\r
821 /* Acquire the lock before updating the job status. */
\r
822 taskENTER_CRITICAL();
\r
824 /* Try and dequeue the next job in the dispatch queue. */
\r
825 IotLink_t * pItem = NULL;
\r
827 /* Dequeue the next job from the dispatch queue. */
\r
828 pItem = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue );
\r
830 /* If there is no job left in the dispatch queue, update the worker status and leave. */
\r
831 if( pItem == NULL )
\r
833 taskEXIT_CRITICAL();
\r
835 /* Abandon the INNER LOOP. Execution will tranfer back to the OUTER LOOP condition. */
\r
840 pJob = IotLink_Container( _taskPoolJob_t, pItem, link );
\r
842 userCallback = pJob->userCallback;
\r
845 pJob->status = IOT_TASKPOOL_STATUS_COMPLETED;
\r
847 taskEXIT_CRITICAL();
\r
849 } while( running == true );
\r
851 vTaskDelete( NULL );
\r
854 /* ---------------------------------------------------------------------------------------------- */
\r
856 static void _initJobsCache( _taskPoolCache_t * const pCache )
\r
858 IotDeQueue_Create( &pCache->freeList );
\r
860 pCache->freeCount = 0;
\r
863 /*-----------------------------------------------------------*/
\r
865 static void _initializeJob( _taskPoolJob_t * const pJob,
\r
866 IotTaskPoolRoutine_t userCallback,
\r
867 void * pUserContext,
\r
870 pJob->link.pNext = NULL;
\r
871 pJob->link.pPrevious = NULL;
\r
872 pJob->userCallback = userCallback;
\r
873 pJob->pUserContext = pUserContext;
\r
877 pJob->flags = IOT_TASK_POOL_INTERNAL_STATIC;
\r
878 pJob->status = IOT_TASKPOOL_STATUS_READY;
\r
882 pJob->status = IOT_TASKPOOL_STATUS_READY;
\r
886 static _taskPoolJob_t * _fetchOrAllocateJob( _taskPoolCache_t * const pCache )
\r
888 _taskPoolJob_t * pJob = NULL;
\r
889 IotLink_t * pLink = IotListDouble_RemoveHead( &( pCache->freeList ) );
\r
891 if( pLink != NULL )
\r
893 pJob = IotLink_Container( _taskPoolJob_t, pLink, link );
\r
896 /* If there is no available job in the cache, then allocate one. */
\r
899 pJob = ( _taskPoolJob_t * ) IotTaskPool_MallocJob( sizeof( _taskPoolJob_t ) );
\r
903 memset( pJob, 0x00, sizeof( _taskPoolJob_t ) );
\r
907 /* Log alocation failure for troubleshooting purposes. */
\r
908 IotLogInfo( "Failed to allocate job." );
\r
911 /* If there was a job in the cache, then make sure we keep the counters up-to-date. */
\r
914 IotTaskPool_Assert( pCache->freeCount > 0 );
\r
916 pCache->freeCount--;
\r
922 /*-----------------------------------------------------------*/
\r
924 static void _recycleJob( _taskPoolCache_t * const pCache,
\r
925 _taskPoolJob_t * const pJob )
\r
927 /* We should never try and recycling a job that is linked into some queue. */
\r
928 IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) == false );//_RB_ Seems to be duplicate of test before this is called.
\r
930 /* We will recycle the job if there is space in the cache. */
\r
931 if( pCache->freeCount < IOT_TASKPOOL_JOBS_RECYCLE_LIMIT )
\r
933 /* Destroy user data, for added safety&security. */
\r
934 pJob->userCallback = NULL;
\r
935 pJob->pUserContext = NULL;
\r
937 /* Reset the status for added debuggability. */
\r
938 pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED;
\r
940 IotListDouble_InsertTail( &pCache->freeList, &pJob->link );
\r
942 pCache->freeCount++;
\r
944 IotTaskPool_Assert( pCache->freeCount >= 1 );
\r
948 _destroyJob( pJob );
\r
952 /*-----------------------------------------------------------*/
\r
954 static void _destroyJob( _taskPoolJob_t * const pJob )
\r
956 /* Destroy user data, for added safety & security. */
\r
957 pJob->userCallback = NULL;
\r
958 pJob->pUserContext = NULL;
\r
960 /* Reset the status for added debuggability. */
\r
961 pJob->status = IOT_TASKPOOL_STATUS_UNDEFINED;
\r
963 /* Only dispose of dynamically allocated jobs. */
\r
964 if( ( pJob->flags & IOT_TASK_POOL_INTERNAL_STATIC ) == 0UL )
\r
966 IotTaskPool_FreeJob( pJob );
\r
970 /* ---------------------------------------------------------------------------------------------- */
\r
972 static bool _IsShutdownStarted( const _taskPool_t * const pTaskPool )
\r
974 return( pTaskPool->running == false );
\r
977 /*-----------------------------------------------------------*/
\r
979 static void _signalShutdown( _taskPool_t * const pTaskPool,
\r
984 /* Set the exit condition. */
\r
985 pTaskPool->running = false;
\r
987 /* Broadcast to all active threads to wake-up. Active threads do check the exit condition right after wakein up. */
\r
988 for( count = 0; count < threads; ++count )
\r
990 ( void ) xSemaphoreGive( pTaskPool->dispatchSignal );
\r
994 /* ---------------------------------------------------------------------------------------------- */
\r
996 static IotTaskPoolError_t _scheduleInternal( _taskPool_t * const pTaskPool,
\r
997 _taskPoolJob_t * const pJob )
\r
999 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
1001 /* Update the job status to 'scheduled'. */
\r
1002 pJob->status = IOT_TASKPOOL_STATUS_SCHEDULED;
\r
1004 /* Append the job to the dispatch queue. */
\r
1005 IotDeQueue_EnqueueTail( &pTaskPool->dispatchQueue, &pJob->link );
\r
1007 /* Signal a worker to pick up the job. */
\r
1008 xSemaphoreGive( pTaskPool->dispatchSignal );
\r
1010 TASKPOOL_NO_FUNCTION_CLEANUP_NOLABEL();
\r
1013 /*-----------------------------------------------------------*/
\r
1015 static bool _matchJobByPointer( const IotLink_t * const pLink,
\r
1018 const _taskPoolJob_t * const pJob = ( _taskPoolJob_t * ) pMatch;
\r
1020 const _taskPoolTimerEvent_t * const pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link );
\r
1022 if( pJob == pTimerEvent->job )
\r
1030 /*-----------------------------------------------------------*/
\r
1032 static IotTaskPoolError_t _tryCancelInternal( _taskPool_t * const pTaskPool,
\r
1033 _taskPoolJob_t * const pJob,
\r
1034 IotTaskPoolJobStatus_t * const pStatus )
\r
1036 TASKPOOL_FUNCTION_ENTRY( IOT_TASKPOOL_SUCCESS );
\r
1038 bool cancelable = false;
\r
1040 /* We can only cancel jobs that are either 'ready' (waiting to be scheduled). 'deferred', or 'scheduled'. */
\r
1042 IotTaskPoolJobStatus_t currentStatus = pJob->status;
\r
1044 switch( currentStatus )
\r
1046 case IOT_TASKPOOL_STATUS_READY:
\r
1047 case IOT_TASKPOOL_STATUS_DEFERRED:
\r
1048 case IOT_TASKPOOL_STATUS_SCHEDULED:
\r
1049 case IOT_TASKPOOL_STATUS_CANCELED:
\r
1050 cancelable = true;
\r
1053 case IOT_TASKPOOL_STATUS_COMPLETED:
\r
1054 /* Log mesggesong purposes. */
\r
1055 IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." );
\r
1059 /* Log mesggesong purposes. */
\r
1060 IotLogError( "Attempt to cancel a job with an undefined state." );
\r
1064 /* Update the returned status to the current status of the job. */
\r
1065 if( pStatus != NULL )
\r
1067 *pStatus = currentStatus;
\r
1070 if( cancelable == false )
\r
1072 TASKPOOL_SET_AND_GOTO_CLEANUP( IOT_TASKPOOL_CANCEL_FAILED );
\r
1076 /* Update the status of the job. */
\r
1077 pJob->status = IOT_TASKPOOL_STATUS_CANCELED;
\r
1079 /* If the job is cancelable and its current status is 'scheduled' then unlink it from the dispatch
\r
1080 * queue and signal any waiting threads. */
\r
1081 if( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED )
\r
1083 /* A scheduled work items must be in the dispatch queue. */
\r
1084 IotTaskPool_Assert( IotLink_IsLinked( &pJob->link ) );
\r
1086 IotDeQueue_Remove( &pJob->link );
\r
1089 /* If the job current status is 'deferred' then the job has to be pending
\r
1090 * in the timeouts queue. */
\r
1091 else if( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED )
\r
1093 /* Find the timer event associated with the current job. There MUST be one, hence assert if not. */
\r
1094 IotLink_t * pTimerEventLink = IotListDouble_FindFirstMatch( &pTaskPool->timerEventsList, NULL, _matchJobByPointer, pJob );
\r
1095 IotTaskPool_Assert( pTimerEventLink != NULL );
\r
1097 if( pTimerEventLink != NULL )
\r
1099 bool shouldReschedule = false;
\r
1101 /* If the job being canceled was at the head of the timeouts queue, then we need to reschedule the timer
\r
1102 * with the next job timeout */
\r
1103 IotLink_t * pHeadLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList );
\r
1105 if( pHeadLink == pTimerEventLink )
\r
1107 shouldReschedule = true;
\r
1110 /* Remove the timer event associated with the canceled job and free the associated memory. */
\r
1111 IotListDouble_Remove( pTimerEventLink );
\r
1112 IotTaskPool_FreeTimerEvent( IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ) );
\r
1114 if( shouldReschedule )
\r
1116 IotLink_t * pNextTimerEventLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList );
\r
1118 if( pNextTimerEventLink != NULL )
\r
1120 _rescheduleDeferredJobsTimer( pTaskPool->timer, IotLink_Container( _taskPoolTimerEvent_t, pNextTimerEventLink, link ) );
\r
1127 /* A cancelable job status should be either 'scheduled' or 'deferrred'. */
\r
1128 IotTaskPool_Assert( ( currentStatus == IOT_TASKPOOL_STATUS_READY ) || ( currentStatus == IOT_TASKPOOL_STATUS_CANCELED ) );
\r
1132 TASKPOOL_NO_FUNCTION_CLEANUP();
\r
1135 /*-----------------------------------------------------------*/
\r
1137 static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1,
\r
1138 const IotLink_t * const pTimerEventLink2 )
\r
1140 const _taskPoolTimerEvent_t * const pTimerEvent1 = IotLink_Container( _taskPoolTimerEvent_t,
\r
1143 const _taskPoolTimerEvent_t * const pTimerEvent2 = IotLink_Container( _taskPoolTimerEvent_t,
\r
1147 if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime )
\r
1152 if( pTimerEvent1->expirationTime > pTimerEvent2->expirationTime )
\r
1160 /*-----------------------------------------------------------*/
\r
1162 static void _rescheduleDeferredJobsTimer( TimerHandle_t const timer,
\r
1163 _taskPoolTimerEvent_t * const pFirstTimerEvent )
\r
1165 uint64_t delta = 0;
\r
1166 TickType_t now = xTaskGetTickCount();
\r
1168 if( pFirstTimerEvent->expirationTime > now )
\r
1170 delta = pFirstTimerEvent->expirationTime - now;
\r
1173 if( delta < TASKPOOL_JOB_RESCHEDULE_DELAY_MS )
\r
1175 delta = TASKPOOL_JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */
\r
1178 IotTaskPool_Assert( delta > 0 );
\r
1180 if( xTimerChangePeriod( timer, ( uint32_t ) delta, portMAX_DELAY ) == pdFAIL )
\r
1182 IotLogWarn( "Failed to re-arm timer for task pool" );
\r
1186 /*-----------------------------------------------------------*/
\r
1188 static void _timerCallback( TimerHandle_t xTimer )
\r
1190 _taskPool_t * pTaskPool = pvTimerGetTimerID( xTimer );
\r
1192 IotTaskPool_Assert( pTaskPool );
\r
1194 _taskPoolTimerEvent_t * pTimerEvent = NULL;
\r
1196 IotLogDebug( "Timer thread started for task pool %p.", pTaskPool );
\r
1198 /* Attempt to lock the timer mutex. Return immediately if the mutex cannot be locked.
\r
1199 * If this mutex cannot be locked it means that another thread is manipulating the
\r
1200 * timeouts list, and will reset the timer to fire again, although it will be late.
\r
1202 taskENTER_CRITICAL();
\r
1204 /* Dispatch all deferred job whose timer expired, then reset the timer for the next
\r
1205 * job down the line. */
\r
1208 /* Peek the first event in the timer event list. */
\r
1209 IotLink_t * pLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList );
\r
1211 /* Check if the timer misfired for any reason. */
\r
1212 if( pLink != NULL )
\r
1214 /* Record the current time. */
\r
1215 TickType_t now = xTaskGetTickCount();
\r
1217 /* Extract the job from its envelope. */
\r
1218 pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link );
\r
1220 /* Check if the first event should be processed now. */
\r
1221 if( pTimerEvent->expirationTime <= now )
\r
1223 /* Remove the timer event for immediate processing. */
\r
1224 IotListDouble_Remove( &( pTimerEvent->link ) );
\r
1228 /* The first element in the timer queue shouldn't be processed yet.
\r
1229 * Arm the timer for when it should be processed and leave altogether. */
\r
1230 _rescheduleDeferredJobsTimer( pTaskPool->timer, pTimerEvent );
\r
1235 /* If there are no timer events to process, terminate this thread. */
\r
1238 IotLogDebug( "No further timer events to process. Exiting timer thread." );
\r
1243 IotLogDebug( "Scheduling job from timer event." );
\r
1245 /* Queue the job associated with the received timer event. */
\r
1246 ( void ) _scheduleInternal( pTaskPool, pTimerEvent->job );
\r
1248 /* Free the timer event. */
\r
1249 IotTaskPool_FreeTimerEvent( pTimerEvent );
\r
1252 taskEXIT_CRITICAL();
\r