Skip to content

Commit e56f416

Browse files
author
Alexei Starovoitov
committed
Merge branch 'task-local-data'
Amery Hung says: ==================== Task local data v6 -> v7 - Fix typos and improve the clarity of the cover letter (Emil) - Some variable naming changes to make it less confusing (Emil) - Add retry in tld_obj_init() as bpf_task_storage_get_recur() with BPF_LOCAL_STORAGE_GET_F_CREATE can fail if the task local storage is also being modified by other threads on the same CPU. This can be especially easy to trigger in CI VM that only has two CPUs. Adding bpf_preempt_{disable,enable} around bpf_task_storage_get() does not solve the problem as other threads competing for the percpu counter lock in task local storage may not limit to bpf helpers. Some may be calling bpf_task_storage_free() when threads exit. There is no good max retry under heavy contention. For the 1st selftest in this set, the max retry to guarantee success grows linearly with the thread count. A proposal is to remove the percpu counter by changing locks in bpf_local_storage to rqspinlock. An alternative is to reduce the probability of failure by allowing bpf syscall and struct_ops programs to use bpf_task_storage_get() instead of the _recur version by modifying bpf_prog_check_recur(). This does not solve the problem in tracing programs though. v6: https://lore.kernel.org/bpf/[email protected]/ * Overview * This patchset introduces task local data, an abstract storage type shared between user space and bpf programs for storing data specific to each task, and implements a library to access it. The goal is to provide an abstraction + implementation that is efficient in data sharing and easy to use. The motivating use case is user space hinting with sched_ext. * Motivating use case * CPU schedulers can potentially make a better decision with hints from user space process. To support experimenting user space hinting with sched_ext, there needs a mechanism to pass a "per-task hint" from user space to the bpf scheduler "efficiently". A bpf feature, UPTR [0], supported by task local storage is introduced to serve the needs. It allows pinning a user space page to the kernel through a special field in task local storage map. This patchset further builds an abstraction on top of it to allow user space and bpf programs to easily share per-task data. * Design * Task local data is built on top of task local storage map and UPTR to achieve fast per-task data sharing. UPTR is a type of special field supported in task local storage map value. A user page assigned to a UPTR will be pinned to the kernel when the map is updated. Therefore, user space programs can update data that will be seen by bpf programs without syscalls. Additionally, unlike most bpf maps, task local data does not require a static map value definition. This design is driven by sched_ext, which would like to allow multiple developers to share a storage without the need to explicitly agree on the layout of it. While a centralized layout definition would have worked, the friction of synchronizing it across different repos is not desirable. This simplify code base management and makes experimenting easier. * Introduction to task local data library * Task local data library provides simple APIs for user space and bpf through two header files, task_local_data.h and task_local_data.bpf.h, respectively. The diagram below illustrates the basic usage. First, an entry of data in task local data, we call it a TLD, needs to be defined in the user space through TLD_DEFINE_KEY() with information including the size and the name. The macro will define a global variable key of opaque type tld_key_t associated with the TLD and initialize it. Then, the user space program can locate the TLD by passing the key to tld_get_data() in different thread, where fd is the file descriptor of the underlying task local storage map. The API returns a pointer to the TLD specific to the calling thread and will remain valid until the thread exits. Finally, user space programs can directly read/write the TLD without bpf syscalls. To use task local storage on the bpf side, struct tld_keys, needs to be defined first. The structure that will be used to create tld_key_map should contain the keys to the TLDs used in a bpf program. In the bpf program, tld_init_object() first needs to be called to initialize a struct tld_object variable on the stack. Then, tld_get_data() can be called to get a pointer to the TLD similar to the user space. The API will use the name to lookup task local data and cache the key in task local storage map, tld_key_map, to speed up subsequent access. The size of the TLD is also needed to prevent out-of-bound access in bpf. ┌─ Application ───────────────────────────────────────────────────────┐ │ TLD_DEFINE_KEY(kx, "X", 4); ┌─ library A ─────────────────────┐│ │ │ void func(...) ││ │ int main(...) │ { ││ │ { │ tld_key_t ky; ││ │ int *x; │ bool *y; ││ │ │ ││ │ x = tld_get_data(fd, kx); │ ky = tld_create_key("Y", 1);││ │ if (x) *x = 123; │ y = tld_get_data(fd, ky); ││ │ ┌────────┤ if (y) *y = true; ││ │ │ └─────────────────────────────────┘│ └───────┬─────────────────│───────────────────────────────────────────┘ V V + ─ Task local data ─ ─ ─ ─ ─ + ┌─ BPF program ──────────────────────┐ | ┌─ tld_data_map ──────────┐ | │ struct tld_object obj; │ | │ BPF Task local storage │ | │ bool *y; │ | │ │ | │ int *x; │ | │ tld_data_u __uptr *data │ | │ │ | │ tld_meta_u __uptr *meta │ | │ if (tld_init_object(task, &obj)) │ | └─────────────────────────┘ | │ return 0; │ | ┌─ tld_key_map ───────────┐ | │ │ | │ BPF Task local storage │ | │ x = tld_get_data(&obj, kx, "X", 4);│ | │ │ |<─┤ if (x) /* do something */ │ | │ tld_key_t kx; │ | │ │ | │ tld_key_t ky; │ | │ y = tld_get_data(&obj, ky, "Y", 1);│ | └─────────────────────────┘ | │ if (y) /* do something */ │ + ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + └────────────────────────────────────┘ * Implementation * Task local data defines the storage to be a task local storage map with two UPTRs pointing to tld_data_u and tld_meta_u. tld_data_u, individual to each thread, contains TLD data and the starting offset of data in a page. tld_meta_u, shared by threads in a process, consists of the TLD counts, total size of TLDs and tld_metadata for each TLD. tld_metadata contains the name and the size of a TLD. struct tld_data_u { u64 start; char data[PAGE_SIZE - sizeof(u64)]; }; struct tld_meta_u { u8 cnt; u16 size; struct metadata metadata[TLD_DATA_CNT]; }; Both user space and bpf API follow the same protocol when accessing task local data. A pointer to a TLD is located using a key. The key is effectively the offset of a TLD in tld_data_u->data. To define a new TLD, the user space API TLD_DEFINE_KEY() iterates tld_meta_u->metadata until an empty slot is found and then update it. It also adds up sizes of prior TLDs to derive the offset (i.e., key). Then, to locate a TLD, tld_get_data() can simply return tld_data_u->data + offset. To locate a TLD in bpf programs, an API with the same name as the user space, tld_get_data() can be called. It takes more effort in the first invocation as it fetches the key by name. Internal helper, __tld_fetch_key() will iterate tld_meta_u->metadata until the name is found. Then, the offset is cached as key in another task local storage map, tld_key_map. When the search fails, the current TLD count is cached instead to skip searching metadata entries that has been searched in future invocation. The detail of task local data operations can be found in patch 1. * Misc * The metadata can potentially use run-length encoding for names to reduce memory wastage and support save more TLDs. I have a version that works, but the selftest takes a bit longer to finish. More investigation needed to find the root cause. I will save this for the future when there is a need to store more than 63 TLDs. [0] https://lore.kernel.org/bpf/[email protected]/ --- v5 -> v6 - Address Andrii's comment - Fix verification failure in no_alu32 - Some cleanup v5: https://lore.kernel.org/bpf/[email protected]/ v4 -> v5 - Add an option to free memory on thread exit to prevent memory leak - Add an option to reduce memory waste if the allocator can use just enough memory to fullfill aligned_alloc() (e.g., glibc) - Tweak bpf API - Remove tld_fetch_key() as it does not work in init_tasl - tld_get_data() now tries to fetch key if it is not cached yet - Optimize bpf side tld_get_data() - Faster fast path - Less code - Use stdatomic.h in user space library with seq_cst order - Introduce TLD_DEFINE_KEY() as the default TLD creation API for easier memory management. - TLD_DEFINE_KEY() can consume memory up to a page and no memory is wasted since their size is known before per-thread data allocation. - tld_create_key() can only use up to TLD_DYN_DATA_SIZE. Since tld_create_key can run any time even after per-thread data allocation, it is impossible to predict the total size. A configurable size of memory is allocated on top of the total size of TLD_DEFINE_KEY() to accommodate dynamic key creation. - Add tld prefix to all macros - Replace map_update(NO_EXIST) in __tld_init_data() with cmpxchg() - No more +1,-1 dance on the bpf side - Reduce printf from ASSERT in race test - Try implementing run-length encoding for name and decide to save it for the future v4: https://lore.kernel.org/bpf/[email protected]/ v3 -> v4 - API improvements - Simplify API - Drop string obfuscation - Use opaque type for key - Better documentation - Implementation - Switch to dynamic allocation for per-task data - Now offer as header-only libraries - No TLS map pinning; leave it to users - Drop pthread dependency - Add more invalid tld_create_key() test - Add a race test for tld_create_key() v3: https://lore.kernel.org/bpf/[email protected]/ --- ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
2 parents a6923c0 + 7841811 commit e56f416

File tree

5 files changed

+986
-0
lines changed

5 files changed

+986
-0
lines changed

include/linux/bpf_verifier.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ static inline bool bpf_prog_check_recur(const struct bpf_prog *prog)
962962
case BPF_PROG_TYPE_STRUCT_OPS:
963963
return prog->aux->jits_use_priv_stack;
964964
case BPF_PROG_TYPE_LSM:
965+
case BPF_PROG_TYPE_SYSCALL:
965966
return false;
966967
default:
967968
return true;
Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef __TASK_LOCAL_DATA_H
3+
#define __TASK_LOCAL_DATA_H
4+
5+
#include <errno.h>
6+
#include <fcntl.h>
7+
#include <sched.h>
8+
#include <stdatomic.h>
9+
#include <stddef.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include <unistd.h>
13+
#include <sys/syscall.h>
14+
#include <sys/types.h>
15+
16+
#ifdef TLD_FREE_DATA_ON_THREAD_EXIT
17+
#include <pthread.h>
18+
#endif
19+
20+
#include <bpf/bpf.h>
21+
22+
/*
23+
* OPTIONS
24+
*
25+
* Define the option before including the header
26+
*
27+
* TLD_FREE_DATA_ON_THREAD_EXIT - Frees memory on thread exit automatically
28+
*
29+
* Thread-specific memory for storing TLD is allocated lazily on the first call to
30+
* tld_get_data(). The thread that calls it must also call tld_free() on thread exit
31+
* to prevent memory leak. Pthread will be included if the option is defined. A pthread
32+
* key will be registered with a destructor that calls tld_free().
33+
*
34+
*
35+
* TLD_DYN_DATA_SIZE - The maximum size of memory allocated for TLDs created dynamically
36+
* (default: 64 bytes)
37+
*
38+
* A TLD can be defined statically using TLD_DEFINE_KEY() or created on the fly using
39+
* tld_create_key(). As the total size of TLDs created with tld_create_key() cannot be
40+
* possibly known statically, a memory area of size TLD_DYN_DATA_SIZE will be allocated
41+
* for these TLDs. This additional memory is allocated for every thread that calls
42+
* tld_get_data() even if no tld_create_key are actually called, so be mindful of
43+
* potential memory wastage. Use TLD_DEFINE_KEY() whenever possible as just enough memory
44+
* will be allocated for TLDs created with it.
45+
*
46+
*
47+
* TLD_NAME_LEN - The maximum length of the name of a TLD (default: 62)
48+
*
49+
* Setting TLD_NAME_LEN will affect the maximum number of TLDs a process can store,
50+
* TLD_MAX_DATA_CNT.
51+
*
52+
*
53+
* TLD_DATA_USE_ALIGNED_ALLOC - Always use aligned_alloc() instead of malloc()
54+
*
55+
* When allocating the memory for storing TLDs, we need to make sure there is a memory
56+
* region of the X bytes within a page. This is due to the limit posed by UPTR: memory
57+
* pinned to the kernel cannot exceed a page nor can it cross the page boundary. The
58+
* library normally calls malloc(2*X) given X bytes of total TLDs, and only uses
59+
* aligned_alloc(PAGE_SIZE, X) when X >= PAGE_SIZE / 2. This is to reduce memory wastage
60+
* as not all memory allocator can use the exact amount of memory requested to fulfill
61+
* aligned_alloc(). For example, some may round the size up to the alignment. Enable the
62+
* option to always use aligned_alloc() if the implementation has low memory overhead.
63+
*/
64+
65+
#define TLD_PAGE_SIZE getpagesize()
66+
#define TLD_PAGE_MASK (~(TLD_PAGE_SIZE - 1))
67+
68+
#define TLD_ROUND_MASK(x, y) ((__typeof__(x))((y) - 1))
69+
#define TLD_ROUND_UP(x, y) ((((x) - 1) | TLD_ROUND_MASK(x, y)) + 1)
70+
71+
#define TLD_READ_ONCE(x) (*(volatile typeof(x) *)&(x))
72+
73+
#ifndef TLD_DYN_DATA_SIZE
74+
#define TLD_DYN_DATA_SIZE 64
75+
#endif
76+
77+
#define TLD_MAX_DATA_CNT (TLD_PAGE_SIZE / sizeof(struct tld_metadata) - 1)
78+
79+
#ifndef TLD_NAME_LEN
80+
#define TLD_NAME_LEN 62
81+
#endif
82+
83+
#ifdef __cplusplus
84+
extern "C" {
85+
#endif
86+
87+
typedef struct {
88+
__s16 off;
89+
} tld_key_t;
90+
91+
struct tld_metadata {
92+
char name[TLD_NAME_LEN];
93+
_Atomic __u16 size;
94+
};
95+
96+
struct tld_meta_u {
97+
_Atomic __u8 cnt;
98+
__u16 size;
99+
struct tld_metadata metadata[];
100+
};
101+
102+
struct tld_data_u {
103+
__u64 start; /* offset of tld_data_u->data in a page */
104+
char data[];
105+
};
106+
107+
struct tld_map_value {
108+
void *data;
109+
struct tld_meta_u *meta;
110+
};
111+
112+
struct tld_meta_u * _Atomic tld_meta_p __attribute__((weak));
113+
__thread struct tld_data_u *tld_data_p __attribute__((weak));
114+
__thread void *tld_data_alloc_p __attribute__((weak));
115+
116+
#ifdef TLD_FREE_DATA_ON_THREAD_EXIT
117+
pthread_key_t tld_pthread_key __attribute__((weak));
118+
119+
static void tld_free(void);
120+
121+
static void __tld_thread_exit_handler(void *unused)
122+
{
123+
tld_free();
124+
}
125+
#endif
126+
127+
static int __tld_init_meta_p(void)
128+
{
129+
struct tld_meta_u *meta, *uninit = NULL;
130+
int err = 0;
131+
132+
meta = (struct tld_meta_u *)aligned_alloc(TLD_PAGE_SIZE, TLD_PAGE_SIZE);
133+
if (!meta) {
134+
err = -ENOMEM;
135+
goto out;
136+
}
137+
138+
memset(meta, 0, TLD_PAGE_SIZE);
139+
meta->size = TLD_DYN_DATA_SIZE;
140+
141+
if (!atomic_compare_exchange_strong(&tld_meta_p, &uninit, meta)) {
142+
free(meta);
143+
goto out;
144+
}
145+
146+
#ifdef TLD_FREE_DATA_ON_THREAD_EXIT
147+
pthread_key_create(&tld_pthread_key, __tld_thread_exit_handler);
148+
#endif
149+
out:
150+
return err;
151+
}
152+
153+
static int __tld_init_data_p(int map_fd)
154+
{
155+
bool use_aligned_alloc = false;
156+
struct tld_map_value map_val;
157+
struct tld_data_u *data;
158+
void *data_alloc = NULL;
159+
int err, tid_fd = -1;
160+
161+
tid_fd = syscall(SYS_pidfd_open, gettid(), O_EXCL);
162+
if (tid_fd < 0) {
163+
err = -errno;
164+
goto out;
165+
}
166+
167+
#ifdef TLD_DATA_USE_ALIGNED_ALLOC
168+
use_aligned_alloc = true;
169+
#endif
170+
171+
/*
172+
* tld_meta_p->size = TLD_DYN_DATA_SIZE +
173+
* total size of TLDs defined via TLD_DEFINE_KEY()
174+
*/
175+
data_alloc = (use_aligned_alloc || tld_meta_p->size * 2 >= TLD_PAGE_SIZE) ?
176+
aligned_alloc(TLD_PAGE_SIZE, tld_meta_p->size) :
177+
malloc(tld_meta_p->size * 2);
178+
if (!data_alloc) {
179+
err = -ENOMEM;
180+
goto out;
181+
}
182+
183+
/*
184+
* Always pass a page-aligned address to UPTR since the size of tld_map_value::data
185+
* is a page in BTF. If data_alloc spans across two pages, use the page that contains large
186+
* enough memory.
187+
*/
188+
if (TLD_PAGE_SIZE - (~TLD_PAGE_MASK & (intptr_t)data_alloc) >= tld_meta_p->size) {
189+
map_val.data = (void *)(TLD_PAGE_MASK & (intptr_t)data_alloc);
190+
data = data_alloc;
191+
data->start = (~TLD_PAGE_MASK & (intptr_t)data_alloc) +
192+
offsetof(struct tld_data_u, data);
193+
} else {
194+
map_val.data = (void *)(TLD_ROUND_UP((intptr_t)data_alloc, TLD_PAGE_SIZE));
195+
data = (void *)(TLD_ROUND_UP((intptr_t)data_alloc, TLD_PAGE_SIZE));
196+
data->start = offsetof(struct tld_data_u, data);
197+
}
198+
map_val.meta = TLD_READ_ONCE(tld_meta_p);
199+
200+
err = bpf_map_update_elem(map_fd, &tid_fd, &map_val, 0);
201+
if (err) {
202+
free(data_alloc);
203+
goto out;
204+
}
205+
206+
tld_data_p = data;
207+
tld_data_alloc_p = data_alloc;
208+
#ifdef TLD_FREE_DATA_ON_THREAD_EXIT
209+
pthread_setspecific(tld_pthread_key, (void *)1);
210+
#endif
211+
out:
212+
if (tid_fd >= 0)
213+
close(tid_fd);
214+
return err;
215+
}
216+
217+
static tld_key_t __tld_create_key(const char *name, size_t size, bool dyn_data)
218+
{
219+
int err, i, sz, off = 0;
220+
__u8 cnt;
221+
222+
if (!TLD_READ_ONCE(tld_meta_p)) {
223+
err = __tld_init_meta_p();
224+
if (err)
225+
return (tld_key_t){err};
226+
}
227+
228+
for (i = 0; i < TLD_MAX_DATA_CNT; i++) {
229+
retry:
230+
cnt = atomic_load(&tld_meta_p->cnt);
231+
if (i < cnt) {
232+
/* A metadata is not ready until size is updated with a non-zero value */
233+
while (!(sz = atomic_load(&tld_meta_p->metadata[i].size)))
234+
sched_yield();
235+
236+
if (!strncmp(tld_meta_p->metadata[i].name, name, TLD_NAME_LEN))
237+
return (tld_key_t){-EEXIST};
238+
239+
off += TLD_ROUND_UP(sz, 8);
240+
continue;
241+
}
242+
243+
/*
244+
* TLD_DEFINE_KEY() is given memory upto a page while at most
245+
* TLD_DYN_DATA_SIZE is allocated for tld_create_key()
246+
*/
247+
if (dyn_data) {
248+
if (off + TLD_ROUND_UP(size, 8) > tld_meta_p->size)
249+
return (tld_key_t){-E2BIG};
250+
} else {
251+
if (off + TLD_ROUND_UP(size, 8) > TLD_PAGE_SIZE - sizeof(struct tld_data_u))
252+
return (tld_key_t){-E2BIG};
253+
tld_meta_p->size += TLD_ROUND_UP(size, 8);
254+
}
255+
256+
/*
257+
* Only one tld_create_key() can increase the current cnt by one and
258+
* takes the latest available slot. Other threads will check again if a new
259+
* TLD can still be added, and then compete for the new slot after the
260+
* succeeding thread update the size.
261+
*/
262+
if (!atomic_compare_exchange_strong(&tld_meta_p->cnt, &cnt, cnt + 1))
263+
goto retry;
264+
265+
strncpy(tld_meta_p->metadata[i].name, name, TLD_NAME_LEN);
266+
atomic_store(&tld_meta_p->metadata[i].size, size);
267+
return (tld_key_t){(__s16)off};
268+
}
269+
270+
return (tld_key_t){-ENOSPC};
271+
}
272+
273+
/**
274+
* TLD_DEFINE_KEY() - Define a TLD and a global variable key associated with the TLD.
275+
*
276+
* @name: The name of the TLD
277+
* @size: The size of the TLD
278+
* @key: The variable name of the key. Cannot exceed TLD_NAME_LEN
279+
*
280+
* The macro can only be used in file scope.
281+
*
282+
* A global variable key of opaque type, tld_key_t, will be declared and initialized before
283+
* main() starts. Use tld_key_is_err() or tld_key_err_or_zero() later to check if the key
284+
* creation succeeded. Pass the key to tld_get_data() to get a pointer to the TLD.
285+
* bpf programs can also fetch the same key by name.
286+
*
287+
* The total size of TLDs created using TLD_DEFINE_KEY() cannot exceed a page. Just
288+
* enough memory will be allocated for each thread on the first call to tld_get_data().
289+
*/
290+
#define TLD_DEFINE_KEY(key, name, size) \
291+
tld_key_t key; \
292+
\
293+
__attribute__((constructor)) \
294+
void __tld_define_key_##key(void) \
295+
{ \
296+
key = __tld_create_key(name, size, false); \
297+
}
298+
299+
/**
300+
* tld_create_key() - Create a TLD and return a key associated with the TLD.
301+
*
302+
* @name: The name the TLD
303+
* @size: The size of the TLD
304+
*
305+
* Return an opaque object key. Use tld_key_is_err() or tld_key_err_or_zero() to check
306+
* if the key creation succeeded. Pass the key to tld_get_data() to get a pointer to
307+
* locate the TLD. bpf programs can also fetch the same key by name.
308+
*
309+
* Use tld_create_key() only when a TLD needs to be created dynamically (e.g., @name is
310+
* not known statically or a TLD needs to be created conditionally)
311+
*
312+
* An additional TLD_DYN_DATA_SIZE bytes are allocated per-thread to accommodate TLDs
313+
* created dynamically with tld_create_key(). Since only a user page is pinned to the
314+
* kernel, when TLDs created with TLD_DEFINE_KEY() uses more than TLD_PAGE_SIZE -
315+
* TLD_DYN_DATA_SIZE, the buffer size will be limited to the rest of the page.
316+
*/
317+
__attribute__((unused))
318+
static tld_key_t tld_create_key(const char *name, size_t size)
319+
{
320+
return __tld_create_key(name, size, true);
321+
}
322+
323+
__attribute__((unused))
324+
static inline bool tld_key_is_err(tld_key_t key)
325+
{
326+
return key.off < 0;
327+
}
328+
329+
__attribute__((unused))
330+
static inline int tld_key_err_or_zero(tld_key_t key)
331+
{
332+
return tld_key_is_err(key) ? key.off : 0;
333+
}
334+
335+
/**
336+
* tld_get_data() - Get a pointer to the TLD associated with the given key of the
337+
* calling thread.
338+
*
339+
* @map_fd: A file descriptor of tld_data_map, the underlying BPF task local storage map
340+
* of task local data.
341+
* @key: A key object created by TLD_DEFINE_KEY() or tld_create_key().
342+
*
343+
* Return a pointer to the TLD if the key is valid; NULL if not enough memory for TLD
344+
* for this thread, or the key is invalid. The returned pointer is guaranteed to be 8-byte
345+
* aligned.
346+
*
347+
* Threads that call tld_get_data() must call tld_free() on exit to prevent
348+
* memory leak if TLD_FREE_DATA_ON_THREAD_EXIT is not defined.
349+
*/
350+
__attribute__((unused))
351+
static void *tld_get_data(int map_fd, tld_key_t key)
352+
{
353+
if (!TLD_READ_ONCE(tld_meta_p))
354+
return NULL;
355+
356+
/* tld_data_p is allocated on the first invocation of tld_get_data() */
357+
if (!tld_data_p && __tld_init_data_p(map_fd))
358+
return NULL;
359+
360+
return tld_data_p->data + key.off;
361+
}
362+
363+
/**
364+
* tld_free() - Free task local data memory of the calling thread
365+
*
366+
* For the calling thread, all pointers to TLDs acquired before will become invalid.
367+
*
368+
* Users must call tld_free() on thread exit to prevent memory leak. Alternatively,
369+
* define TLD_FREE_DATA_ON_THREAD_EXIT and a thread exit handler will be registered
370+
* to free the memory automatically.
371+
*/
372+
__attribute__((unused))
373+
static void tld_free(void)
374+
{
375+
if (tld_data_alloc_p) {
376+
free(tld_data_alloc_p);
377+
tld_data_alloc_p = NULL;
378+
tld_data_p = NULL;
379+
}
380+
}
381+
382+
#ifdef __cplusplus
383+
} /* extern "C" */
384+
#endif
385+
386+
#endif /* __TASK_LOCAL_DATA_H */

0 commit comments

Comments
 (0)