Instead of storing a PyCapsule in _o and repeatedly doing lookups to
dereference a stored malloc'd pointer, it is easier to just directly
store a Python buffer-like object as _o. Then, instead of using
Py_{INC,DEC}REF across the paired aio_p{read,write} and corresponding
completion function, we now rely on PyObject_GetBuffer and
ByBuffer_Release. The use of memoryview protects us from the python
user changing the buffer out from under our feet:
$ ./run nbdsh
nbd> h.connect_command(['nbdkit','-s','memory','10'])
nbd> b = bytearray(10)
nbd> b1 = nbd.Buffer.from_bytearray(b)
nbd> h.aio_pread(b1, 0)
1
nbd> b.pop()
Traceback (most recent call last):
File "/usr/lib64/python3.10/code.py", line 90, in runcode
exec(code, self.locals)
File "<console>", line 1, in <module>
BufferError: Existing exports of data: object cannot be re-sized
nbd> h.poll(-1)
1
nbd> h.aio_command_completed(1)
True
nbd> b1 = None
nbd> b.pop()
0
This ALSO means that we're doing less copying! now that
nbd.Buffer.from_bytearray() is reusing an existing python object
instead of copying to a C malloc'd buffer, we have noticeable effects
on performance. For example, on my machine:
$ export script='
m=1024*1024
size=h.get_size()
zero=bytearray(m)
for i in range(size // m):
c = h.aio_pwrite(nbd.Buffer.from_bytearray(zero), m*i)
while not h.aio_command_completed(c):
h.poll(-1)
'
$ time nbdkit -U - null 10G --run 'nbdsh -u $uri -c "$script"'
takes 2.9s pre-patch, and 2.1s post-patch.
I noticed that nbd.Buffer(-1) changes from:
RuntimeError: length < 0
to
SystemError: Negative size passed to PyByteArray_FromStringAndSize
It would not be hard to revert that part, if needed.
Pure Python doesn't have any quick isinstance() test for whether an
object is buffer-like (only C has that, in PyObject_CheckBuffer) [1].
That makes it interesting to preserve our pre-existing semantics (from
the recent commit d477f7c7) where nbd.Buffer(int) gives uninitialized
memory, but nbd.Buffer.from_bytearray(int) relies on the
bytearray(int) constructor to give us initialized memory. For the
constructor, I had to use C, but for .from_bytearray (which is now
slightly misnamed, oh well), I chose to stick to a python try/except
instead of deferring to a C helper function.
[1]
https://stackoverflow.com/questions/30017991/check-if-an-object-supports-...
The generated code changes as follows:
| --- python/methods.h.bak 2022-06-03 17:10:24.533842689 -0500
| +++ python/methods.h 2022-06-03 17:10:33.833858093 -0500
| @@ -28,16 +28,11 @@
|
| #include <assert.h>
|
| -struct py_aio_buffer {
| - Py_ssize_t len;
| - void *data;
| -};
| -
| extern char **nbd_internal_py_get_string_list (PyObject *);
| extern void nbd_internal_py_free_string_list (char **);
| extern int nbd_internal_py_get_sockaddr (PyObject *,
| struct sockaddr_storage *, socklen_t *);
| -extern struct py_aio_buffer *nbd_internal_py_get_aio_buffer (PyObject *);
| +extern PyObject *nbd_internal_py_get_aio_buffer (PyObject *);
| extern PyObject *nbd_internal_py_get_nbd_buffer_type (void);
|
| static inline struct nbd_handle *
| @@ -66,9 +61,6 @@
| extern PyObject *nbd_internal_py_close (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_display_version (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_alloc_aio_buffer (PyObject *self, PyObject *args);
| -extern PyObject *nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject
*args);
| -extern PyObject *nbd_internal_py_aio_buffer_to_bytearray (PyObject *self, PyObject
*args);
| -extern PyObject *nbd_internal_py_aio_buffer_size (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_aio_buffer_is_zero (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_set_debug (PyObject *self, PyObject *args);
| extern PyObject *nbd_internal_py_get_debug (PyObject *self, PyObject *args);
| --- python/methods.c.bak 2022-06-03 17:10:25.741844689 -0500
| +++ python/methods.c 2022-06-03 17:13:09.689116273 -0500
| @@ -37,7 +37,7 @@
| */
| struct user_data {
| PyObject *fn; /* Optional pointer to Python function. */
| - PyObject *buf; /* Optional pointer to persistent buffer. */
| + Py_buffer view; /* persistent buffer, if view->obj set. */
| };
|
| static struct user_data *
| @@ -58,7 +58,7 @@ free_user_data (void *user_data)
|
| if (data) {
| Py_XDECREF (data->fn);
| - Py_XDECREF (data->buf);
| + PyBuffer_Release(&data->view);
| free (data);
| }
| }
| @@ -3163,7 +3163,7 @@ nbd_internal_py_aio_pread (PyObject *sel
| int64_t ret;
| PyObject *py_ret = NULL;
| PyObject *buf; /* instance of nbd.Buffer */
| - struct py_aio_buffer *buf_buf; /* Contents of nbd.Buffer */
| + PyObject *buf_buf; /* Contents of nbd.Buffer */
| uint64_t offset_u64;
| unsigned long long offset; /* really uint64_t */
| struct user_data *completion_user_data = NULL;
| @@ -3196,12 +3196,11 @@ nbd_internal_py_aio_pread (PyObject *sel
| flags_u32 = flags;
| buf_buf = nbd_internal_py_get_aio_buffer (buf);
| if (!buf_buf) goto out;
| - /* Increment refcount since buffer may be saved by libnbd. */
| - Py_INCREF (buf);
| - completion_user_data->buf = buf;
| + if (PyObject_GetBuffer(buf_buf, &completion_user_data->view,
| + PyBUF_CONTIG) < 0) goto out;
| offset_u64 = offset;
|
| - ret = nbd_aio_pread (h, buf_buf->data, buf_buf->len, offset_u64, completion,
flags_u32);
| + ret = nbd_aio_pread (h, completion_user_data->view.buf,
completion_user_data->view.len, offset_u64, completion, flags_u32);
| completion_user_data = NULL;
| if (ret == -1) {
| raise_exception ();
| @@ -3210,6 +3209,7 @@ nbd_internal_py_aio_pread (PyObject *sel
| py_ret = PyLong_FromLongLong (ret);
|
| out:
| + Py_XDECREF (buf_buf);
| free_user_data (completion_user_data);
| return py_ret;
| }
| @@ -3222,7 +3222,7 @@ nbd_internal_py_aio_pread_structured (Py
| int64_t ret;
| PyObject *py_ret = NULL;
| PyObject *buf; /* instance of nbd.Buffer */
| - struct py_aio_buffer *buf_buf; /* Contents of nbd.Buffer */
| + PyObject *buf_buf; /* Contents of nbd.Buffer */
| uint64_t offset_u64;
| unsigned long long offset; /* really uint64_t */
| struct user_data *chunk_user_data = NULL;
| @@ -3259,9 +3259,8 @@ nbd_internal_py_aio_pread_structured (Py
| flags_u32 = flags;
| buf_buf = nbd_internal_py_get_aio_buffer (buf);
| if (!buf_buf) goto out;
| - /* Increment refcount since buffer may be saved by libnbd. */
| - Py_INCREF (buf);
| - completion_user_data->buf = buf;
| + if (PyObject_GetBuffer(buf_buf, &completion_user_data->view,
| + PyBUF_CONTIG) < 0) goto out;
| offset_u64 = offset;
| chunk.user_data = chunk_user_data = alloc_user_data ();
| if (chunk_user_data == NULL) goto out;
| @@ -3274,7 +3273,7 @@ nbd_internal_py_aio_pread_structured (Py
| Py_INCREF (py_chunk_fn);
| chunk_user_data->fn = py_chunk_fn;
|
| - ret = nbd_aio_pread_structured (h, buf_buf->data, buf_buf->len, offset_u64,
chunk, completion, flags_u32);
| + ret = nbd_aio_pread_structured (h, completion_user_data->view.buf,
completion_user_data->view.len, offset_u64, chunk, completion, flags_u32);
| chunk_user_data = NULL;
| completion_user_data = NULL;
| if (ret == -1) {
| @@ -3284,6 +3283,7 @@ nbd_internal_py_aio_pread_structured (Py
| py_ret = PyLong_FromLongLong (ret);
|
| out:
| + Py_XDECREF (buf_buf);
| free_user_data (chunk_user_data);
| free_user_data (completion_user_data);
| return py_ret;
| @@ -3297,7 +3297,7 @@ nbd_internal_py_aio_pwrite (PyObject *se
| int64_t ret;
| PyObject *py_ret = NULL;
| PyObject *buf; /* instance of nbd.Buffer */
| - struct py_aio_buffer *buf_buf; /* Contents of nbd.Buffer */
| + PyObject *buf_buf; /* Contents of nbd.Buffer */
| uint64_t offset_u64;
| unsigned long long offset; /* really uint64_t */
| struct user_data *completion_user_data = NULL;
| @@ -3330,12 +3330,11 @@ nbd_internal_py_aio_pwrite (PyObject *se
| flags_u32 = flags;
| buf_buf = nbd_internal_py_get_aio_buffer (buf);
| if (!buf_buf) goto out;
| - /* Increment refcount since buffer may be saved by libnbd. */
| - Py_INCREF (buf);
| - completion_user_data->buf = buf;
| + if (PyObject_GetBuffer(buf_buf, &completion_user_data->view,
| + PyBUF_CONTIG_RO) < 0) goto out;
| offset_u64 = offset;
|
| - ret = nbd_aio_pwrite (h, buf_buf->data, buf_buf->len, offset_u64, completion,
flags_u32);
| + ret = nbd_aio_pwrite (h, completion_user_data->view.buf,
completion_user_data->view.len, offset_u64, completion, flags_u32);
| completion_user_data = NULL;
| if (ret == -1) {
| raise_exception ();
| @@ -3344,6 +3343,7 @@ nbd_internal_py_aio_pwrite (PyObject *se
| py_ret = PyLong_FromLongLong (ret);
|
| out:
| + Py_XDECREF (buf_buf);
| free_user_data (completion_user_data);
| return py_ret;
| }
| --- python/nbd.py.bak 2022-06-03 17:10:23.350840729 -0500
| +++ python/nbd.py 2022-06-03 17:10:33.852858124 -0500
| @@ -128,19 +128,21 @@ class Buffer(object):
|
| @classmethod
| def from_bytearray(cls, ba):
| - '''create an AIO buffer from a bytearray'''
| - o = libnbdmod.aio_buffer_from_bytearray(ba)
| + '''create an AIO buffer from a bytearray or other
buffer'''
| self = cls(0)
| - self._o = o
| + try:
| + self._o = memoryview(ba).cast('B')
| + except TypeError:
| + self._o = bytearray(ba)
| return self
|
| def to_bytearray(self):
| '''copy an AIO buffer into a bytearray'''
| - return libnbdmod.aio_buffer_to_bytearray(self)
| + return bytearray(self._o)
|
| def size(self):
| '''return the size of an AIO buffer'''
| - return libnbdmod.aio_buffer_size(self)
| + return len(self._o)
|
| def is_zero(self, offset=0, size=-1):
| '''
| @@ -154,7 +156,7 @@ class Buffer(object):
| always returns true. If size > 0, we check the interval
| [offset..offset+size-1].
| '''
| - return libnbdmod.aio_buffer_is_zero(self, offset, size)
| + return libnbdmod.aio_buffer_is_zero(self._o, offset, size)
|
|
| class NBD(object):
---
generator/Python.ml | 52 ++++++------
python/handle.c | 188 +++++++-------------------------------------
2 files changed, 52 insertions(+), 188 deletions(-)
diff --git a/generator/Python.ml b/generator/Python.ml
index b862b44..270858f 100644
--- a/generator/Python.ml
+++ b/generator/Python.ml
@@ -34,16 +34,11 @@ let
pr "#include <assert.h>\n";
pr "\n";
pr "\
-struct py_aio_buffer {
- Py_ssize_t len;
- void *data;
-};
-
extern char **nbd_internal_py_get_string_list (PyObject *);
extern void nbd_internal_py_free_string_list (char **);
extern int nbd_internal_py_get_sockaddr (PyObject *,
struct sockaddr_storage *, socklen_t *);
-extern struct py_aio_buffer *nbd_internal_py_get_aio_buffer (PyObject *);
+extern PyObject *nbd_internal_py_get_aio_buffer (PyObject *);
extern PyObject *nbd_internal_py_get_nbd_buffer_type (void);
static inline struct nbd_handle *
@@ -77,9 +72,6 @@ let
) ([ "create"; "close";
"display_version";
"alloc_aio_buffer";
- "aio_buffer_from_bytearray";
- "aio_buffer_to_bytearray";
- "aio_buffer_size";
"aio_buffer_is_zero" ] @ List.map fst handle_calls);
pr "\n";
@@ -109,9 +101,6 @@ let
) ([ "create"; "close";
"display_version";
"alloc_aio_buffer";
- "aio_buffer_from_bytearray";
- "aio_buffer_to_bytearray";
- "aio_buffer_size";
"aio_buffer_is_zero" ] @ List.map fst handle_calls);
pr " { NULL, NULL, 0, NULL }\n";
pr "};\n";
@@ -301,7 +290,7 @@ let
| BytesPersistIn (n, _)
| BytesPersistOut (n, _) ->
pr " PyObject *%s; /* instance of nbd.Buffer */\n" n;
- pr " struct py_aio_buffer *%s_buf; /* Contents of nbd.Buffer */\n" n
+ pr " PyObject *%s_buf; /* Contents of nbd.Buffer */\n" n
| Closure { cbname } ->
pr " struct user_data *%s_user_data = NULL;\n" cbname;
pr " PyObject *py_%s_fn;\n" cbname;
@@ -436,12 +425,16 @@ let
| BytesOut (n, count) ->
pr " %s = PyByteArray_FromStringAndSize (NULL, %s);\n" n count;
pr " if (%s == NULL) goto out;\n" n
- | BytesPersistIn (n, _) | BytesPersistOut (n, _) ->
+ | BytesPersistIn (n, _) ->
pr " %s_buf = nbd_internal_py_get_aio_buffer (%s);\n" n n;
pr " if (!%s_buf) goto out;\n" n;
- pr " /* Increment refcount since buffer may be saved by libnbd. */\n";
- pr " Py_INCREF (%s);\n" n;
- pr " completion_user_data->buf = %s;\n" n
+ pr " if (PyObject_GetBuffer(%s_buf,
&completion_user_data->view,\n" n;
+ pr " PyBUF_CONTIG_RO) < 0) goto out;\n"
+ | BytesPersistOut (n, _) ->
+ pr " %s_buf = nbd_internal_py_get_aio_buffer (%s);\n" n n;
+ pr " if (!%s_buf) goto out;\n" n;
+ pr " if (PyObject_GetBuffer(%s_buf,
&completion_user_data->view,\n" n;
+ pr " PyBUF_CONTIG) < 0) goto out;\n"
| Closure { cbname } ->
pr " %s.user_data = %s_user_data = alloc_user_data ();\n" cbname
cbname;
pr " if (%s_user_data == NULL) goto out;\n" cbname;
@@ -482,8 +475,8 @@ let
| Bool n -> pr ", %s" n
| BytesIn (n, _) -> pr ", %s.buf, %s.len" n n
| BytesOut (n, count) -> pr ", PyByteArray_AS_STRING (%s), %s" n count
- | BytesPersistIn (n, _)
- | BytesPersistOut (n, _) -> pr ", %s_buf->data, %s_buf->len" n n
+ | BytesPersistIn _ | BytesPersistOut _ ->
+ pr ", completion_user_data->view.buf,
completion_user_data->view.len"
| Closure { cbname } -> pr ", %s" cbname
| Enum (n, _) -> pr ", %s" n
| Flags (n, _) -> pr ", %s_u32" n
@@ -576,7 +569,8 @@ let
pr " if (%s.obj)\n" n;
pr " PyBuffer_Release (&%s);\n" n
| BytesOut (n, _) -> pr " Py_XDECREF (%s);\n" n
- | BytesPersistIn _ | BytesPersistOut _ -> ()
+ | BytesPersistIn (n, _) | BytesPersistOut (n, _) ->
+ pr " Py_XDECREF (%s_buf);\n" n
| Closure { cbname } ->
pr " free_user_data (%s_user_data);\n" cbname
| Enum _ -> ()
@@ -625,7 +619,7 @@ let
pr " */\n";
pr "struct user_data {\n";
pr " PyObject *fn; /* Optional pointer to Python function. */\n";
- pr " PyObject *buf; /* Optional pointer to persistent buffer. */\n";
+ pr " Py_buffer view; /* persistent buffer, if view->obj set. */\n";
pr "};\n";
pr "\n";
pr "static struct user_data *\n";
@@ -646,7 +640,7 @@ let
pr "\n";
pr " if (data) {\n";
pr " Py_XDECREF (data->fn);\n";
- pr " Py_XDECREF (data->buf);\n";
+ pr " PyBuffer_Release(&data->view);\n";
pr " free (data);\n";
pr " }\n";
pr "}\n";
@@ -768,19 +762,21 @@ let
@classmethod
def from_bytearray(cls, ba):
- '''create an AIO buffer from a bytearray'''
- o = libnbdmod.aio_buffer_from_bytearray(ba)
+ '''create an AIO buffer from a bytearray or other
buffer'''
self = cls(0)
- self._o = o
+ try:
+ self._o = memoryview(ba).cast('B')
+ except TypeError:
+ self._o = bytearray(ba)
return self
def to_bytearray(self):
'''copy an AIO buffer into a bytearray'''
- return libnbdmod.aio_buffer_to_bytearray(self)
+ return bytearray(self._o)
def size(self):
'''return the size of an AIO buffer'''
- return libnbdmod.aio_buffer_size(self)
+ return len(self._o)
def is_zero(self, offset=0, size=-1):
'''
@@ -794,7 +790,7 @@ let
always returns true. If size > 0, we check the interval
[offset..offset+size-1].
'''
- return libnbdmod.aio_buffer_is_zero(self, offset, size)
+ return libnbdmod.aio_buffer_is_zero(self._o, offset, size)
class NBD(object):
diff --git a/python/handle.c b/python/handle.c
index f84c6e0..7f67159 100644
--- a/python/handle.c
+++ b/python/handle.c
@@ -98,205 +98,73 @@ nbd_internal_py_display_version (PyObject *self, PyObject *args)
static const char aio_buffer_name[] = "nbd.Buffer";
-struct py_aio_buffer *
+PyObject *
nbd_internal_py_get_aio_buffer (PyObject *buffer)
{
- if (PyObject_IsInstance (buffer, nbd_internal_py_get_nbd_buffer_type ())) {
- PyObject *capsule = PyObject_GetAttrString(buffer, "_o");
- return PyCapsule_GetPointer (capsule, aio_buffer_name);
- }
+ if (PyObject_IsInstance (buffer, nbd_internal_py_get_nbd_buffer_type ()))
+ return PyObject_GetAttrString(buffer, "_o");
PyErr_SetString (PyExc_TypeError,
"aio_buffer: expecting nbd.Buffer instance");
return NULL;
}
-static void
-free_aio_buffer (PyObject *capsule)
-{
- struct py_aio_buffer *buf = PyCapsule_GetPointer (capsule, aio_buffer_name);
-
- if (buf)
- free (buf->data);
- free (buf);
-}
-
/* Allocate a persistent buffer used for nbd_aio_pread. */
PyObject *
nbd_internal_py_alloc_aio_buffer (PyObject *self, PyObject *args)
{
- struct py_aio_buffer *buf;
- PyObject *ret;
-
- buf = malloc (sizeof *buf);
- if (buf == NULL) {
- PyErr_NoMemory ();
- return NULL;
- }
-
- if (!PyArg_ParseTuple (args, (char *) "n:nbd_internal_py_alloc_aio_buffer",
- &buf->len)) {
- free (buf);
- return NULL;
- }
-
- if (buf->len < 0) {
- PyErr_SetString (PyExc_RuntimeError, "length < 0");
- free (buf);
- return NULL;
- }
- buf->data = malloc (buf->len);
- if (buf->data == NULL) {
- PyErr_NoMemory ();
- free (buf);
- return NULL;
- }
-
- ret = PyCapsule_New (buf, aio_buffer_name, free_aio_buffer);
- if (ret == NULL) {
- free (buf->data);
- free (buf);
- return NULL;
- }
-
- return ret;
-}
-
-PyObject *
-nbd_internal_py_aio_buffer_from_bytearray (PyObject *self, PyObject *args)
-{
- PyObject *obj;
- PyObject *arr = NULL;
Py_ssize_t len;
- void *data;
- struct py_aio_buffer *buf;
- PyObject *ret;
- if (!PyArg_ParseTuple (args,
- (char *)
"O:nbd_internal_py_aio_buffer_from_bytearray",
- &obj))
+ if (!PyArg_ParseTuple (args, (char *) "n:nbd_internal_py_alloc_aio_buffer",
+ &len))
return NULL;
- if (! PyByteArray_Check (obj)) {
- arr = PyByteArray_FromObject (obj);
- if (arr == NULL)
- return NULL;
- obj = arr;
- }
- data = PyByteArray_AsString (obj);
- if (!data) {
- PyErr_SetString (PyExc_RuntimeError,
- "parameter is not a bytearray or buffer");
- Py_XDECREF (arr);
- return NULL;
- }
- len = PyByteArray_Size (obj);
-
- buf = malloc (sizeof *buf);
- if (buf == NULL) {
- PyErr_NoMemory ();
- Py_XDECREF (arr);
- return NULL;
- }
-
- buf->len = len;
- buf->data = malloc (len);
- if (buf->data == NULL) {
- PyErr_NoMemory ();
- free (buf);
- Py_XDECREF (arr);
- return NULL;
- }
- memcpy (buf->data, data, len);
- Py_XDECREF (arr);
-
- ret = PyCapsule_New (buf, aio_buffer_name, free_aio_buffer);
- if (ret == NULL) {
- free (buf->data);
- free (buf);
- return NULL;
- }
-
- return ret;
-}
-
-PyObject *
-nbd_internal_py_aio_buffer_to_bytearray (PyObject *self, PyObject *args)
-{
- PyObject *obj;
- struct py_aio_buffer *buf;
-
- if (!PyArg_ParseTuple (args,
- (char *) "O:nbd_internal_py_aio_buffer_to_bytearray",
- &obj))
- return NULL;
-
- buf = nbd_internal_py_get_aio_buffer (obj);
- if (buf == NULL)
- return NULL;
-
- return PyByteArray_FromStringAndSize (buf->data, buf->len);
-}
-
-PyObject *
-nbd_internal_py_aio_buffer_size (PyObject *self, PyObject *args)
-{
- PyObject *obj;
- struct py_aio_buffer *buf;
-
- if (!PyArg_ParseTuple (args,
- (char *) "O:nbd_internal_py_aio_buffer_size",
- &obj))
- return NULL;
-
- buf = nbd_internal_py_get_aio_buffer (obj);
- if (buf == NULL)
- return NULL;
-
- return PyLong_FromSsize_t (buf->len);
+ /* Constructing bytearray(len) in python zeroes the memory; doing it this
+ * way gives uninitialized memory. This correctly flags negative len.
+ */
+ return PyByteArray_FromStringAndSize (NULL, len);
}
PyObject *
nbd_internal_py_aio_buffer_is_zero (PyObject *self, PyObject *args)
{
- PyObject *obj;
- struct py_aio_buffer *buf;
+ Py_buffer buf;
Py_ssize_t offset, size;
+ PyObject *ret = NULL;
if (!PyArg_ParseTuple (args,
- (char *) "Onn:nbd_internal_py_aio_buffer_is_zero",
- &obj, &offset, &size))
+ (char *) "y*nn:nbd_internal_py_aio_buffer_is_zero",
+ &buf, &offset, &size))
return NULL;
- if (size == 0)
- Py_RETURN_TRUE;
-
- buf = nbd_internal_py_get_aio_buffer (obj);
- if (buf == NULL)
- return NULL;
+ if (size == 0) {
+ ret = Py_True;
+ Py_INCREF (ret);
+ goto out;
+ }
/* Check the bounds of the offset. */
- if (offset < 0 || offset > buf->len) {
+ if (offset < 0 || offset > buf.len) {
PyErr_SetString (PyExc_IndexError, "offset out of range");
- return NULL;
+ goto out;
}
/* Compute or check the length. */
if (size == -1)
- size = buf->len - offset;
+ size = buf.len - offset;
else if (size < 0) {
PyErr_SetString (PyExc_IndexError,
"size cannot be negative, "
"except -1 to mean to the end of the buffer");
- return NULL;
+ goto out;
}
- else if ((size_t) offset + size > buf->len) {
+ else if ((size_t) offset + size > buf.len) {
PyErr_SetString (PyExc_IndexError, "size out of range");
- return NULL;
+ goto out;
}
- if (is_zero (buf->data + offset, size))
- Py_RETURN_TRUE;
- else
- Py_RETURN_FALSE;
+ ret = PyBool_FromLong (is_zero (buf.buf + offset, size));
+ out:
+ PyBuffer_Release(&buf);
+ return ret;
}
--
2.36.1