1
0
Fork 0

Initial commit.

This commit is contained in:
Peter H. Fröhlich 2019-06-14 22:59:08 +02:00
commit 219c118d7f
7 changed files with 340 additions and 0 deletions

12
.clang-format Normal file
View File

@ -0,0 +1,12 @@
BasedOnStyle: LLVM
IndentWidth: 8
UseTab: Always
BreakBeforeBraces: Linux
AllowShortIfStatementsOnASingleLine: false
IndentCaseLabels: false
AlwaysBreakAfterReturnType: All
AlignAfterOpenBracket: DontAlign
AlignTrailingComments: false
ContinuationIndentWidth: 16
SortIncludes: false

14
LICENSE.md Normal file
View File

@ -0,0 +1,14 @@
# BSD Zero Clause License
Copyright © 2019 by Peter H. Fröhlich <peter.hans.froehlich@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

18
Makefile Normal file
View File

@ -0,0 +1,18 @@
CFLAGS=-std=c11 -Wall -Wextra -Wpedantic -g -Og \
-fsanitize=undefined -fsanitize=address
LDFLAGS=-fsanitize=undefined -fsanitize=address
ALL=demo
all: $(ALL)
demo: demo.o reconque.o
clean:
$(RM) $(ALL) *.o
check:
cppcheck --enable=all --std=c11 *.[ch]
format:
clang-format -i *.[ch]
.PHONY: all check clean format

61
README.md Normal file
View File

@ -0,0 +1,61 @@
# reconques
Replace queues that are in active use! 😄
## Motivation
Say you're writing a network service that uses queues to buffer messages.
Say you need to be able to *dynamically* adjust the sizes of those queues while
the service is running.
Say you cannot afford to lose any messages even when *reducing* the capacity of
a queue.
Say you cannot afford to *copy* existing messages from one queue to another
either (those queues might be long-ish).
What do you do?
## Design
The basic idea is simple:
Allow a new queue to "wrap" an existing one.
Problem solved! 😄
A push or enqueue operation works on the new queue; a pop or dequeue operation
works on the old queue until it's empty; once the old queue is empty, you free
it; from here on out, a pop or dequeue operation works on the new queue.
(Yes this would be harder with stacks.)
## Implementation
The implementation in `reconque.h` and `reconque.c` is designed to be as simple
as possible. The point was to illustrate the idea and to avoid complications
that are not strictly necessary. The `demo.c` program is not much of a demo,
more of a unit test really. Sorry for the lack of comments, but I hope the code
is readable enough.
## Complications
Speaking of complications:
1. The technique used violates a basic queue invariant, namely that you can
pop at most `n` elements out of a queue with capacity `n`. This can trip up
code using a queue wrapping others. Here we get away with it because there's
no operation to ask a queue how many elements it has. If you need that kind
of information, you'll have to think a little.
2. If you need to `free` messages as they're being dequeued, you'll need to
think about what happens when `rcq_free` is being called on a non-empty,
potentially wrapping, queue. (I mostly put reference-counted objects into
queues, and as part of my `rcq_free`-equivalent I'd release those.)
3. If you're performance-hungry, make sure you replace the `%` with a bitwise
`&` and power-of-two capacities. I wouldn't recommend replacing the recursive
parts, but if you find them offensive...
4. Threads? Oh well. You'll need to add mutexes are apply fancy atomics in all
the right places. Sorry, you're on your own.
## License
I very much doubt that you can use the code "as is" for a real system, but feel
free to take it as a starting point if that seems helpful. Since it's under a
0-clause BSD license, which essentially makes it public domain, you're welcome
to do as you please. If the code was helpful to you, I'd *appreciate* an email,
but it's not required.

101
demo.c Normal file
View File

@ -0,0 +1,101 @@
/* SPDX-License-Identifier: 0BSD */
#include <assert.h>
#include <stdlib.h>
#include "reconque.h"
#define FAKE(x) ((void *)(x))
void
basic(void)
{
struct reconque *q1 = rcq_alloc(5);
assert(q1);
for (size_t i = 0; i < 5; i++) {
assert(rcq_push(q1, FAKE(i + 1)) == 0);
}
assert(rcq_push(q1, FAKE(1000)) < 0);
for (size_t i = 0; i < 5; i++) {
assert(rcq_pop(q1) == FAKE(i + 1));
}
assert(rcq_pop(q1) == NULL);
rcq_free(q1);
}
void
grow(void)
{
struct reconque *q1 = rcq_alloc(5);
assert(q1);
for (size_t i = 0; i < 3; i++) {
assert(rcq_push(q1, FAKE(i + 1)) == 0);
}
struct reconque *q2 = rcq_recon(10, q1);
assert(q2);
q1 = NULL; /* q2 now "owns" q1 so we should drop it */
for (size_t i = 0; i < 8; i++) {
assert(rcq_push(q2, FAKE(i + 3 + 1)) == 0);
}
for (size_t i = 0; i < 11; i++) {
assert(rcq_pop(q2) == FAKE(i + 1));
}
assert(rcq_pop(q2) == NULL);
/*
* Admittedly it's a little weird that we can `rcq_pop` more often than
* the capacity of `q2` would indicate, but those are the breaks.
*/
rcq_free(q2);
}
void
triple(void)
{
struct reconque *q1 = rcq_alloc(7);
assert(q1);
assert(rcq_push(q1, FAKE(1)) == 0);
struct reconque *q2 = rcq_recon(10, q1);
assert(q2);
q1 = NULL;
assert(rcq_push(q2, FAKE(2)) == 0);
struct reconque *q3 = rcq_recon(3, q2);
assert(q3);
q2 = NULL;
for (size_t i = 0; i < 3; i++) {
assert(rcq_push(q3, FAKE(i + 3)) == 0);
}
for (size_t i = 0; i < 5; i++) {
assert(rcq_pop(q3) == FAKE(i + 1));
}
assert(rcq_pop(q3) == NULL);
rcq_free(q3);
}
int
main(void)
{
basic(); /* just basic FIFO and full/empty */
grow(); /* replace with bigger queue */
triple(); /* just showing off at this point */
exit(EXIT_SUCCESS);
}

102
reconque.c Normal file
View File

@ -0,0 +1,102 @@
/* SPDX-License-Identifier: 0BSD */
#include <assert.h>
#include <stdlib.h>
#include "reconque.h"
struct reconque {
struct reconque *old; /* queue we're in the process of replacing */
size_t slots; /* total number of slots in `data[]` */
size_t used; /* number of used slots in `data[]` */
size_t head; /* where `rcq_pop` reads */
size_t tail; /* where `rcq_push` writes */
void *data[];
};
struct reconque *
rcq_alloc(size_t slots)
{
assert(slots > 0);
struct reconque *q = RCQ_MALLOC(sizeof(*q) + slots * sizeof(*q->data));
if (!q) {
return NULL;
}
q->old = NULL;
q->slots = slots;
q->used = q->head = q->tail = 0;
return q;
}
struct reconque *
rcq_recon(size_t slots, struct reconque *old)
{
assert(slots > 0);
assert(old);
struct reconque *q = rcq_alloc(slots);
if (!q) {
return NULL;
}
q->old = old;
return q;
}
void
rcq_free(struct reconque *q)
{
assert(q);
if (q->old) {
rcq_free(q->old);
}
RCQ_FREE(q);
}
int
rcq_push(struct reconque *q, void *item)
{
assert(q);
if (q->used >= q->slots) {
return -1; /* full */
}
q->data[q->tail] = item;
q->tail = (q->tail + 1) % q->slots;
q->used++;
return 0;
}
void *
rcq_pop(struct reconque *q)
{
assert(q);
if (q->old) {
void *item = rcq_pop(q->old);
if (item) {
return item;
}
rcq_free(q->old);
q->old = NULL;
}
if (q->used == 0) {
return NULL;
}
void *item = q->data[q->head];
q->head = (q->head + 1) % q->slots;
q->used--;
return item;
}

32
reconque.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#ifndef RECONQUE_H_
#define RECONQUE_H_
/* SPDX-License-Identifier: 0BSD */
#ifndef RCQ_MALLOC
#define RCQ_MALLOC(n) (malloc(n))
#endif
#ifndef RCQ_FREE
#define RCQ_FREE(p) (free(p))
#endif
struct reconque;
struct reconque *
rcq_alloc(size_t slots);
struct reconque *
rcq_recon(size_t slots, struct reconque *old);
void
rcq_free(struct reconque *queue);
int
rcq_push(struct reconque *queue, void *item);
void *
rcq_pop(struct reconque *queue);
#endif