Initial commit.
This commit is contained in:
commit
219c118d7f
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue