#define _GNU_SOURCE
|
|
|
|
#include "erl_nif.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
// #include "fifo.h"
|
|
#include "lifo.h"
|
|
|
|
typedef struct {
|
|
ERL_NIF_TERM ok;
|
|
ERL_NIF_TERM error;
|
|
ERL_NIF_TERM fifo;
|
|
ERL_NIF_TERM lifo;
|
|
ERL_NIF_TERM ttl;
|
|
ERL_NIF_TERM max_size;
|
|
} atoms_t;
|
|
|
|
typedef struct {
|
|
ErlNifResourceType *queue;
|
|
atoms_t atoms;
|
|
} priv_t;
|
|
|
|
typedef struct {
|
|
union {
|
|
fifo_handle_t fifo;
|
|
lifo_handle_t lifo;
|
|
} handle;
|
|
ErlNifBinary data;
|
|
struct timespec added;
|
|
} item_t;
|
|
|
|
typedef enum {
|
|
QTYPE_FIFO = 0,
|
|
QTYPE_LIFO
|
|
} queue_type_t;
|
|
|
|
typedef struct queue {
|
|
union {
|
|
fifo_t fifo;
|
|
lifo_t lifo;
|
|
} queue;
|
|
uint64_t ttl;
|
|
uint64_t max_size;
|
|
void (*push) (struct queue *inst, item_t *item);
|
|
item_t* (*pop) (struct queue *inst);
|
|
void (*free) (struct queue *inst);
|
|
uint64_t (*size) (struct queue *inst);
|
|
void (*cleanup) (struct queue *inst);
|
|
} queue_t;
|
|
|
|
// returns tuple {error, atom()}
|
|
static inline ERL_NIF_TERM
|
|
make_error(ErlNifEnv* env, const char *error) {
|
|
priv_t *priv = (priv_t *) enif_priv_data(env);
|
|
|
|
return enif_make_tuple2(env, priv->atoms.error, enif_make_atom(env, error));
|
|
}
|
|
|
|
// returns time diff in milliseconds
|
|
static inline int64_t
|
|
tdiff(struct timespec *t2, struct timespec *t1) {
|
|
return (t2->tv_sec * 1000 + t2->tv_nsec / 1000000UL) -
|
|
(t1->tv_sec * 1000 + t1->tv_nsec / 1000000UL);
|
|
}
|
|
|
|
static inline void
|
|
gettime(struct timespec *tp) {
|
|
int rc = clock_gettime(CLOCK_MONOTONIC_RAW, tp);
|
|
assert(rc == 0);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* FIFO callbacks */
|
|
/******************************************************************************/
|
|
|
|
static void
|
|
cleanup_fifo(queue_t *inst) {
|
|
struct timespec now;
|
|
|
|
gettime(&now);
|
|
|
|
for (;;) {
|
|
item_t *item = NULL;
|
|
__fifo_peak(&inst->queue.fifo, item, handle.fifo);
|
|
|
|
if (item == NULL)
|
|
return;
|
|
|
|
int64_t diff = tdiff(&now, &item->added);
|
|
if (diff < inst->ttl) {
|
|
return;
|
|
} else {
|
|
__fifo_pop(&inst->queue.fifo, item, handle.fifo);
|
|
enif_release_binary(&item->data);
|
|
enif_free(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
push_fifo(queue_t *inst, item_t *item) {
|
|
__fifo_push(&inst->queue.fifo, item, handle.fifo);
|
|
}
|
|
|
|
static item_t *
|
|
pop_fifo(queue_t *inst) {
|
|
item_t *item = NULL;
|
|
|
|
if (inst->ttl > 0) {
|
|
struct timespec now;
|
|
|
|
gettime(&now);
|
|
|
|
for (;;) {
|
|
__fifo_pop(&inst->queue.fifo, item, handle.fifo);
|
|
|
|
if (item == NULL)
|
|
return NULL;
|
|
|
|
int64_t diff = tdiff(&now, &item->added);
|
|
if (diff < inst->ttl) {
|
|
return item;
|
|
} else {
|
|
enif_release_binary(&item->data);
|
|
enif_free(item);
|
|
}
|
|
}
|
|
} else {
|
|
__fifo_pop(&inst->queue.fifo, item, handle.fifo);
|
|
}
|
|
|
|
return item;
|
|
}
|
|
|
|
static void
|
|
free_fifo(queue_t *inst) {
|
|
item_t *item;
|
|
|
|
for(;;) {
|
|
__fifo_pop(&inst->queue.fifo, item, handle.fifo);
|
|
|
|
if (item == NULL)
|
|
return;
|
|
|
|
enif_release_binary(&item->data);
|
|
enif_free(item);
|
|
}
|
|
}
|
|
|
|
static uint64_t
|
|
size_fifo(queue_t *inst) {
|
|
return fifo_length(&inst->queue.fifo);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/* LIFO callbacks */
|
|
/******************************************************************************/
|
|
|
|
static void
|
|
cleanup_lifo(queue_t *inst) {
|
|
struct timespec now;
|
|
|
|
gettime(&now);
|
|
|
|
for(;;) {
|
|
item_t *item = inst->queue.lifo.tail;
|
|
|
|
if (item == NULL)
|
|
return;
|
|
|
|
int64_t diff = tdiff(&now, &item->added);
|
|
if (diff < inst->ttl) {
|
|
return;
|
|
} else {
|
|
item_t *prev = item->handle.lifo.prev;
|
|
|
|
if (prev != NULL)
|
|
prev->handle.lifo.next = NULL;
|
|
|
|
inst->queue.lifo.tail = prev;
|
|
|
|
enif_release_binary(&item->data);
|
|
enif_free(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
push_lifo(queue_t *inst, item_t *item) {
|
|
__lifo_push(&inst->queue.lifo, item, handle.lifo);
|
|
}
|
|
|
|
static item_t *
|
|
pop_lifo(queue_t *inst) {
|
|
item_t *item = NULL;
|
|
|
|
if (inst->ttl > 0)
|
|
cleanup_lifo(inst);
|
|
|
|
__lifo_pop(&inst->queue.lifo, item, handle.lifo);
|
|
|
|
return item;
|
|
}
|
|
|
|
static void
|
|
free_lifo(queue_t *inst) {
|
|
item_t *item;
|
|
|
|
for(;;) {
|
|
__lifo_pop(&inst->queue.lifo, item, handle.lifo);
|
|
|
|
if (item == NULL)
|
|
return;
|
|
|
|
enif_release_binary(&item->data);
|
|
enif_free(item);
|
|
}
|
|
}
|
|
|
|
static uint64_t
|
|
size_lifo(queue_t *inst) {
|
|
return lifo_length(&inst->queue.lifo);
|
|
}
|
|
|
|
/******************************************************************************
|
|
** NIFs
|
|
*******************************************************************************/
|
|
|
|
static ERL_NIF_TERM
|
|
new_queue(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
|
|
if (!enif_is_list(env, argv[0]))
|
|
return enif_make_badarg(env);
|
|
|
|
priv_t *priv = (priv_t *) enif_priv_data(env);
|
|
|
|
queue_type_t qtype = QTYPE_FIFO;
|
|
unsigned long ttl = 0;
|
|
unsigned long max_size = 0;
|
|
|
|
ERL_NIF_TERM settings_list = argv[0];
|
|
ERL_NIF_TERM head;
|
|
|
|
// parses proplist [fifo, lifo, {ttl, non_neg_integer()}, {max_size, non_neg_integer()}]
|
|
while(enif_get_list_cell(env, settings_list, &head, &settings_list))
|
|
{
|
|
const ERL_NIF_TERM *items;
|
|
int arity;
|
|
|
|
if (enif_is_atom(env, head)) {
|
|
if (enif_is_identical(head, priv->atoms.fifo)) {
|
|
qtype = QTYPE_FIFO;
|
|
} else if (enif_is_identical(head, priv->atoms.lifo)) {
|
|
qtype = QTYPE_LIFO;
|
|
} else {
|
|
return enif_make_badarg(env);
|
|
}
|
|
} else if (enif_get_tuple(env, head, &arity, &items) && arity == 2) {
|
|
if (enif_is_identical(items[0], priv->atoms.ttl)) {
|
|
if (!enif_get_ulong(env, items[1], &ttl)) {
|
|
return enif_make_badarg(env);
|
|
}
|
|
} else if (enif_is_identical(items[0], priv->atoms.max_size)) {
|
|
if (!enif_get_ulong(env, items[1], &max_size)) {
|
|
return enif_make_badarg(env);
|
|
}
|
|
} else {
|
|
return enif_make_badarg(env);
|
|
}
|
|
} else {
|
|
return enif_make_badarg(env);
|
|
}
|
|
}
|
|
|
|
queue_t *inst = (queue_t *) enif_alloc_resource(priv->queue, sizeof(*inst));
|
|
|
|
if (inst == NULL)
|
|
return make_error(env, "enif_alloc_resource");
|
|
|
|
inst->ttl = ttl;
|
|
inst->max_size = max_size;
|
|
|
|
switch (qtype) {
|
|
case QTYPE_FIFO:
|
|
fifo_init(&inst->queue.fifo);
|
|
inst->push = &push_fifo;
|
|
inst->pop = &pop_fifo;
|
|
inst->free = &free_fifo;
|
|
inst->size = &size_fifo;
|
|
inst->cleanup = &cleanup_fifo;
|
|
break;
|
|
case QTYPE_LIFO:
|
|
lifo_init(&inst->queue.lifo);
|
|
inst->push = &push_lifo;
|
|
inst->pop = &pop_lifo;
|
|
inst->free = &free_lifo;
|
|
inst->size = &size_lifo;
|
|
inst->cleanup = &cleanup_lifo;
|
|
break;
|
|
}
|
|
|
|
ERL_NIF_TERM result = enif_make_resource(env, inst);
|
|
enif_release_resource(inst);
|
|
|
|
return enif_make_tuple2(env, priv->atoms.ok, result);
|
|
}
|
|
|
|
static ERL_NIF_TERM
|
|
push_item(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
|
|
priv_t *priv = (priv_t *) enif_priv_data(env);
|
|
|
|
queue_t *inst;
|
|
|
|
if (!enif_get_resource(env, argv[0], priv->queue, (void**) &inst))
|
|
return enif_make_badarg(env);
|
|
|
|
// todo: check an owner of the queue
|
|
|
|
ErlNifBinary bin;
|
|
if (!enif_inspect_binary(env, argv[1], &bin))
|
|
return enif_make_badarg(env);
|
|
|
|
if (inst->ttl > 0) {
|
|
inst->cleanup(inst);
|
|
}
|
|
|
|
if (inst->max_size > 0 && inst->size(inst) >= inst->max_size) {
|
|
return enif_make_tuple2(env, priv->atoms.error, priv->atoms.max_size);
|
|
}
|
|
|
|
item_t *item = (item_t *) enif_alloc(sizeof(*item));
|
|
|
|
if (item == NULL)
|
|
return make_error(env, "enif_alloc");
|
|
|
|
if (!enif_alloc_binary(bin.size, &item->data)) {
|
|
enif_free(item);
|
|
return make_error(env, "enif_alloc_binary");
|
|
}
|
|
|
|
memcpy(item->data.data, bin.data, bin.size);
|
|
|
|
if (inst->ttl > 0) {
|
|
gettime(&item->added);
|
|
}
|
|
|
|
inst->push(inst, item);
|
|
return priv->atoms.ok;
|
|
}
|
|
|
|
static ERL_NIF_TERM
|
|
pop_item(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
|
|
priv_t *priv = (priv_t *) enif_priv_data(env);
|
|
|
|
queue_t *inst;
|
|
item_t *item;
|
|
|
|
if (!enif_get_resource(env, argv[0], priv->queue, (void**) &inst))
|
|
return enif_make_badarg(env);
|
|
|
|
// todo: check an owner of the queue
|
|
|
|
item = inst->pop(inst);
|
|
|
|
if (item == NULL)
|
|
return enif_make_list(env, 0);
|
|
|
|
ERL_NIF_TERM result = enif_make_binary(env, &item->data);
|
|
|
|
enif_free(item);
|
|
|
|
return enif_make_list1(env, result);
|
|
}
|
|
|
|
static ERL_NIF_TERM
|
|
queue_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
|
|
priv_t *priv = (priv_t *) enif_priv_data(env);
|
|
|
|
queue_t *inst;
|
|
|
|
if (!enif_get_resource(env, argv[0], priv->queue, (void**) &inst))
|
|
return enif_make_badarg(env);
|
|
|
|
return enif_make_uint64(env, inst->size(inst));
|
|
}
|
|
|
|
/******************************************************************************
|
|
** NIF initialization
|
|
*******************************************************************************/
|
|
|
|
static void
|
|
enq_queue_free(ErlNifEnv* env, void* obj) {
|
|
queue_t *inst = obj;
|
|
inst->free(inst);
|
|
}
|
|
|
|
static priv_t *
|
|
make_priv(ErlNifEnv *env) {
|
|
priv_t *priv = enif_alloc(sizeof(*priv));
|
|
|
|
if (priv == NULL)
|
|
return NULL;
|
|
|
|
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER;
|
|
priv->queue = enif_open_resource_type(env, NULL, "enq_queue", enq_queue_free, flags, NULL);
|
|
|
|
priv->atoms.ok = enif_make_atom(env, "ok");
|
|
priv->atoms.error = enif_make_atom(env, "error");
|
|
priv->atoms.fifo = enif_make_atom(env, "fifo");
|
|
priv->atoms.lifo = enif_make_atom(env, "lifo");
|
|
priv->atoms.ttl = enif_make_atom(env, "ttl");
|
|
priv->atoms.max_size = enif_make_atom(env, "max_size");
|
|
|
|
return priv;
|
|
}
|
|
|
|
static int
|
|
enq_nif_load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info) {
|
|
*priv_data = make_priv(env);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
enq_nif_upgrade(ErlNifEnv *env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info) {
|
|
*priv_data = make_priv(env);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ErlNifFunc enq_nif_funcs[] = {
|
|
{"new", 1, new_queue},
|
|
{"push", 2, push_item},
|
|
{"pop", 1, pop_item},
|
|
{"size", 1, queue_size},
|
|
};
|
|
|
|
ERL_NIF_INIT(enq_nif, enq_nif_funcs, enq_nif_load, NULL, enq_nif_upgrade, NULL)
|