clinuxpthreads

program hangs on pthread_rwlock_wrlock?


My implementation of a user_queue:

user_queue.h

#ifndef USER_QUEUE_H
#define USER_QUEUE_H
#include <pthread.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>

typedef struct user_t
{
    uint32_t uid;
    uint32_t timestamp;
    struct user_t *next;
}user_t;

typedef struct u_queue_t
{
    pthread_rwlock_t lock;
    user_t *head;
    user_t *tail;
}u_queue_t;

#define NOW() (get_timestamp())

void init_user_queue(u_queue_t *u);
void add_user(u_queue_t *u, user_t *user);
void delete_user(u_queue_t *u, uint32_t uid);
user_t *find_user(u_queue_t *u, uint32_t uid);
void update_user_timestamp(u_queue_t *u, uint32_t uid, uint32_t now);
uint32_t get_timestamp(void);
extern u_queue_t user_queue;

#endif

user_queue.c

#include "user_queue.h"

u_queue_t user_queue;

void init_user_queue(u_queue_t *u)
{
    pthread_rwlock_init(&(u->lock), NULL);
    u->head = NULL;
    u->tail = NULL;
}

void add_user(u_queue_t *u, user_t *user)
{
    printf("add user0\n");
    pthread_rwlock_wrlock(&(u->lock));
    printf("add_user1\n");
    if(u->head == NULL)
    {
        u->head = user;
        u->tail = user;
        user->next = NULL;
    }
    else
    {
        u->tail->next = user;
        u->tail = user;
        user->next = NULL;
    }
    printf("add_user2\n");
    pthread_rwlock_unlock(&(u->lock));
}

void delete_user(u_queue_t *u, uint32_t uid)
{
    user_t *p = u->head;
    user_t *tmp;


    pthread_rwlock_wrlock(&(u->lock));
    if(p == NULL)
    {
        fprintf(stderr, "Error: empty user queue\n");
        return;
    }
    else
    {
        if(p->uid == uid)
        {
            u->head = p->next;
            u->tail = p->next;
        }
        free(p);
    }

    while(p->next != NULL)
    {
        if(p->next->uid == uid)
        {
            break;
        }
        else
        {
            p = p->next;
        }
    }

    tmp = p->next;
    p->next = tmp->next;
    if(tmp == u->tail)
    {
        u->tail = p;
    }
    free(tmp);
    pthread_rwlock_unlock(&(u->lock));
}

user_t *find_user(u_queue_t *u, uint32_t uid)
{
    user_t *p;
    printf("find_user1\n");
    pthread_rwlock_rdlock(&(u->lock));
    printf("find_user2\n");
    p = u->head;
    if(p == NULL)
    {
        return NULL;
    }

    while(p != NULL)
    {
        if(p->uid == uid)
        {
            break;
        }
    }
    pthread_rwlock_unlock(&(u->lock));
    return p;
}

uint32_t get_timestamp(void)
{
    return (uint32_t)time(NULL);
}

void update_user_timestamp(u_queue_t *u, uint32_t uid, uint32_t now)
{
    user_t *p = find_user(u, uid);
    if(p == NULL)
    {
        fprintf(stderr, "update_user_timestamp error: invalid user id\n");
        return;
    }

    p->timestamp = now;
}

And the code I user the user_queue is as follows:

if(uid)
{
    user_t *p;
    p = find_user(&user_queue, uid);
    printf("test\n");
    if(p == NULL)
    {
        memcpy(data, LOGIN_SUCCESS_MESSAGE, sizeof(server_login_message_t));
        m = message_new(MESSAGE_LOGIN, MAX_MESSAGE_SIZE, uid, data);
        status = create_node((void *)m, n->client_fd);
        u = (user_t *)malloc(sizeof(user_t));
        u->uid = uid;
        printf("test4\n");
        add_user(&user_queue, u);
        printf("test5\n");
        produce_message(&message_out_queue, status); 
        free(n);
    }
    else
    {
        printf("login again\n");
        memcpy(data, LOGIN_REPEAT_MESSAGE, sizeof(server_login_message_t));
        m = message_new(MESSAGE_LOGIN, MAX_MESSAGE_SIZE, uid, data);
        status = create_node((void *)m, n->client_fd);
        uint32_t now = NOW();
        update_user_timestamp(&user_queue, uid, now);
        produce_message(&message_out_queue, status); 
        free(n);
    }
}  

I use pstack and find that the thread hangs on pthread_rwlock_wrlock() statement in function add_user, but I think the lock is properly freed before calling pthread_rwlock_wrlock, so what's wrong in my code? what tech could I use to solve the problem?


Solution

  • For starters:

      pthread_rwlock_rdlock(&(u->lock));
      printf("find_user2\n");
      p = u->head;
      if(p == NULL)
      {
        return NULL;
      }
    

    The code above from find_user() leave the lock rw-locked in case of p being NULL. Fatal.

    There is a similar bug in delete_user().


    Also the code should really test the outcome of the pthread*() calls, as at least this would help you during debugging.