0

I made a circular buffer with multiple clients writing (in the end I want them to write messages of different size) into a buffer. The server reads them out. It's based on the code in a consumer/producer problem:

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

#define BUFFER_SIZE 10

struct cBuf{
    char    *buf;  
    int     size; 
    int     start; 
    int     end;    
    pthread_mutex_t mutex;
    pthread_cond_t  buffer_full;
    pthread_cond_t  buffer_empty;
};

struct cBuf cb;

void buf_Init(struct cBuf *cb, int size) {
    int i;
    cb->size  = size + 1; 
    cb->start = 0;
    cb->end   = 0; 
    cb->buf = (char *)calloc(cb->size, sizeof(char));   
}
void buf_Free(struct cBuf *cb) {
    free(cb->buf);
}
int buf_IsFull(struct cBuf *cb) {
    return (cb->end + 1) % cb->size == cb->start; 
}
int buf_IsEmpty(struct cBuf *cb) {
    return cb->end == cb->start; 
}

int buf_Insert(struct cBuf *cb, char *elem) {

    int i,j;
    pthread_mutex_lock(&(cb->mutex));
    for (i=0; i < strlen(elem); ++ i){
        if (buf_IsFull(cb)==1) printf("\nProducer (buf_Insert) is waiting ");
        while(buf_IsFull(cb)){                      
            pthread_cond_wait(&(cb->buffer_empty),&(cb->mutex));
        } 

        cb->buf[cb->end] = elem[i]; 
        cb->end = (cb->end + 1) % cb->size;     
        printf("%c-",elem[i]);
    }

    pthread_cond_signal(&(cb->buffer_full));
    pthread_mutex_unlock(&(cb->mutex));     
    return 0;       
}

int buf_Read(struct cBuf *cb, char *out) {
    int i,j;
    pthread_mutex_lock(&(cb->mutex));
    if (buf_IsEmpty(cb))printf("\nConsumer (buf_Read) is waiting ");
    while(buf_IsEmpty(cb)){
        pthread_cond_wait(&(cb->buffer_full),&(cb->mutex));
    }

    for (i=0;i<BUFFER_SIZE-1;i++){
        if (cb->start == cb->end) break;

        out[i] = cb->buf[cb->start];
        cb->buf[cb->start] = '_';
        cb->start = (cb->start + 1) % cb->size;

        printf("%c-",out[i]);
    }
    pthread_cond_signal(&(cb->buffer_empty));
    pthread_mutex_unlock(&(cb->mutex)); 
    return 0;
}

void * client(void *cb){
    pthread_detach(pthread_self());

    struct cBuf *myData;
    myData = (struct cBuf*) cb;

    char input[]="Hello World!";

    if (buf_Insert(myData, input)) printf("\n");
    return 0;
}

int main(void) {
    char out[60];
    pthread_t thread;
    int i;

    pthread_cond_init(&(cb.buffer_full),NULL);
    pthread_cond_init(&(cb.buffer_empty),NULL);

    buf_Init(&cb, BUFFER_SIZE);

    for (i = 0; i<1; i++){
            if(pthread_create (&thread,NULL, client, (void *) &cb) !=0){
            #ifdef DEBUG
            printf("\nDEBUG (Main Thread) - Error while creating thread");
            #endif
        } else {
            #ifdef DEBUG
            printf("\nDEBUG (Main Thread) - Thread created");
            #endif
        }
    }

    while (1){
        if (buf_Read(&cb,out)) printf ("succes");
    }

    buf_Free(&cb);
    return 0;
}

It mostly works when the buffer is bigger than the message of a single client (by making buffer_size bigger, e.g., 16). When I make it smaller, however, it seems to deadlock, and even after a lot of research, I can't figure out why. When I run the code in a debugger, the code appears to stall on the line

pthread_cond_wait(&(cb->buffer_empty),&(cb->mutex));

Why is the code stalling here and how can I prevent it from stalling?

icktoofay
  • 126,289
  • 21
  • 250
  • 231
Thomas
  • 1,678
  • 4
  • 24
  • 47

1 Answers1

1

Did you say "smaller than the message", in singular? If the buffer is not big enough to store even one message, than the producer will stop halfway writing it in the queue and never get around to notifying the consumer that it has something to consume.

Brief scan through the code seems to confirm that -- if even one message can't be written, you block in the writing loop and don't get to the pthread_cond_signal call at the end of the function, so you never notify the consumer and it can't free up the buffer.

This problem is principal. The elementary unit that the consumer can start consuming has to fit in the queue. You can resolve the problem in two ways -- either make sure the buffer is large enough for the message, or make the message processable in smaller units and notify the consumer (pthread_cond_signal) after each unit.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • I think I might have found the reason thanks to your answer: the pthread_cond_signal(&(cb->buffer_full)); is in the wrong place; if the buffer is full i need to cond_signal to signal the server that is needs to start removing values; checking if it works now.. – Thomas May 28 '12 at 10:21
  • it think i solved it (i signaled the consumer in the wrong place; now it signals when the buffer is full) now i still have the problem that when using more than one client or producer they al race to start producing input even when after the buffer was emptied I want the producer that has not completely transmitted his message to continue... – Thomas May 28 '12 at 10:42
  • @ThomasVerbeke: They will all try to produce. That's how it is. The only sane solution is that you never let go the lock until you've written a message that is understandable by the consumer as it stands. An incomplete message is something that must never ever appear in the queue with multiple readers or multiple writers, because you simply can't guarantee it won't be followed by something from another writer or next part read by another reader. – Jan Hudec May 28 '12 at 11:10
  • @ThomasVerbeke: You need to make sure the message fits in the queue. If they are long, the easiest method is actually to leave them in the memory they are in and pass a pointer to it. You just have to either make sure the producer does not use the buffer past that point and the consumer frees it, or that the buffer is reference-counted and the last one to use it frees it. – Jan Hudec May 28 '12 at 11:12
  • thank you very much for your help: I added a function that checks if there is room enough left in the buffer for the message and that solved the problem: now. If there is room the message is added to the buffer if there isn't the buffer is read by the server and the message can now be added if there is enough room – Thomas May 28 '12 at 12:52