/ components / pthread / pthread_local_storage.c
pthread_local_storage.c
  1  // Copyright 2017 Espressif Systems (Shanghai) PTE LTD
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  #include <errno.h>
 15  #include <pthread.h>
 16  #include <string.h>
 17  #include "esp_err.h"
 18  #include "esp_log.h"
 19  #include "freertos/FreeRTOS.h"
 20  #include "freertos/task.h"
 21  #include "sys/lock.h"
 22  #include "sys/queue.h"
 23  
 24  #include "pthread_internal.h"
 25  
 26  #define PTHREAD_TLS_INDEX 0
 27  
 28  typedef void (*pthread_destructor_t)(void*);
 29  
 30  /* This is a very naive implementation of key-indexed thread local storage, using two linked lists
 31     (one is a global list of registered keys, one per thread for thread local storage values).
 32  
 33     It won't work well if lots of keys & thread-local values are stored (O(n) lookup for both),
 34     but it should work for small amounts of data.
 35  */
 36  typedef struct key_entry_t_ {
 37      pthread_key_t key;
 38      pthread_destructor_t destructor;
 39      SLIST_ENTRY(key_entry_t_) next;
 40  } key_entry_t;
 41  
 42  // List of all keys created with pthread_key_create()
 43  SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys);
 44  
 45  static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED;
 46  
 47  // List of all value entries associated with a thread via pthread_setspecific()
 48  typedef struct value_entry_t_ {
 49      pthread_key_t key;
 50      void *value;
 51      SLIST_ENTRY(value_entry_t_) next;
 52  } value_entry_t;
 53  
 54  // Type for the head of the list, as saved as a FreeRTOS thread local storage pointer
 55  SLIST_HEAD(values_list_t_, value_entry_t_);
 56  typedef struct values_list_t_ values_list_t;
 57  
 58  int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor)
 59  {
 60      key_entry_t *new_key = malloc(sizeof(key_entry_t));
 61      if (new_key == NULL) {
 62          return ENOMEM;
 63      }
 64  
 65      portENTER_CRITICAL(&s_keys_lock);
 66  
 67      const key_entry_t *head = SLIST_FIRST(&s_keys);
 68      new_key->key = (head == NULL) ? 1 : (head->key + 1);
 69      new_key->destructor = destructor;
 70      *key = new_key->key;
 71  
 72      SLIST_INSERT_HEAD(&s_keys, new_key, next);
 73  
 74      portEXIT_CRITICAL(&s_keys_lock);
 75      return 0;
 76  }
 77  
 78  static key_entry_t *find_key(pthread_key_t key)
 79  {
 80      portENTER_CRITICAL(&s_keys_lock);
 81      key_entry_t *result = NULL;;
 82      SLIST_FOREACH(result, &s_keys, next) {
 83          if(result->key == key) {
 84              break;
 85          }
 86      }
 87      portEXIT_CRITICAL(&s_keys_lock);
 88      return result;
 89  }
 90  
 91  int pthread_key_delete(pthread_key_t key)
 92  {
 93  
 94      portENTER_CRITICAL(&s_keys_lock);
 95  
 96      /* Ideally, we would also walk all tasks' thread local storage value_list here
 97         and delete any values associated with this key. We do not do this...
 98      */
 99  
100      key_entry_t *entry = find_key(key);
101      if (entry != NULL) {
102          SLIST_REMOVE(&s_keys, entry, key_entry_t_, next);
103          free(entry);
104      }
105  
106      portEXIT_CRITICAL(&s_keys_lock);
107  
108      return 0;
109  }
110  
111  /* Clean up callback for deleted tasks.
112  
113     This is called from one of two places:
114  
115     If the thread was created via pthread_create() then it's called by pthread_task_func() when that thread ends,
116     and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted.
117  
118     For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted.
119  
120     (The reason for calling it early for pthreads is to keep the timing consistent with "normal" pthreads, so after
121     pthread_join() the task's destructors have all been called even if the idle task hasn't run cleanup yet.)
122  */
123  static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls)
124  {
125      values_list_t *tls = (values_list_t *)v_tls;
126      assert(tls != NULL);
127  
128      /* Walk the list, freeing all entries and calling destructors if they are registered */
129      value_entry_t *entry = SLIST_FIRST(tls);
130      while(entry != NULL) {
131          // This is a little slow, walking the linked list of keys once per value,
132          // but assumes that the thread's value list will have less entries
133          // than the keys list
134          key_entry_t *key = find_key(entry->key);
135          if (key != NULL && key->destructor != NULL) {
136              key->destructor(entry->value);
137          }
138          value_entry_t *next_entry = SLIST_NEXT(entry, next);
139          free(entry);
140          entry = next_entry;
141      }
142      free(tls);
143  }
144  
145  #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
146  /* Called from FreeRTOS task delete hook */
147  void pthread_local_storage_cleanup(TaskHandle_t task)
148  {
149      void *tls = pvTaskGetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX);
150      if (tls != NULL) {
151          pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
152          vTaskSetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX, NULL);
153      }
154  }
155  
156  void __real_vPortCleanUpTCB(void *tcb);
157  
158  /* If static task cleanup hook is defined then its applications responsibility to define `vPortCleanUpTCB`.
159     Here we are wrapping it, so that we can do pthread specific TLS cleanup and then invoke application
160     real specific `vPortCleanUpTCB` */
161  void __wrap_vPortCleanUpTCB(void *tcb)
162  {
163      pthread_local_storage_cleanup(tcb);
164      __real_vPortCleanUpTCB(tcb);
165  }
166  #endif
167  
168  /* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */
169  void pthread_internal_local_storage_destructor_callback(void)
170  {
171      void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
172      if (tls != NULL) {
173          pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
174          /* remove the thread-local-storage pointer to avoid the idle task cleanup
175             calling it again...
176          */
177  #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
178          vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL);
179  #else
180          vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
181                                                          PTHREAD_TLS_INDEX,
182                                                          NULL,
183                                                          NULL);
184  #endif
185      }
186  }
187  
188  static value_entry_t *find_value(const values_list_t *list, pthread_key_t key)
189  {
190      value_entry_t *result = NULL;;
191      SLIST_FOREACH(result, list, next) {
192          if(result->key == key) {
193              break;
194          }
195      }
196      return result;
197  }
198  
199  void *pthread_getspecific(pthread_key_t key)
200  {
201      values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
202      if (tls == NULL) {
203          return NULL;
204      }
205  
206      value_entry_t *entry = find_value(tls, key);
207      if(entry != NULL) {
208          return entry->value;
209      }
210      return NULL;
211  }
212  
213  int pthread_setspecific(pthread_key_t key, const void *value)
214  {
215      key_entry_t *key_entry = find_key(key);
216      if (key_entry == NULL) {
217          return ENOENT; // this situation is undefined by pthreads standard
218      }
219  
220      values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
221      if (tls == NULL) {
222          tls = calloc(1, sizeof(values_list_t));
223          if (tls == NULL) {
224              return ENOMEM;
225          }
226  #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
227          vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls);
228  #else
229          vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
230                                                          PTHREAD_TLS_INDEX,
231                                                          tls,
232                                                          pthread_local_storage_thread_deleted_callback);
233  #endif
234      }
235  
236      value_entry_t *entry = find_value(tls, key);
237      if (entry != NULL) {
238          if (value != NULL) {
239              // cast on next line is necessary as pthreads API uses
240              // 'const void *' here but elsewhere uses 'void *'
241              entry->value = (void *) value;
242          } else { // value == NULL, remove the entry
243              SLIST_REMOVE(tls, entry, value_entry_t_, next);
244              free(entry);
245          }
246      } else if (value != NULL) {
247          entry = malloc(sizeof(value_entry_t));
248          if (entry == NULL) {
249              return ENOMEM;
250          }
251          entry->key = key;
252          entry->value = (void *) value; // see note above about cast
253          SLIST_INSERT_HEAD(tls, entry, next);
254      }
255  
256      return 0;
257  }
258  
259  /* Hook function to force linking this file */
260  void pthread_include_pthread_local_storage_impl(void)
261  {
262  }