Question

As $ man errno says, "errno is defined by the ISO C standard to be a modifiable lvalue of type int, and must not be explicitly declared; errno may be a macro. errno is thread-local; setting it in one thread does not affect its value in any other thread".

I'm developing a C library which will work both in POSIX and Windows, so instead of using errno and GetLastError/SetLastError I've decided to stick to my own error type. Every my function returns error code as cg_error object, where cg_error is just a typedef. However, for some functions like custom allocators it is still better to use something like errno, but with my own cg_error type.


AFAIK errno in glibc is implemented this way:

#define errno (*__errno_location ())


I'm trying to implement a similar function using pthreads on Linux and TlsAlloc and friends for Windows. Here is what I have now (yet only POSIX, seems to be a Solaris implementation from article "Thread-Specific Storage pattern" found on the Web):

cg_error * CG_ERRNO_TLS(void)
{
#if CG_FEATURE_POSIX
  static int once;
  static pthread_key_t key;
  static pthread_mutex_t lock;
  cg_error * error = NULL;
  if (once)
  {
    pthread_mutex_lock(&lock);
    if (once)
    {
      (void) pthread_key_create(&key, cg_free);
      once = 1;
    }
    pthread_mutex_unlock(&lock);
  }
  error = pthread_getspecific(key);
  if (!error)
  {
    error = cg_malloc(sizeof(*error));
    (void) pthread_setspecific(key, error);
  }
  return error;
#endif
}

#define cg_errno (*CG_ERRNO_TLS())

However, when I try to set or get cg_errno, its int value is 6344768, which is not what I want. What am I doing wrong? What's the right way to define something like errno? Thanks in advance!


P.S. I know that I could use __thread and __declspec(thread), but such things are compiler-specific (and probably system specific; I've heard that __thread doesn't work e.g. for MacOSX with gcc).

P.P.S. Base value of cg_error is CG_ERROR_NONE, which is always 0.



UPDATE:

#if CG_FEATURE_POSIX
static pthread_key_t cg_errno_key;
static pthread_once_t cg_errno_once = PTHREAD_ONCE_INIT;


static void cg_errno_init(void)
{ (void) pthread_key_create(&cg_errno_key, cg_free); }


cg_error * cg_errno_storage(void)
{
  cg_error * error = NULL;
  (void) pthread_once(&cg_errno_once, cg_errno_init);
  error = pthread_getspecific(cg_errno_key);
  if (!error)
  {
    error = cg_malloc(sizeof(*error));
    (void) pthread_setspecific(cg_errno_key, error);
  }
  return error;
}

#define cg_errno (*cg_errno_storage())
#endif
Was it helpful?

Solution

Your condition on once is wrong. It should be !once.

But using an int for that is not guaranteed to work. There is a special type pthread_once_t with function pthread_once for the task that you are trying to achieve.

Also, lock should have an initializer, PTHREAD_MUTEX_INITIALIZER.

A use of pthread_once_t would look like this:

static pthread_key_t key;
static pthread_once_t once = PTHREAD_ONCE_INIT;

static
void init_error_key_once(void) {
  pthread_key_create(&key, cg_free);
}

cg_error * CG_ERRNO_TLS_POSIX(void)
{
  cg_error * error = NULL;
  pthread_once(&once, init_error_key_once);
  ...

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top