Description
Pycapnp mostly works by passing file descriptors into the capnproto c++ library. This causes some problems:
- Python wrappers around file descriptors are non-functional. For example, when reading messages from a file, gzip.open cannot be used. Similarly, Pythons socket SSL wrapper cannot be used in synchronous mode. A workaround exists to use ssl in asyncio mode, but that is also a hack.
- In synchronous mode, blocking operations cannot be interrupted by Python. See Consider using Cysignals #298 and How to handle interrupts in
KJ_SYSCALL
capnproto#1542. - It is difficult to get file descriptor asyncio read/write to be cross-platform due to platform support limitations.
To remedy this, I propose that instead of passing file descriptors to capnproto, we create wrappers around Python's IO facilities.
- For synchronous file operations, we write a subclass of
kj::InputStream
andkj::OutputStream
that wraps a Python file object- For synchronous socket operations, one can obtain a file-like object by calling socket.makefile. (This would also work with ssl.SSLSocket)
- For asyncio socket operations, we write a subclass of
kj::AsyncInputStream
andkj:AsyncOutputStream
that wraps a StreamReader and StreamWriter. - For asyncio file operations, it might be possible to use aiofile or aiofiles with the same interface as the previous point. To be investigated.
With these facilities, all classes like TwoPartyClient
and TwoPartyServer
will be modified to receive one of these wrapper classes. That should resolve all of the aforementioned problems.
A potential downside is that everything might slow down, because read and write operations have to be routed through the Python interpreter. I haven't measured the impact of this. If this is a problem, I guess we can also keep the file descriptor API (with it's known deficiencies). Downside of this, is that the file descriptor API requires a fairly large amount of code to work (see asyncProvider.cpp
in #310)