Skip to content

Commit

Permalink
pythongh-76785: Add SendChannel.send_buffer() (python#110246)
Browse files Browse the repository at this point in the history
(This is still a test module.)
  • Loading branch information
ericsnowcurrently authored Oct 9, 2023
1 parent f4cb0d2 commit 7bd560c
Show file tree
Hide file tree
Showing 13 changed files with 467 additions and 67 deletions.
16 changes: 15 additions & 1 deletion Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,26 @@ extern void _PyEval_InitState(PyInterpreterState *, PyThread_type_lock);
extern void _PyEval_FiniState(struct _ceval_state *ceval);
extern void _PyEval_SignalReceived(PyInterpreterState *interp);

// bitwise flags:
#define _Py_PENDING_MAINTHREADONLY 1
#define _Py_PENDING_RAWFREE 2

// Export for '_testinternalcapi' shared extension
PyAPI_FUNC(int) _PyEval_AddPendingCall(
PyInterpreterState *interp,
_Py_pending_call_func func,
void *arg,
int mainthreadonly);
int flags);

typedef int (*_Py_simple_func)(void *);
extern int _Py_CallInInterpreter(
PyInterpreterState *interp,
_Py_simple_func func,
void *arg);
extern int _Py_CallInInterpreterAndRawFree(
PyInterpreterState *interp,
_Py_simple_func func,
void *arg);

extern void _PyEval_SignalAsyncExc(PyInterpreterState *interp);
#ifdef HAVE_FORK
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_ceval_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct _pending_calls {
struct _pending_call {
_Py_pending_call_func func;
void *arg;
int flags;
} calls[NPENDINGCALLS];
int first;
int last;
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ _PyInterpreterState_SetFinalizing(PyInterpreterState *interp, PyThreadState *tst
}


extern PyInterpreterState* _PyInterpreterState_LookUpID(int64_t);
// Export for the _xxinterpchannels module.
PyAPI_FUNC(PyInterpreterState *) _PyInterpreterState_LookUpID(int64_t);

extern int _PyInterpreterState_IDInitref(PyInterpreterState *);
extern int _PyInterpreterState_IDIncref(PyInterpreterState *);
Expand Down
21 changes: 21 additions & 0 deletions Include/internal/pycore_pybuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef Py_INTERNAL_PYBUFFER_H
#define Py_INTERNAL_PYBUFFER_H
#ifdef __cplusplus
extern "C" {
#endif

#ifndef Py_BUILD_CORE
# error "this header requires Py_BUILD_CORE define"
#endif


// Exported for the _xxinterpchannels module.
PyAPI_FUNC(int) _PyBuffer_ReleaseInInterpreter(
PyInterpreterState *interp, Py_buffer *view);
PyAPI_FUNC(int) _PyBuffer_ReleaseInInterpreterAndRawFree(
PyInterpreterState *interp, Py_buffer *view);

#ifdef __cplusplus
}
#endif
#endif /* !Py_INTERNAL_PYBUFFER_H */
15 changes: 15 additions & 0 deletions Lib/test/support/interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,21 @@ def send_nowait(self, obj):
# See bpo-32604 and gh-19829.
return _channels.send(self._id, obj)

def send_buffer(self, obj):
"""Send the object's buffer to the channel's receiving end.
This blocks until the object is received.
"""
_channels.send_buffer(self._id, obj)

def send_buffer_nowait(self, obj):
"""Send the object's buffer to the channel's receiving end.
If the object is immediately received then return True
(else False). Otherwise this is the same as send().
"""
return _channels.send_buffer(self._id, obj)

def close(self):
_channels.close(self._id, send=True)

Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test__xxinterpchannels.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,21 @@ def test_recv_sending_interp_destroyed(self):
channels.recv(cid2)
del cid2

def test_send_buffer(self):
buf = bytearray(b'spamspamspam')
cid = channels.create()
channels.send_buffer(cid, buf)
obj = channels.recv(cid)

self.assertIsNot(obj, buf)
self.assertIsInstance(obj, memoryview)
self.assertEqual(obj, buf)

buf[4:8] = b'eggs'
self.assertEqual(obj, buf)
obj[4:8] = b'ham.'
self.assertEqual(obj, buf)

def test_allowed_types(self):
cid = channels.create()
objects = [
Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_interpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1067,3 +1067,46 @@ def test_recv_nowait_default(self):
self.assertEqual(obj4, b'spam')
self.assertEqual(obj5, b'eggs')
self.assertIs(obj6, default)

def test_send_buffer(self):
buf = bytearray(b'spamspamspam')
obj = None
rch, sch = interpreters.create_channel()

def f():
nonlocal obj
while True:
try:
obj = rch.recv()
break
except interpreters.ChannelEmptyError:
time.sleep(0.1)
t = threading.Thread(target=f)
t.start()

sch.send_buffer(buf)
t.join()

self.assertIsNot(obj, buf)
self.assertIsInstance(obj, memoryview)
self.assertEqual(obj, buf)

buf[4:8] = b'eggs'
self.assertEqual(obj, buf)
obj[4:8] = b'ham.'
self.assertEqual(obj, buf)

def test_send_buffer_nowait(self):
buf = bytearray(b'spamspamspam')
rch, sch = interpreters.create_channel()
sch.send_buffer_nowait(buf)
obj = rch.recv()

self.assertIsNot(obj, buf)
self.assertIsInstance(obj, memoryview)
self.assertEqual(obj, buf)

buf[4:8] = b'eggs'
self.assertEqual(obj, buf)
obj[4:8] = b'ham.'
self.assertEqual(obj, buf)
1 change: 1 addition & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,7 @@ PYTHON_HEADERS= \
$(srcdir)/Include/internal/pycore_parking_lot.h \
$(srcdir)/Include/internal/pycore_pathconfig.h \
$(srcdir)/Include/internal/pycore_pyarena.h \
$(srcdir)/Include/internal/pycore_pybuffer.h \
$(srcdir)/Include/internal/pycore_pyerrors.h \
$(srcdir)/Include/internal/pycore_pyhash.h \
$(srcdir)/Include/internal/pycore_pylifecycle.h \
Expand Down
Loading

0 comments on commit 7bd560c

Please sign in to comment.