Current section

Files

Jump to
erlang_python c_src py_reactor_buffer.c
Raw

c_src/py_reactor_buffer.c

/*
* Copyright 2026 Benoit Chesneau
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file py_reactor_buffer.c
* @brief Zero-copy buffer implementation for reactor protocol layer
*/
/* Enable memmem on Linux (it's a GNU extension) */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "py_reactor_buffer.h"
#include <unistd.h>
#include <errno.h>
#include <string.h>
/* Portable memmem fallback for systems that don't have it.
* memmem is available on: Linux (glibc), macOS, FreeBSD >= 13
* Not available on: older BSDs, some embedded systems */
#if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(__FreeBSD__)
static void *portable_memmem(const void *haystack, size_t haystacklen,
const void *needle, size_t needlelen) {
if (needlelen == 0) return (void *)haystack;
if (needlelen > haystacklen) return NULL;
const unsigned char *h = haystack;
const unsigned char *n = needle;
const unsigned char *end = h + haystacklen - needlelen + 1;
while (h < end) {
h = memchr(h, n[0], end - h);
if (h == NULL) return NULL;
if (memcmp(h, n, needlelen) == 0) return (void *)h;
h++;
}
return NULL;
}
#define memmem portable_memmem
#endif
/* Resource type - initialized in py_nif.c */
ErlNifResourceType *REACTOR_BUFFER_RESOURCE_TYPE = NULL;
/* ============================================================================
* Resource Management
* ============================================================================ */
void reactor_buffer_resource_dtor(ErlNifEnv *env, void *obj) {
(void)env;
reactor_buffer_resource_t *buf = (reactor_buffer_resource_t *)obj;
if (buf->data != NULL) {
enif_free(buf->data);
buf->data = NULL;
}
}
reactor_buffer_resource_t *reactor_buffer_alloc(size_t capacity) {
if (REACTOR_BUFFER_RESOURCE_TYPE == NULL) {
return NULL;
}
reactor_buffer_resource_t *resource = enif_alloc_resource(
REACTOR_BUFFER_RESOURCE_TYPE, sizeof(reactor_buffer_resource_t));
if (resource == NULL) {
return NULL;
}
resource->data = enif_alloc(capacity);
if (resource->data == NULL) {
enif_release_resource(resource);
return NULL;
}
resource->size = 0;
resource->capacity = capacity;
resource->ref_count = 0;
return resource;
}
int reactor_buffer_read_fd(int fd, size_t max_size,
reactor_buffer_resource_t **out_resource,
size_t *out_size) {
if (max_size > REACTOR_MAX_READ_SIZE) {
max_size = REACTOR_MAX_READ_SIZE;
}
reactor_buffer_resource_t *resource = reactor_buffer_alloc(max_size);
if (resource == NULL) {
return -1;
}
ssize_t n = read(fd, resource->data, max_size);
if (n < 0) {
enif_release_resource(resource);
if (errno == EAGAIN || errno == EWOULDBLOCK) {
*out_resource = NULL;
*out_size = 0;
return 0; /* Would block - not an error */
}
return -1; /* Error */
}
if (n == 0) {
enif_release_resource(resource);
*out_resource = NULL;
*out_size = 0;
return 1; /* EOF */
}
resource->size = (size_t)n;
*out_resource = resource;
*out_size = (size_t)n;
return 0;
}
/* ============================================================================
* Python Buffer Protocol
* ============================================================================ */
static void ReactorBuffer_releasebuffer(PyObject *obj, Py_buffer *view) {
(void)view;
ReactorBufferObject *self = (ReactorBufferObject *)obj;
if (self->resource != NULL) {
self->resource->ref_count--;
}
}
static int ReactorBuffer_getbuffer(PyObject *obj, Py_buffer *view, int flags) {
ReactorBufferObject *self = (ReactorBufferObject *)obj;
if (self->resource == NULL || self->resource->data == NULL) {
PyErr_SetString(PyExc_BufferError, "Buffer has been released");
return -1;
}
view->obj = obj;
view->buf = self->resource->data;
view->len = self->resource->size;
view->readonly = 1;
view->itemsize = 1;
view->format = (flags & PyBUF_FORMAT) ? "B" : NULL;
view->ndim = 1;
view->shape = (flags & PyBUF_ND) ? &view->len : NULL;
view->strides = (flags & PyBUF_STRIDES) ? &view->itemsize : NULL;
view->suboffsets = NULL;
view->internal = NULL;
self->resource->ref_count++;
Py_INCREF(obj);
return 0;
}
static PyBufferProcs ReactorBuffer_as_buffer = {
.bf_getbuffer = ReactorBuffer_getbuffer,
.bf_releasebuffer = ReactorBuffer_releasebuffer,
};
/* ============================================================================
* Cached Memoryview Helper
* ============================================================================ */
/**
* Get or create the cached memoryview for fast buffer operations.
* The memoryview is created lazily and cached for subsequent accesses.
*/
static PyObject *ReactorBuffer_get_memoryview(ReactorBufferObject *self) {
if (self->cached_memoryview == NULL) {
if (self->resource == NULL || self->resource->data == NULL) {
PyErr_SetString(PyExc_BufferError, "Buffer has been released");
return NULL;
}
/* Create memoryview from self (uses our buffer protocol) */
self->cached_memoryview = PyMemoryView_FromObject((PyObject *)self);
if (self->cached_memoryview == NULL) {
return NULL;
}
}
Py_INCREF(self->cached_memoryview);
return self->cached_memoryview;
}
/* ============================================================================
* Python Sequence Protocol
* ============================================================================ */
static Py_ssize_t ReactorBuffer_length(ReactorBufferObject *self) {
if (self->resource == NULL) {
return 0;
}
return (Py_ssize_t)self->resource->size;
}
static PyObject *ReactorBuffer_item(ReactorBufferObject *self, Py_ssize_t i) {
if (self->resource == NULL || self->resource->data == NULL) {
PyErr_SetString(PyExc_IndexError, "Buffer has been released");
return NULL;
}
if (i < 0) {
i += self->resource->size;
}
if (i < 0 || (size_t)i >= self->resource->size) {
PyErr_SetString(PyExc_IndexError, "index out of range");
return NULL;
}
return PyLong_FromLong(self->resource->data[i]);
}
static PyObject *ReactorBuffer_subscript(ReactorBufferObject *self, PyObject *key) {
if (self->resource == NULL || self->resource->data == NULL) {
PyErr_SetString(PyExc_IndexError, "Buffer has been released");
return NULL;
}
if (PyLong_Check(key)) {
Py_ssize_t i = PyLong_AsSsize_t(key);
if (i == -1 && PyErr_Occurred()) {
return NULL;
}
return ReactorBuffer_item(self, i);
}
if (PySlice_Check(key)) {
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(key, self->resource->size,
&start, &stop, &step, &slicelength) < 0) {
return NULL;
}
if (step == 1) {
/* Contiguous slice - return bytes directly */
return PyBytes_FromStringAndSize(
(char *)self->resource->data + start, slicelength);
}
/* Non-contiguous slice - build bytes */
PyObject *result = PyBytes_FromStringAndSize(NULL, slicelength);
if (result == NULL) {
return NULL;
}
char *dest = PyBytes_AS_STRING(result);
for (Py_ssize_t i = 0, j = start; i < slicelength; i++, j += step) {
dest[i] = self->resource->data[j];
}
return result;
}
PyErr_SetString(PyExc_TypeError, "indices must be integers or slices");
return NULL;
}
static PySequenceMethods ReactorBuffer_as_sequence = {
.sq_length = (lenfunc)ReactorBuffer_length,
.sq_item = (ssizeargfunc)ReactorBuffer_item,
};
static PyMappingMethods ReactorBuffer_as_mapping = {
.mp_length = (lenfunc)ReactorBuffer_length,
.mp_subscript = (binaryfunc)ReactorBuffer_subscript,
};
/* ============================================================================
* Python Methods
* ============================================================================ */
static void ReactorBuffer_dealloc(ReactorBufferObject *self) {
Py_CLEAR(self->cached_memoryview);
if (self->resource_ref != NULL) {
enif_release_resource(self->resource_ref);
self->resource_ref = NULL;
self->resource = NULL;
}
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *ReactorBuffer_bytes(ReactorBufferObject *self, PyObject *Py_UNUSED(ignored)) {
if (self->resource == NULL || self->resource->data == NULL) {
return PyBytes_FromStringAndSize("", 0);
}
return PyBytes_FromStringAndSize((char *)self->resource->data,
self->resource->size);
}
/* Return memoryview for zero-copy access */
static PyObject *ReactorBuffer_memoryview(ReactorBufferObject *self, PyObject *Py_UNUSED(ignored)) {
return ReactorBuffer_get_memoryview(self);
}
static PyObject *ReactorBuffer_repr(ReactorBufferObject *self) {
if (self->resource == NULL) {
return PyUnicode_FromString("<ReactorBuffer (released)>");
}
return PyUnicode_FromFormat("<ReactorBuffer size=%zu>", self->resource->size);
}
/* Bytes-like method: startswith */
static PyObject *ReactorBuffer_startswith(ReactorBufferObject *self, PyObject *args) {
PyObject *prefix;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
if (!PyArg_ParseTuple(args, "O|nn:startswith", &prefix, &start, &end)) {
return NULL;
}
if (self->resource == NULL || self->resource->data == NULL) {
Py_RETURN_FALSE;
}
Py_buffer prefix_buf;
if (PyObject_GetBuffer(prefix, &prefix_buf, PyBUF_SIMPLE) < 0) {
return NULL;
}
Py_ssize_t size = self->resource->size;
if (start < 0) start += size;
if (start < 0) start = 0;
if (end < 0) end += size;
if (end > size) end = size;
if (start > end) {
PyBuffer_Release(&prefix_buf);
Py_RETURN_FALSE;
}
Py_ssize_t available = end - start;
int result = 0;
if (prefix_buf.len <= available) {
result = memcmp(self->resource->data + start, prefix_buf.buf,
prefix_buf.len) == 0;
}
PyBuffer_Release(&prefix_buf);
return PyBool_FromLong(result);
}
/* Bytes-like method: endswith */
static PyObject *ReactorBuffer_endswith(ReactorBufferObject *self, PyObject *args) {
PyObject *suffix;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
if (!PyArg_ParseTuple(args, "O|nn:endswith", &suffix, &start, &end)) {
return NULL;
}
if (self->resource == NULL || self->resource->data == NULL) {
Py_RETURN_FALSE;
}
Py_buffer suffix_buf;
if (PyObject_GetBuffer(suffix, &suffix_buf, PyBUF_SIMPLE) < 0) {
return NULL;
}
Py_ssize_t size = self->resource->size;
if (start < 0) start += size;
if (start < 0) start = 0;
if (end < 0) end += size;
if (end > size) end = size;
if (start > end) {
PyBuffer_Release(&suffix_buf);
Py_RETURN_FALSE;
}
Py_ssize_t available = end - start;
int result = 0;
if (suffix_buf.len <= available) {
Py_ssize_t offset = end - suffix_buf.len;
if (offset >= start) {
result = memcmp(self->resource->data + offset, suffix_buf.buf,
suffix_buf.len) == 0;
}
}
PyBuffer_Release(&suffix_buf);
return PyBool_FromLong(result);
}
/* Bytes-like method: find */
static PyObject *ReactorBuffer_find(ReactorBufferObject *self, PyObject *args) {
PyObject *sub;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
if (!PyArg_ParseTuple(args, "O|nn:find", &sub, &start, &end)) {
return NULL;
}
if (self->resource == NULL || self->resource->data == NULL) {
return PyLong_FromLong(-1);
}
Py_buffer sub_buf;
if (PyObject_GetBuffer(sub, &sub_buf, PyBUF_SIMPLE) < 0) {
return NULL;
}
Py_ssize_t size = self->resource->size;
if (start < 0) start += size;
if (start < 0) start = 0;
if (end < 0) end += size;
if (end > size) end = size;
Py_ssize_t result = -1;
if (start <= end && sub_buf.len <= (end - start)) {
const unsigned char *haystack = self->resource->data + start;
Py_ssize_t haystack_len = end - start;
const unsigned char *needle = sub_buf.buf;
Py_ssize_t needle_len = sub_buf.len;
if (needle_len == 0) {
result = start;
} else if (needle_len == 1) {
/* Single byte: use memchr (very fast) */
void *found = memchr(haystack, needle[0], haystack_len);
if (found != NULL) {
result = start + ((const unsigned char *)found - haystack);
}
} else {
/* Multi-byte: use memchr to find first byte, then memcmp to verify.
* This is faster than memmem for short patterns (HTTP parsing). */
const unsigned char *p = haystack;
Py_ssize_t remaining = haystack_len;
while (remaining >= needle_len) {
p = memchr(p, needle[0], remaining);
if (p == NULL) break;
if (memcmp(p, needle, needle_len) == 0) {
result = start + (p - haystack);
break;
}
p++;
remaining = haystack_len - (p - haystack);
}
}
}
PyBuffer_Release(&sub_buf);
return PyLong_FromSsize_t(result);
}
/* Bytes-like method: rfind */
static PyObject *ReactorBuffer_rfind(ReactorBufferObject *self, PyObject *args) {
PyObject *sub;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
if (!PyArg_ParseTuple(args, "O|nn:rfind", &sub, &start, &end)) {
return NULL;
}
if (self->resource == NULL || self->resource->data == NULL) {
return PyLong_FromLong(-1);
}
Py_buffer sub_buf;
if (PyObject_GetBuffer(sub, &sub_buf, PyBUF_SIMPLE) < 0) {
return NULL;
}
Py_ssize_t size = self->resource->size;
if (start < 0) start += size;
if (start < 0) start = 0;
if (end < 0) end += size;
if (end > size) end = size;
Py_ssize_t result = -1;
if (start <= end && sub_buf.len <= (end - start)) {
const unsigned char *haystack = self->resource->data + start;
Py_ssize_t haystack_len = end - start;
const unsigned char *needle = sub_buf.buf;
Py_ssize_t needle_len = sub_buf.len;
if (needle_len == 0) {
result = end;
} else {
/* Use memmem iteratively to find last occurrence */
const unsigned char *search_start = haystack;
Py_ssize_t search_len = haystack_len;
void *found;
while ((found = memmem(search_start, search_len, needle, needle_len)) != NULL) {
result = start + ((const unsigned char *)found - haystack);
/* Continue searching after this match */
search_start = (const unsigned char *)found + 1;
search_len = haystack_len - (search_start - haystack);
if (search_len < needle_len) break;
}
}
}
PyBuffer_Release(&sub_buf);
return PyLong_FromSsize_t(result);
}
/* Bytes-like method: index */
static PyObject *ReactorBuffer_index(ReactorBufferObject *self, PyObject *args) {
PyObject *result = ReactorBuffer_find(self, args);
if (result == NULL) {
return NULL;
}
Py_ssize_t pos = PyLong_AsSsize_t(result);
if (pos == -1 && !PyErr_Occurred()) {
Py_DECREF(result);
PyErr_SetString(PyExc_ValueError, "subsection not found");
return NULL;
}
return result;
}
/* Bytes-like method: count */
static PyObject *ReactorBuffer_count(ReactorBufferObject *self, PyObject *args) {
PyObject *sub;
Py_ssize_t start = 0;
Py_ssize_t end = PY_SSIZE_T_MAX;
if (!PyArg_ParseTuple(args, "O|nn:count", &sub, &start, &end)) {
return NULL;
}
if (self->resource == NULL || self->resource->data == NULL) {
return PyLong_FromLong(0);
}
Py_buffer sub_buf;
if (PyObject_GetBuffer(sub, &sub_buf, PyBUF_SIMPLE) < 0) {
return NULL;
}
Py_ssize_t size = self->resource->size;
if (start < 0) start += size;
if (start < 0) start = 0;
if (end < 0) end += size;
if (end > size) end = size;
Py_ssize_t count = 0;
if (start <= end && sub_buf.len > 0 && sub_buf.len <= (end - start)) {
const unsigned char *haystack = self->resource->data + start;
Py_ssize_t haystack_len = end - start;
const unsigned char *needle = sub_buf.buf;
Py_ssize_t needle_len = sub_buf.len;
if (needle_len == 1) {
/* Single byte: use memchr repeatedly */
const unsigned char *p = haystack;
Py_ssize_t remaining = haystack_len;
while ((p = memchr(p, needle[0], remaining)) != NULL) {
count++;
p++;
remaining = haystack_len - (p - haystack);
}
} else {
/* Multi-byte: use memchr + memcmp pattern */
const unsigned char *p = haystack;
Py_ssize_t remaining = haystack_len;
while (remaining >= needle_len) {
p = memchr(p, needle[0], remaining);
if (p == NULL) break;
if (memcmp(p, needle, needle_len) == 0) {
count++;
p += needle_len; /* Non-overlapping */
} else {
p++;
}
remaining = haystack_len - (p - haystack);
}
}
} else if (sub_buf.len == 0 && start <= end) {
/* Empty pattern matches between every character + at start/end */
count = (end - start) + 1;
}
PyBuffer_Release(&sub_buf);
return PyLong_FromSsize_t(count);
}
/* Bytes-like method: decode */
static PyObject *ReactorBuffer_decode(ReactorBufferObject *self, PyObject *args, PyObject *kwargs) {
static char *kwlist[] = {"encoding", "errors", NULL};
const char *encoding = "utf-8";
const char *errors = "strict";
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ss:decode", kwlist,
&encoding, &errors)) {
return NULL;
}
if (self->resource == NULL || self->resource->data == NULL) {
return PyUnicode_FromStringAndSize("", 0);
}
return PyUnicode_Decode((char *)self->resource->data, self->resource->size,
encoding, errors);
}
/* Bytes-like method: split */
static PyObject *ReactorBuffer_split(ReactorBufferObject *self, PyObject *args, PyObject *kwargs) {
/* Delegate to bytes.split by creating a bytes object */
PyObject *bytes_obj = ReactorBuffer_bytes(self, NULL);
if (bytes_obj == NULL) {
return NULL;
}
PyObject *split_meth = PyObject_GetAttrString(bytes_obj, "split");
if (split_meth == NULL) {
Py_DECREF(bytes_obj);
return NULL;
}
PyObject *result = PyObject_Call(split_meth, args, kwargs);
Py_DECREF(split_meth);
Py_DECREF(bytes_obj);
return result;
}
/* Bytes-like method: strip */
static PyObject *ReactorBuffer_strip(ReactorBufferObject *self, PyObject *args) {
PyObject *bytes_obj = ReactorBuffer_bytes(self, NULL);
if (bytes_obj == NULL) {
return NULL;
}
PyObject *result = PyObject_CallMethod(bytes_obj, "strip", "O", args);
Py_DECREF(bytes_obj);
return result;
}
/* Rich comparison (for == and other comparisons with bytes) */
static PyObject *ReactorBuffer_richcompare(PyObject *self, PyObject *other, int op) {
ReactorBufferObject *buf = (ReactorBufferObject *)self;
if (buf->resource == NULL || buf->resource->data == NULL) {
if (op == Py_EQ) {
Py_RETURN_FALSE;
} else if (op == Py_NE) {
Py_RETURN_TRUE;
}
Py_RETURN_NOTIMPLEMENTED;
}
Py_buffer other_buf;
if (PyObject_GetBuffer(other, &other_buf, PyBUF_SIMPLE) < 0) {
PyErr_Clear();
if (op == Py_EQ) {
Py_RETURN_FALSE;
} else if (op == Py_NE) {
Py_RETURN_TRUE;
}
Py_RETURN_NOTIMPLEMENTED;
}
int cmp;
if (buf->resource->size != (size_t)other_buf.len) {
cmp = (buf->resource->size < (size_t)other_buf.len) ? -1 : 1;
} else {
cmp = memcmp(buf->resource->data, other_buf.buf, buf->resource->size);
}
PyBuffer_Release(&other_buf);
switch (op) {
case Py_LT: return PyBool_FromLong(cmp < 0);
case Py_LE: return PyBool_FromLong(cmp <= 0);
case Py_EQ: return PyBool_FromLong(cmp == 0);
case Py_NE: return PyBool_FromLong(cmp != 0);
case Py_GT: return PyBool_FromLong(cmp > 0);
case Py_GE: return PyBool_FromLong(cmp >= 0);
default: Py_RETURN_NOTIMPLEMENTED;
}
}
/* Hash function (compatible with bytes) */
static Py_hash_t ReactorBuffer_hash(ReactorBufferObject *self) {
if (self->resource == NULL || self->resource->data == NULL) {
return 0;
}
/* Use the same hash as bytes */
PyObject *bytes = ReactorBuffer_bytes(self, NULL);
if (bytes == NULL) {
return -1;
}
Py_hash_t hash = PyObject_Hash(bytes);
Py_DECREF(bytes);
return hash;
}
/* Contains check for 'in' operator */
static int ReactorBuffer_contains(ReactorBufferObject *self, PyObject *arg) {
if (self->resource == NULL || self->resource->data == NULL) {
return 0;
}
Py_buffer sub_buf;
if (PyObject_GetBuffer(arg, &sub_buf, PyBUF_SIMPLE) < 0) {
return -1;
}
int result = 0;
if (sub_buf.len == 0) {
result = 1;
} else if ((size_t)sub_buf.len <= self->resource->size) {
const unsigned char *haystack = self->resource->data;
size_t haystack_len = self->resource->size;
const unsigned char *needle = sub_buf.buf;
size_t needle_len = sub_buf.len;
for (size_t i = 0; i <= haystack_len - needle_len; i++) {
if (memcmp(haystack + i, needle, needle_len) == 0) {
result = 1;
break;
}
}
}
PyBuffer_Release(&sub_buf);
return result;
}
/* Test helper to create a buffer for testing */
static PyObject *ReactorBuffer_test_create(PyTypeObject *type, PyObject *args) {
(void)type;
Py_buffer data_buf;
if (!PyArg_ParseTuple(args, "y*:_test_create", &data_buf)) {
return NULL;
}
reactor_buffer_resource_t *resource = reactor_buffer_alloc(data_buf.len);
if (resource == NULL) {
PyBuffer_Release(&data_buf);
PyErr_SetString(PyExc_MemoryError, "Failed to allocate buffer");
return NULL;
}
memcpy(resource->data, data_buf.buf, data_buf.len);
resource->size = data_buf.len;
PyBuffer_Release(&data_buf);
PyObject *result = ReactorBuffer_from_resource(resource, resource);
/* from_resource does enif_keep_resource, so we release our reference */
enif_release_resource(resource);
return result;
}
static PyMethodDef ReactorBuffer_methods[] = {
{"__bytes__", (PyCFunction)ReactorBuffer_bytes, METH_NOARGS,
"Return bytes copy of buffer"},
{"memoryview", (PyCFunction)ReactorBuffer_memoryview, METH_NOARGS,
"Return a memoryview for zero-copy access"},
{"startswith", (PyCFunction)ReactorBuffer_startswith, METH_VARARGS,
"Return True if buffer starts with prefix"},
{"endswith", (PyCFunction)ReactorBuffer_endswith, METH_VARARGS,
"Return True if buffer ends with suffix"},
{"find", (PyCFunction)ReactorBuffer_find, METH_VARARGS,
"Return lowest index of substring, or -1 if not found"},
{"rfind", (PyCFunction)ReactorBuffer_rfind, METH_VARARGS,
"Return highest index of substring, or -1 if not found"},
{"index", (PyCFunction)ReactorBuffer_index, METH_VARARGS,
"Return lowest index of substring; raise ValueError if not found"},
{"count", (PyCFunction)ReactorBuffer_count, METH_VARARGS,
"Return number of non-overlapping occurrences"},
{"decode", (PyCFunction)ReactorBuffer_decode, METH_VARARGS | METH_KEYWORDS,
"Decode the buffer using the specified encoding"},
{"split", (PyCFunction)ReactorBuffer_split, METH_VARARGS | METH_KEYWORDS,
"Split the buffer"},
{"strip", (PyCFunction)ReactorBuffer_strip, METH_VARARGS,
"Strip whitespace or specified bytes"},
{"_test_create", (PyCFunction)ReactorBuffer_test_create,
METH_VARARGS | METH_CLASS,
"Create a ReactorBuffer for testing (internal use)"},
{NULL}
};
/* Extend sequence methods with contains */
static PySequenceMethods ReactorBuffer_as_sequence_full = {
.sq_length = (lenfunc)ReactorBuffer_length,
.sq_item = (ssizeargfunc)ReactorBuffer_item,
.sq_contains = (objobjproc)ReactorBuffer_contains,
};
PyTypeObject ReactorBufferType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "erlang.reactor.ReactorBuffer",
.tp_doc = "Zero-copy reactor read buffer",
.tp_basicsize = sizeof(ReactorBufferObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_dealloc = (destructor)ReactorBuffer_dealloc,
.tp_repr = (reprfunc)ReactorBuffer_repr,
.tp_as_buffer = &ReactorBuffer_as_buffer,
.tp_as_sequence = &ReactorBuffer_as_sequence_full,
.tp_as_mapping = &ReactorBuffer_as_mapping,
.tp_hash = (hashfunc)ReactorBuffer_hash,
.tp_richcompare = ReactorBuffer_richcompare,
.tp_methods = ReactorBuffer_methods,
};
/* ============================================================================
* Initialization
* ============================================================================ */
PyObject *ReactorBuffer_from_resource(reactor_buffer_resource_t *resource,
void *resource_ref) {
ReactorBufferObject *obj = PyObject_New(ReactorBufferObject, &ReactorBufferType);
if (obj == NULL) {
return NULL;
}
obj->resource = resource;
obj->resource_ref = resource_ref;
obj->cached_memoryview = NULL; /* Created lazily on first use */
enif_keep_resource(resource_ref);
return (PyObject *)obj;
}
int ReactorBuffer_init_type(void) {
if (PyType_Ready(&ReactorBufferType) < 0) {
return -1;
}
return 0;
}
int ReactorBuffer_register_with_reactor(void) {
/* Import erlang module (created in py_callback.c) */
PyObject *erlang_module = PyImport_ImportModule("erlang");
if (erlang_module == NULL) {
/* Module doesn't exist yet - this shouldn't happen */
PyErr_Clear();
return -1;
}
/* Add ReactorBuffer type to the erlang module */
Py_INCREF(&ReactorBufferType);
if (PyModule_AddObject(erlang_module, "ReactorBuffer",
(PyObject *)&ReactorBufferType) < 0) {
Py_DECREF(&ReactorBufferType);
Py_DECREF(erlang_module);
return -1;
}
Py_DECREF(erlang_module);
return 0;
}