pycapnp/capnp/helpers/asyncHelper.h
Jacob Alexander 8915ef79f1
TwoWayPipe and basic asyncio support
Note: I've tried not to break any behaviour of the previously working APIs

Python API Changes / Additions
- capnp/lib/capnp.pyx
  * class _RemotePromise
    + [Added] cpdef _wait(self)
      = Exception raising code that used to be inside of wait(self)
    + [Modified] def wait(self)
      = Same functionality as before
    + [Added] async def a_wait(self)
      = Cannot use await as that's a reserved keyword
      = Uses pollRemote and asyncio.sleep(0) to make call asynchronous
  * class _TwoPartyVatNetwork
    + [Added] cdef _init_pipe(self, _TwoWayPipe pipe, Side side,
    schema_cpp.ReaderOptions opts)
      = Instanciates a TwoPartyVatNetwork using a TwoWayPipe (instead of
      using a file handle or connection as before)
  * class TwoPartyClient
    + [Modified] def __init__(self, socket=None, restorer=None,
    traversal_limit_in_words=None, nesting_limit=None)
      = Changes the socket parameter to be optional
      = If socket is not specified, default to using a TwoWayPipe
    + [Added] async def read(self, bufsize)
      = awaitable function that blocks until data has been read
      = bufsize defines the maximum amount of data to be read back
        (e.g. 4096 bytes)
      = Reads data from TwoWayPipe
    + [Added] def write(self, data)
      = Write data to TwoWayPipe
      = Not awaitable as the write interface of the TwoWayPipe doesn't
      have poll functionality
  * class TwoPartyServer
    + [Modified] def __init__(self, socket=None, restorer=None,
    server_socket=None, bootstrap=None, traversal_limit_in_words=None,
    nesting_limit=None)
      = Changes the socket parameter to be optional
      = If socket is not specified, default to using a TwoWayPipe
      = Simplified code by removing an else (self._connect)
    + [Added] async def read(self, bufsize)
      = awaitable function that blocks until data has been read
      = bufsize defines the maximum amount of data to be read back
        (e.g. 4096 bytes)
      = Reads data from TwoWayPipe
    + [Added] def write(self, data)
      = Write data to TwoWayPipe
      = Not awaitable as the write interface of the TwoWayPipe doesn't
      have poll functionality
    + [Added] async def poll_forever(self)
      = asyncio equivalent of run_forever()
  * class _TwoWayPipe
    + Wrapper class for TwoWayPipe

Other Additions
- capnp/helpers/asyncHelper.h
  * pollWaitScope
    + Pumps the kj event handler
    + Used for the TwoWayServer
  * pollRemote
    + Polls a remote promise
    + i.e. a capnp RPC call
- capnp/helpers/asyncIoHelper.h
  * AsyncIoStreamReadHelper
    + I wasn't able to figure out Promise[size_t] using Cython so this was
    the next best thing I could think of doing
    + Was needed to handle read polling from a read promise
      = Polling is used for asyncio as kj waits need a wrapper to be
      compatible
- capnp/lib/capnp.pyx
  * makeTwoWayPipe
    + Wrapper for kj newTwoWayPipe function
  * poll_once
    + Single pump of the kj event handler (used with pollWaitScope)

TwoWayClient Usage - TwoWayPipe
- See examples/async_client.py

TwoWayServer Usage - TwoWayPipe
- See examples/async_server.py

capnp/helpers/asyncIoHelper.h

Misc Changes
- Fixed thread_server.py and thread_client.py to use bootstrap instead
of ez_restore
- async_client.py and async_server.py examples
  * Uses the same thread.capnp as thread_client.py and thread_server.py
  * They are compatible, so you can mix and match client and server for
  compatibility testing
  * async_client.py and async_server.py require <address>:<port>
  formatting (unlike autodetection from thread_client.py and
  thread_server.py)
2019-09-26 21:42:48 -07:00

69 lines
1.9 KiB
C++

#pragma once
#include "kj/async.h"
#include "Python.h"
#include "capabilityHelper.h"
class PyEventPort: public kj::EventPort {
public:
PyEventPort(PyObject * _py_event_port): py_event_port(_py_event_port) {
// We don't need to incref/decref, since this C++ class will be owned by the Python wrapper class, and we'll make sure the python class doesn't refcount to 0 elsewhere.
// Py_INCREF(py_event_port);
}
virtual bool wait() {
GILAcquire gil;
PyObject_CallMethod(py_event_port, const_cast<char *>("wait"), NULL);
return true; // TODO: get the bool result from python
}
virtual bool poll() {
GILAcquire gil;
PyObject_CallMethod(py_event_port, const_cast<char *>("poll"), NULL);
return true; // TODO: get the bool result from python
}
virtual void setRunnable(bool runnable) {
GILAcquire gil;
PyObject * arg = Py_False;
if (runnable)
arg = Py_True;
PyObject_CallMethod(py_event_port, const_cast<char *>("set_runnable"), const_cast<char *>("o"), arg);
}
private:
PyObject * py_event_port;
};
void waitNeverDone(kj::WaitScope & scope) {
GILRelease gil;
kj::NEVER_DONE.wait(scope);
}
void pollWaitScope(kj::WaitScope & scope) {
GILRelease gil;
scope.poll();
}
kj::Timer * getTimer(kj::AsyncIoContext * context) {
return &context->lowLevelProvider->getTimer();
}
void waitVoidPromise(kj::Promise<void> * promise, kj::WaitScope & scope) {
GILRelease gil;
promise->wait(scope);
}
PyObject * waitPyPromise(kj::Promise<PyObject *> * promise, kj::WaitScope & scope) {
GILRelease gil;
return promise->wait(scope);
}
capnp::Response< ::capnp::DynamicStruct> * waitRemote(capnp::RemotePromise< ::capnp::DynamicStruct> * promise, kj::WaitScope & scope) {
GILRelease gil;
return new capnp::Response< ::capnp::DynamicStruct>(promise->wait(scope));
}
bool pollRemote(capnp::RemotePromise< ::capnp::DynamicStruct> * promise, kj::WaitScope & scope) {
GILRelease gil;
return promise->poll(scope);
}