Cancellation points: Close up some logic to eliminte some race conditions.
This commit is contained in:
parent
e9d3c3362a
commit
bc3ca25cc7
@ -88,7 +88,7 @@ int close(int fd)
|
||||
|
||||
/* close() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
#if CONFIG_NFILE_DESCRIPTORS > 0
|
||||
/* Did we get a valid file descriptor? */
|
||||
|
@ -223,7 +223,7 @@ int fcntl(int fd, int cmd, ...)
|
||||
|
||||
/* fcntl() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Setup to access the variable argument list */
|
||||
|
||||
|
@ -122,7 +122,7 @@ int fsync(int fd)
|
||||
|
||||
/* fsync() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the file structure corresponding to the file descriptor. */
|
||||
|
||||
|
@ -105,7 +105,7 @@ int open(const char *path, int oflags, ...)
|
||||
|
||||
/* open() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* If the file is opened for creation, then get the mode bits */
|
||||
|
||||
|
@ -368,7 +368,7 @@ int poll(FAR struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
|
||||
/* poll() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* This semaphore is used for signaling and, hence, should not have
|
||||
* priority inheritance enabled.
|
||||
|
@ -145,7 +145,7 @@ ssize_t pread(int fd, FAR void *buf, size_t nbytes, off_t offset)
|
||||
|
||||
/* pread() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the file structure corresponding to the file descriptor. */
|
||||
|
||||
|
@ -143,7 +143,7 @@ ssize_t pwrite(int fd, FAR const void *buf, size_t nbytes, off_t offset)
|
||||
|
||||
/* pread() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the file structure corresponding to the file descriptor. */
|
||||
|
||||
@ -161,6 +161,6 @@ ssize_t pwrite(int fd, FAR const void *buf, size_t nbytes, off_t offset)
|
||||
ret = file_pwrite(filep, buf, nbytes, offset);
|
||||
}
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
return ret;
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ ssize_t read(int fd, FAR void *buf, size_t nbytes)
|
||||
|
||||
/* read() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Did we get a valid file descriptor? */
|
||||
|
||||
|
@ -113,7 +113,7 @@ int select(int nfds, FAR fd_set *readfds, FAR fd_set *writefds,
|
||||
|
||||
/* select() is cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* How many pollfd structures do we need to allocate? */
|
||||
|
||||
|
@ -166,7 +166,7 @@ ssize_t write(int fd, FAR const void *buf, size_t nbytes)
|
||||
|
||||
/* write() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Did we get a valid file descriptor? */
|
||||
|
||||
|
@ -42,6 +42,8 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
|
||||
@ -170,12 +172,19 @@ EXTERN const pthread_attr_t g_default_pthread_attr;
|
||||
* pending cancellation and, if so, calls either exit() or
|
||||
* pthread_exit(), depending upon the type of the thread.
|
||||
*
|
||||
* Input Parameters:
|
||||
* None
|
||||
*
|
||||
* Returned Value
|
||||
* true is returned if a cancellation is pending but cannot be performed
|
||||
* now due to the nesting level.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CANCELLATION_POINTS
|
||||
void enter_cancellation_point(void);
|
||||
bool enter_cancellation_point(void);
|
||||
#else
|
||||
# define enter_cancellation_point()
|
||||
# define enter_cancellation_point() false
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
@ -193,6 +202,12 @@ void enter_cancellation_point(void);
|
||||
* pending cancellation and, if so, calls either exit() or
|
||||
* pthread_exit(), depending upon the type of the thread.
|
||||
*
|
||||
* Input Parameters:
|
||||
* None
|
||||
*
|
||||
* Returned Value
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef CONFIG_CANCELLATION_POINTS
|
||||
|
@ -135,7 +135,7 @@ int psock_accept(FAR struct socket *psock, FAR struct sockaddr *addr,
|
||||
|
||||
/* Treat as a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Is the socket a stream? */
|
||||
|
||||
@ -365,7 +365,7 @@ int accept(int sockfd, FAR struct sockaddr *addr, FAR socklen_t *addrlen)
|
||||
|
||||
/* accept() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify that the sockfd corresponds to valid, allocated socket */
|
||||
|
||||
|
@ -519,7 +519,7 @@ int psock_connect(FAR struct socket *psock, FAR const struct sockaddr *addr,
|
||||
|
||||
/* Treat as a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify that the psock corresponds to valid, allocated socket */
|
||||
|
||||
@ -752,7 +752,7 @@ int connect(int sockfd, FAR const struct sockaddr *addr, socklen_t addrlen)
|
||||
|
||||
/* accept() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the underlying socket structure */
|
||||
|
||||
|
@ -1854,7 +1854,7 @@ ssize_t psock_recvfrom(FAR struct socket *psock, FAR void *buf, size_t len,
|
||||
|
||||
/* Treat as a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify that non-NULL pointers were passed */
|
||||
|
||||
@ -2087,7 +2087,7 @@ ssize_t recvfrom(int sockfd, FAR void *buf, size_t len, int flags,
|
||||
|
||||
/* recvfrom() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the underlying socket structure */
|
||||
|
||||
|
@ -126,7 +126,7 @@ ssize_t psock_send(FAR struct socket *psock, FAR const void *buf, size_t len,
|
||||
|
||||
/* Treat as a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
switch (psock->s_type)
|
||||
{
|
||||
@ -273,7 +273,7 @@ ssize_t send(int sockfd, FAR const void *buf, size_t len, int flags)
|
||||
|
||||
/* send() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the underlying socket structure */
|
||||
|
||||
|
@ -314,7 +314,7 @@ ssize_t sendto(int sockfd, FAR const void *buf, size_t len, int flags,
|
||||
|
||||
/* sendto() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Get the underlying socket structure */
|
||||
|
||||
|
@ -50,6 +50,7 @@
|
||||
|
||||
#include <nuttx/irq.h>
|
||||
#include <nuttx/arch.h>
|
||||
#include <nuttx/pthread.h>
|
||||
|
||||
#include "sched/sched.h"
|
||||
#include "mqueue/mqueue.h"
|
||||
@ -141,6 +142,21 @@ FAR struct mqueue_msg_s *mq_waitreceive(mqd_t mqdes)
|
||||
FAR struct mqueue_inode_s *msgq;
|
||||
FAR struct mqueue_msg_s *rcvmsg;
|
||||
|
||||
/* mq_waitreceive() is not a cancellation point, but it is always called
|
||||
* from a cancellation point.
|
||||
*/
|
||||
|
||||
if (enter_cancellation_point())
|
||||
{
|
||||
/* If there is a pending cancellation, then do not perform
|
||||
* the wait. Exit now with ECANCELED.
|
||||
*/
|
||||
|
||||
set_errno(ECANCELED);
|
||||
leave_cancellation_point();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get a pointer to the message queue */
|
||||
|
||||
msgq = mqdes->msgq;
|
||||
@ -195,6 +211,7 @@ FAR struct mqueue_msg_s *mq_waitreceive(mqd_t mqdes)
|
||||
msgq->nmsgs--;
|
||||
}
|
||||
|
||||
leave_cancellation_point();
|
||||
return rcvmsg;
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ ssize_t mq_receive(mqd_t mqdes, FAR char *msg, size_t msglen,
|
||||
|
||||
/* mq_receive() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify the input parameters and, in case of an error, set
|
||||
* errno appropriately.
|
||||
|
@ -106,7 +106,7 @@ int mq_send(mqd_t mqdes, FAR const char *msg, size_t msglen, int prio)
|
||||
|
||||
/* mq_send() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify the input parameters -- setting errno appropriately
|
||||
* on any failures to verify.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/****************************************************************************
|
||||
* sched/mqueue/mq_send.c
|
||||
* sched/mqueue/mq_sndinternal.c
|
||||
*
|
||||
* Copyright (C) 2007, 2009, 2013-2016 Gregory Nutt. All rights reserved.
|
||||
* Author: Gregory Nutt <gnutt@nuttx.org>
|
||||
@ -53,6 +53,7 @@
|
||||
#include <nuttx/arch.h>
|
||||
#include <nuttx/sched.h>
|
||||
#include <nuttx/signal.h>
|
||||
#include <nuttx/pthread.h>
|
||||
|
||||
#include "sched/sched.h"
|
||||
#ifndef CONFIG_DISABLE_SIGNALS
|
||||
@ -232,6 +233,21 @@ int mq_waitsend(mqd_t mqdes)
|
||||
FAR struct tcb_s *rtcb;
|
||||
FAR struct mqueue_inode_s *msgq;
|
||||
|
||||
/* mq_waitsend() is not a cancellation point, but it is always called from
|
||||
* a cancellation point.
|
||||
*/
|
||||
|
||||
if (enter_cancellation_point())
|
||||
{
|
||||
/* If there is a pending cancellation, then do not perform
|
||||
* the wait. Exit now with ECANCELED.
|
||||
*/
|
||||
|
||||
set_errno(ECANCELED);
|
||||
leave_cancellation_point();
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
/* Get a pointer to the message queue */
|
||||
|
||||
msgq = mqdes->msgq;
|
||||
@ -249,6 +265,7 @@ int mq_waitsend(mqd_t mqdes)
|
||||
/* No... We will return an error to the caller. */
|
||||
|
||||
set_errno(EAGAIN);
|
||||
leave_cancellation_point();
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
@ -283,12 +300,14 @@ int mq_waitsend(mqd_t mqdes)
|
||||
|
||||
if (get_errno() != OK)
|
||||
{
|
||||
leave_cancellation_point();
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leave_cancellation_point();
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ ssize_t mq_timedreceive(mqd_t mqdes, FAR char *msg, size_t msglen,
|
||||
|
||||
/* mq_timedreceive() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify the input parameters and, in case of an error, set
|
||||
* errno appropriately.
|
||||
|
@ -181,7 +181,7 @@ int mq_timedsend(mqd_t mqdes, FAR const char *msg, size_t msglen, int prio,
|
||||
|
||||
/* mq_timedsend() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify the input parameters -- setting errno appropriately
|
||||
* on any failures to verify.
|
||||
|
@ -181,7 +181,7 @@ int pthread_cond_timedwait(FAR pthread_cond_t *cond, FAR pthread_mutex_t *mutex,
|
||||
|
||||
/* pthread_cond_timedwait() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Make sure that non-NULL references were provided. */
|
||||
|
||||
|
@ -77,7 +77,7 @@ int pthread_cond_wait(FAR pthread_cond_t *cond, FAR pthread_mutex_t *mutex)
|
||||
|
||||
/* pthread_cond_wait() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Make sure that non-NULL references were provided. */
|
||||
|
||||
|
@ -96,7 +96,7 @@ int pthread_join(pthread_t thread, FAR pthread_addr_t *pexit_value)
|
||||
|
||||
/* pthread_join() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* First make sure that this is not an attempt to join to
|
||||
* ourself.
|
||||
|
@ -62,6 +62,6 @@
|
||||
|
||||
void pthread_testcancel(void)
|
||||
{
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
leave_cancellation_point();
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ int waitid(idtype_t idtype, id_t id, FAR siginfo_t *info, int options)
|
||||
|
||||
/* waitid() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* MISSING LOGIC: If WNOHANG is provided in the options, then this function
|
||||
* should returned immediately. However, there is no mechanism available now
|
||||
|
@ -188,7 +188,7 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options)
|
||||
|
||||
/* waitpid() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* None of the options are supported */
|
||||
|
||||
@ -317,7 +317,7 @@ pid_t waitpid(pid_t pid, int *stat_loc, int options)
|
||||
|
||||
/* waitpid() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* None of the options are supported */
|
||||
|
||||
|
@ -106,7 +106,7 @@ int sem_timedwait(FAR sem_t *sem, FAR const struct timespec *abstime)
|
||||
|
||||
/* sem_timedwait() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Verify the input parameters and, in case of an error, set
|
||||
* errno appropriately.
|
||||
|
@ -87,21 +87,31 @@ int sem_wait(FAR sem_t *sem)
|
||||
|
||||
DEBUGASSERT(sem != NULL && up_interrupt_context() == false);
|
||||
|
||||
/* sem_wait is a cancellation point */
|
||||
/* The following operations must be performed with interrupts
|
||||
* disabled because sem_post() may be called from an interrupt
|
||||
* handler.
|
||||
*/
|
||||
|
||||
enter_cancellation_point();
|
||||
flags = enter_critical_section();
|
||||
|
||||
/* sem_wait() is a cancellation point */
|
||||
|
||||
if (enter_cancellation_point())
|
||||
{
|
||||
/* If there is a pending cancellation, then do not perform
|
||||
* the wait. Exit now with ECANCELED.
|
||||
*/
|
||||
|
||||
set_errno(ECANCELED);
|
||||
leave_cancellation_point();
|
||||
leave_critical_section(flags);
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
/* Make sure we were supplied with a valid semaphore. */
|
||||
|
||||
if (sem != NULL)
|
||||
{
|
||||
/* The following operations must be performed with interrupts
|
||||
* disabled because sem_post() may be called from an interrupt
|
||||
* handler.
|
||||
*/
|
||||
|
||||
flags = enter_critical_section();
|
||||
|
||||
/* Check if the lock is available */
|
||||
|
||||
if (sem->semcount > 0)
|
||||
@ -191,10 +201,6 @@ int sem_wait(FAR sem_t *sem)
|
||||
sched_unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Interrupts may now be enabled. */
|
||||
|
||||
leave_critical_section(flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -202,5 +208,6 @@ int sem_wait(FAR sem_t *sem)
|
||||
}
|
||||
|
||||
leave_cancellation_point();
|
||||
leave_critical_section(flags);
|
||||
return ret;
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ int nanosleep(FAR const struct timespec *rqtp, FAR struct timespec *rmtp)
|
||||
|
||||
/* nanosleep() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
if (!rqtp || rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000)
|
||||
{
|
||||
|
@ -80,7 +80,7 @@ int pause(void)
|
||||
|
||||
/* pause() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Set up for the sleep. Using the empty set means that we are not
|
||||
* waiting for any particular signal. However, any unmasked signal
|
||||
|
@ -101,7 +101,7 @@ int sigsuspend(FAR const sigset_t *set)
|
||||
|
||||
/* sigsuspend() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Several operations must be performed below: We must determine if any
|
||||
* signal is pending and, if not, wait for the signal. Since signals can
|
||||
|
@ -176,7 +176,7 @@ int sigtimedwait(FAR const sigset_t *set, FAR struct siginfo *info,
|
||||
|
||||
/* sigtimedwait() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
sched_lock(); /* Not necessary */
|
||||
|
||||
/* Several operations must be performed below: We must determine if any
|
||||
|
@ -72,7 +72,7 @@ int sigwaitinfo(FAR const sigset_t *set, FAR struct siginfo *info)
|
||||
|
||||
/* sigwaitinfo() is a cancellation point */
|
||||
|
||||
enter_cancellation_point();
|
||||
(void)enter_cancellation_point();
|
||||
|
||||
/* Just a wrapper around sigtimedwait() */
|
||||
|
||||
|
@ -99,11 +99,19 @@
|
||||
* pending cancellation and, if so, calls either exit() or
|
||||
* pthread_exit(), depending upon the type of the thread.
|
||||
*
|
||||
* Input Parameters:
|
||||
* None
|
||||
*
|
||||
* Returned Value
|
||||
* true is returned if a cancellation is pending but cannot be performed
|
||||
* now due to the nesting level.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void enter_cancellation_point(void)
|
||||
bool enter_cancellation_point(void)
|
||||
{
|
||||
FAR struct tcb_s *tcb = this_task();
|
||||
bool ret = false;
|
||||
|
||||
/* Disabling pre-emption should provide sufficient protection. We only
|
||||
* need the TCB to be stationary (no interrupt level modification is
|
||||
@ -126,24 +134,32 @@ void enter_cancellation_point(void)
|
||||
(tcb->flags & TCB_FLAG_CANCEL_DEFERRED) != 0) ||
|
||||
tcb->cpcount > 0)
|
||||
{
|
||||
/* If there is a pending cancellation and we are at the outermost
|
||||
* nesting level of cancellation function calls, then just exit
|
||||
* according to the type of the thread.
|
||||
*/
|
||||
/* Check if there is a pending cancellation */
|
||||
|
||||
if ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0 &&
|
||||
tcb->cpcount == 0)
|
||||
if ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0)
|
||||
{
|
||||
/* Yes... return true (if we don't exit here) */
|
||||
|
||||
ret = true;
|
||||
|
||||
/* If there is a pending cancellation and we are at the outermost
|
||||
* nesting level of cancellation function calls, then exit
|
||||
* according to the type of the thread.
|
||||
*/
|
||||
|
||||
if (tcb->cpcount == 0)
|
||||
{
|
||||
#ifndef CONFIG_DISABLE_PTHREAD
|
||||
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_PTHREAD)
|
||||
{
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
else
|
||||
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_PTHREAD)
|
||||
{
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, indicate that we are at a cancellation point by
|
||||
@ -156,6 +172,7 @@ void enter_cancellation_point(void)
|
||||
}
|
||||
|
||||
sched_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -173,6 +190,12 @@ void enter_cancellation_point(void)
|
||||
* pending cancellation and, if so, calls either exit() or
|
||||
* pthread_exit(), depending upon the type of the thread.
|
||||
*
|
||||
* Input Parameters:
|
||||
* None
|
||||
*
|
||||
* Returned Value
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void leave_cancellation_point(void)
|
||||
|
Loading…
Reference in New Issue
Block a user