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?
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.