Loading...
Searching...
No Matches
coap_threadsafe_internal.h
Go to the documentation of this file.
/*
* coap_threadsafe_internal.h -- Mapping of threadsafe functions
*
* Copyright (C) 2023-2024 Jon Shallow <supjps-libcoap@jpshallow.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*
* This file is part of the CoAP library libcoap. Please see README for terms
* of use.
*/
#ifndef COAP_THREADSAFE_INTERNAL_H_
#define COAP_THREADSAFE_INTERNAL_H_
/*
* Support thread safe access into libcoap
*
* Locking at different component levels (i.e context and session) is
* problematic in that coap_process_io() needs to lock the context as
* it scans for all the sessions and then could lock the session being
* processed as well - but context needs to remain locked as a list is
* being scanned.
*
* Then if the session process needs to update context ( e.g. delayqueue),
* context needs to be locked. So, if coap_send() is done on a session,
* it has to be locked, but a retransmission of a PDU by coap_process_io()
* has the context already locked.
*
* However, when the context is going away (coap_free_context()), other
* threads may still be access the lock in what is now freed memory.
* A solution (by flagging being freed), worked, but still with a timing
* window wen the context was finally de-allocated. Coverity Scan did
* not like the solution.
*
* So the initial support for thread safe is done at global lock level
* using global_lock. However, context is provided as a parameter should
* context level locking be subsequently used.
*
* Any public API call needs to potentially lock global_lock.
*
* If a public API needs thread safe protection, the coap_X() function
* locks the global_lock lock, calls the coap_X_lkd() function
* that does all the work and on return unlocks the global_lock before
* returning to the caller of coap_X(). These coap_X() functions
* need COAP_API in their definitions.
*
* Any internal libcoap calls that are to the public API coap_X() must call
* coap_X_lkd() if the calling code is already locked.
* [The compiler will throw out a deprecation warning against any internal
* libcoap call to a COAP_API labelled function]
*
* Any call-back into app space must be done by using the coap_lock_callback()
* (or coap_lock_callback_ret()) wrapper where the global_lock remains locked.
*
* Note:
* libcoap may call a handler, which may in turn call into libcoap, which may
* then call a handler. global_lock will remain locked thoughout this process
* by the same thread.
*
* Alternatively, coap_lock_callback_release() (or
* coap_lock_callback_ret_release()), is used where the global_lock is unlocked
* for the duration of the call-back. Used for things like a request
* handler which could be busy for some time.
*
* Note: On return from the call-back, the code has to be careful not to
* use memory locations that may have been updated in the call-back by
* calling a Public API.
*
* Any wait on select() or equivalent when a thread is waiting on an event
* must be preceded by unlock global_lock, and then global_lock re-locked after
* return;
*
* To check for recursive deadlock coding errors, COAP_THREAD_RECURSIVE_CHECK
* needs to be defined.
*
* If thread safe is not enabled, then locking of the global_lock does not take
* place.
*/
#if COAP_THREAD_SAFE
# if COAP_THREAD_RECURSIVE_CHECK
/*
* Locking, with deadlock detection
*/
typedef struct coap_lock_t {
coap_mutex_t mutex;
coap_thread_pid_t pid;
const char *lock_file;
unsigned int lock_line;
unsigned int unlock_line;
const char *unlock_file;
const char *callback_file;
unsigned int callback_line;
unsigned int in_callback;
unsigned int lock_count;
} coap_lock_t;
void coap_lock_unlock_func(const char *file, int line);
int coap_lock_lock_func(const char *file, int line);
#define coap_lock_lock(c,failed) do { \
if (!coap_lock_lock_func(__FILE__, __LINE__)) { \
failed; \
} \
} while (0)
#define coap_lock_unlock(c) do { \
coap_lock_unlock_func(__FILE__, __LINE__); \
} while (0)
#define coap_lock_callback(c,func) do { \
coap_lock_check_locked(c); \
global_lock.in_callback++; \
global_lock.callback_file = __FILE__; \
global_lock.callback_line = __LINE__; \
func; \
global_lock.in_callback--; \
} while (0)
#define coap_lock_callback_ret(r,c,func) do { \
coap_lock_check_locked(c); \
global_lock.in_callback++; \
global_lock.callback_file = __FILE__; \
global_lock.callback_line = __LINE__; \
(r) = func; \
global_lock.in_callback--; \
} while (0)
#define coap_lock_callback_release(c,func,failed) do { \
coap_lock_check_locked(c); \
coap_lock_unlock(c); \
func; \
coap_lock_lock(c,failed); \
} while (0)
#define coap_lock_callback_ret_release(r,c,func,failed) do { \
coap_lock_check_locked(c); \
coap_lock_unlock(c); \
(r) = func; \
coap_lock_lock(c,failed); \
} while (0)
extern coap_lock_t global_lock;
# else /* ! COAP_THREAD_RECURSIVE_CHECK */
/*
* Locking, but no deadlock detection
*/
typedef struct coap_lock_t {
coap_mutex_t mutex;
coap_thread_pid_t pid;
uint32_t in_callback;
volatile uint32_t lock_count;
} coap_lock_t;
void coap_lock_unlock_func(void);
int coap_lock_lock_func(void);
#define coap_lock_lock(c,failed) do { \
if (!coap_lock_lock_func()) { \
failed; \
} \
} while (0)
#define coap_lock_unlock(c) do { \
assert(c); \
coap_lock_unlock_func(); \
} while (0)
#define coap_lock_callback(c,func) do { \
coap_lock_check_locked(c); \
global_lock.in_callback++; \
func; \
global_lock.in_callback--; \
} while (0)
#define coap_lock_callback_ret(r,c,func) do { \
coap_lock_check_locked(c); \
global_lock.in_callback++; \
global_lock.in_callback++; \
(r) = func; \
global_lock.in_callback--; \
} while (0)
#define coap_lock_callback_release(c,func,failed) do { \
coap_lock_check_locked(c); \
coap_lock_unlock(c); \
func; \
coap_lock_lock(c,failed); \
} while (0)
#define coap_lock_callback_ret_release(r,c,func,failed) do { \
coap_lock_check_locked(c); \
coap_lock_unlock(c); \
(r) = func; \
coap_lock_lock(c,failed); \
} while (0)
# endif /* ! COAP_THREAD_RECURSIVE_CHECK */
#define coap_lock_init() do { \
memset(&global_lock.mutex, 0, sizeof(global_lock.mutex)); \
coap_mutex_init(&global_lock.mutex); \
} while (0)
#define coap_lock_check_locked(c) do { \
assert(coap_thread_pid == global_lock.pid); \
} while (0)
#define coap_lock_invert(c,alt_lock,failed) do { \
coap_lock_check_locked(c); \
coap_lock_unlock(c); \
alt_lock; \
coap_lock_lock(c,failed); \
} while (0)
extern coap_lock_t global_lock;
#else /* ! COAP_THREAD_SAFE */
/*
* No locking - single thread
*/
typedef coap_mutex_t coap_lock_t;
#define coap_lock_lock(c,failed)
#define coap_lock_unlock(c)
#define coap_lock_init()
#define coap_lock_check_locked(c) {}
#define coap_lock_callback(c,func) func
#define coap_lock_callback_ret(r,c,func) (r) = func
#define coap_lock_callback_release(c,func,failed) func
#define coap_lock_callback_ret_release(r,c,func,failed) (r) = func
#define coap_lock_invert(c,alt_lock,failed) func
#endif /* ! COAP_THREAD_SAFE */
#endif /* COAP_THREADSAFE_INTERNAL_H_ */
coap_mutex_t
int coap_mutex_t
Definition coap_mutex_internal.h:141
coap_thread_pid_t
#define coap_thread_pid_t
Definition coap_mutex_internal.h:148
coap_lock_t
coap_mutex_t coap_lock_t
Definition coap_threadsafe_internal.h:445