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)
93 lines
2.2 KiB
Python
Executable File
93 lines
2.2 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
from __future__ import print_function
|
|
|
|
import asyncio
|
|
import argparse
|
|
import threading
|
|
import time
|
|
import capnp
|
|
import socket
|
|
|
|
import thread_capnp
|
|
|
|
capnp.remove_event_loop()
|
|
capnp.create_event_loop(threaded=True)
|
|
|
|
|
|
def parse_args():
|
|
parser = argparse.ArgumentParser(usage='Connects to the Example thread server \
|
|
at the given address and does some RPCs')
|
|
parser.add_argument("host", help="HOST:PORT")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
class StatusSubscriber(thread_capnp.Example.StatusSubscriber.Server):
|
|
|
|
'''An implementation of the StatusSubscriber interface'''
|
|
|
|
def status(self, value, **kwargs):
|
|
print('status: {}'.format(time.time()))
|
|
|
|
|
|
async def myreader(client, reader):
|
|
while True:
|
|
data = await reader.read(4096)
|
|
client.write(data)
|
|
|
|
|
|
async def mywriter(client, writer):
|
|
while True:
|
|
data = await client.read(4096)
|
|
writer.write(data.tobytes())
|
|
await writer.drain()
|
|
|
|
|
|
async def background(cap):
|
|
subscriber = StatusSubscriber()
|
|
promise = cap.subscribeStatus(subscriber)
|
|
await promise.a_wait()
|
|
|
|
|
|
async def main(host):
|
|
host = host.split(':')
|
|
addr = host[0]
|
|
port = host[1]
|
|
# Handle both IPv4 and IPv6 cases
|
|
try:
|
|
print("Try IPv4")
|
|
reader, writer = await asyncio.open_connection(
|
|
addr, port,
|
|
)
|
|
except:
|
|
print("Try IPv6")
|
|
reader, writer = await asyncio.open_connection(
|
|
addr, port,
|
|
family=socket.AF_INET6
|
|
)
|
|
|
|
# Start TwoPartyClient using TwoWayPipe (takes no arguments in this mode)
|
|
client = capnp.TwoPartyClient()
|
|
cap = client.bootstrap().cast_as(thread_capnp.Example)
|
|
|
|
# Assemble reader and writer tasks, run in the background
|
|
coroutines = [myreader(client, reader), mywriter(client, writer)]
|
|
asyncio.gather(*coroutines, return_exceptions=True)
|
|
|
|
# Start background task for subscriber
|
|
tasks = [background(cap)]
|
|
asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
# Run blocking tasks
|
|
print('main: {}'.format(time.time()))
|
|
await cap.longRunning().a_wait()
|
|
print('main: {}'.format(time.time()))
|
|
await cap.longRunning().a_wait()
|
|
print('main: {}'.format(time.time()))
|
|
await cap.longRunning().a_wait()
|
|
print('main: {}'.format(time.time()))
|
|
|
|
if __name__ == '__main__':
|
|
asyncio.run(main(parse_args().host))
|