void *rqueue_read(rqueue_t *rb) { int i; void *v = NULL; for (i = 0; i < RQUEUE_MAX_RETRIES; i++) { if (__builtin_expect(ATOMIC_CAS(rb->read_sync, 0, 1), 1)) { rqueue_page_t *head = ATOMIC_READ(rb->head); rqueue_page_t *commit = ATOMIC_READ(rb->commit); rqueue_page_t *tail = ATOMIC_READ(rb->tail); rqueue_page_t *next = ATOMIC_READ(head->next); rqueue_page_t *old_next = ATOMIC_READ(rb->reader->next); if (rb->reader == commit || (head == tail && commit != tail) || ATOMIC_READ(rb->writes) == 0) { // nothing to read ATOMIC_CAS(rb->read_sync, 1, 0); break; } if (ATOMIC_CAS(rb->reader->next, old_next, RQUEUE_FLAG_ON(next, RQUEUE_FLAG_HEAD))) { rb->reader->prev = head->prev; if (ATOMIC_CAS(head->prev->next, RQUEUE_FLAG_ON(head, RQUEUE_FLAG_HEAD), rb->reader)) { ATOMIC_CAS(rb->head, head, next); next->prev = rb->reader; rb->reader = head; /* rb->reader->next = next; rb->reader->prev = next->prev; */ v = ATOMIC_READ(rb->reader->value); ATOMIC_CAS(rb->reader->value, v, NULL); ATOMIC_INCREMENT(rb->reads); ATOMIC_CAS(rb->read_sync, 1, 0); break; } else { fprintf(stderr, "head swap failed\n"); } } else { fprintf(stderr, "reader->next swap failed\n"); } ATOMIC_CAS(rb->read_sync, 1, 0); } } return v; }
rqueue_t *rqueue_create(size_t size, rqueue_mode_t mode) { size_t i; rqueue_t *rb = calloc(1, sizeof(rqueue_t)); if (!rb) return NULL; rb->size = (size > RQUEUE_MIN_SIZE) ? size : RQUEUE_MIN_SIZE; rb->mode = mode; for (i = 0; i < size; i++) { rqueue_page_t *page = calloc(1, sizeof(rqueue_page_t)); if (!page) { free(rb); // TODO - free the pages allocated so far return NULL; } if (!rb->head) { rb->head = rb->tail = page; } else if (rb->tail) { rb->tail->next = page; } page->prev = rb->tail; rb->tail = page; } if (!rb->head || !rb->tail) { free(rb); return NULL; } // close the ringbuffer rb->head->prev = rb->tail; rb->tail->next = rb->head; rb->tail->next = RQUEUE_FLAG_ON(rb->tail->next, RQUEUE_FLAG_HEAD); rb->tail = rb->head; rb->commit = rb->tail; // the reader page is out of the ringbuffer rb->reader = calloc(1, sizeof(rqueue_page_t)); if (!rb->reader) { free(rb); return NULL; } rb->reader->prev = rb->head->prev; rb->reader->next = rb->head; return rb; }
int rqueue_write(rqueue_t *rb, void *value) { int retries = 0; int did_update = 0; int did_move_head = 0; rqueue_page_t *temp_page = NULL; rqueue_page_t *next_page = NULL; rqueue_page_t *tail = NULL; rqueue_page_t *head = NULL; rqueue_page_t *commit; ATOMIC_INCREMENT(rb->num_writers); do { temp_page = ATOMIC_READ(rb->tail); commit = ATOMIC_READ(rb->commit); next_page = RQUEUE_FLAG_OFF(ATOMIC_READ(temp_page->next), RQUEUE_FLAG_ALL); head = ATOMIC_READ(rb->head); if (rb->mode == RQUEUE_MODE_BLOCKING) { if (temp_page == commit && next_page == head) { if (ATOMIC_READ(rb->writes) - ATOMIC_READ(rb->reads) != 0) { //fprintf(stderr, "No buffer space\n"); if (ATOMIC_READ(rb->num_writers) == 1) ATOMIC_CAS(rb->commit, ATOMIC_READ(rb->commit), ATOMIC_READ(rb->tail)); ATOMIC_DECREMENT(rb->num_writers); return -2; } } else if (next_page == head) { if (ATOMIC_READ(rb->num_writers) == 1) { tail = temp_page; break; } else { if (ATOMIC_READ(rb->num_writers) == 1) ATOMIC_CAS(rb->commit, ATOMIC_READ(rb->commit), ATOMIC_READ(rb->tail)); ATOMIC_DECREMENT(rb->num_writers); return -2; } } } tail = ATOMIC_CAS_RETURN(rb->tail, temp_page, next_page); } while (tail != temp_page && !(RQUEUE_CHECK_FLAG(ATOMIC_READ(tail->next), RQUEUE_FLAG_UPDATE)) && retries++ < RQUEUE_MAX_RETRIES); if (!tail) { if (ATOMIC_READ(rb->num_writers) == 1) ATOMIC_CAS(rb->commit, ATOMIC_READ(rb->commit), ATOMIC_READ(rb->tail)); ATOMIC_DECREMENT(rb->num_writers); return -1; } rqueue_page_t *nextp = RQUEUE_FLAG_OFF(ATOMIC_READ(tail->next), RQUEUE_FLAG_ALL); if (ATOMIC_CAS(tail->next, RQUEUE_FLAG_ON(nextp, RQUEUE_FLAG_HEAD), RQUEUE_FLAG_ON(nextp, RQUEUE_FLAG_UPDATE))) { did_update = 1; //fprintf(stderr, "Did update head pointer\n"); if (rb->mode == RQUEUE_MODE_OVERWRITE) { // we need to advance the head if in overwrite mode ...otherwise we must stop //fprintf(stderr, "Will advance head and overwrite old data\n"); rqueue_page_t *nextpp = RQUEUE_FLAG_OFF(ATOMIC_READ(nextp->next), RQUEUE_FLAG_ALL); if (ATOMIC_CAS(nextp->next, nextpp, RQUEUE_FLAG_ON(nextpp, RQUEUE_FLAG_HEAD))) { if (ATOMIC_READ(rb->tail) != next_page) { ATOMIC_CAS(nextp->next, RQUEUE_FLAG_ON(nextpp, RQUEUE_FLAG_HEAD), nextpp); } else { ATOMIC_CAS(rb->head, head, nextpp); did_move_head = 1; } } } } void *old_value = ATOMIC_READ(tail->value); ATOMIC_CAS(tail->value, old_value, value); if (old_value && rb->free_value_cb) rb->free_value_cb(old_value); if (did_update) { //fprintf(stderr, "Try restoring head pointer\n"); ATOMIC_CAS(tail->next, RQUEUE_FLAG_ON(nextp, RQUEUE_FLAG_UPDATE), did_move_head ? RQUEUE_FLAG_OFF(nextp, RQUEUE_FLAG_ALL) : RQUEUE_FLAG_ON(nextp, RQUEUE_FLAG_HEAD)); //fprintf(stderr, "restored head pointer\n"); } ATOMIC_INCREMENT(rb->writes); if (ATOMIC_READ(rb->num_writers) == 1) ATOMIC_CAS(rb->commit, ATOMIC_READ(rb->commit), tail); ATOMIC_DECREMENT(rb->num_writers); return 0; }