Skip to content

Commit

Permalink
Facilitate alternative event loops (like uvloop).
Browse files Browse the repository at this point in the history
  • Loading branch information
lschoe authored Jan 2, 2024
1 parent 90165a8 commit a11ff6a
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,4 @@ By running `python -m mpyc` instead you even get this REPL with the MPyC runtime
The file `gen.bat` shows how to generate fresh key material for SSL. To generate SSL key material of your own, first run
`pip install cryptography` (alternatively, run `pip install pyOpenSSL`).

Copyright © 2018-2023 Berry Schoenmakers
Copyright © 2018-2024 Berry Schoenmakers
17 changes: 16 additions & 1 deletion mpyc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@
and statistics (securely mimicking Python’s statistics module).
"""

__version__ = '0.9.6'
__version__ = '0.9.7'
__license__ = 'MIT License'

import os
import sys
import argparse
import logging
import importlib.util
import asyncio


def get_arg_parser():
Expand Down Expand Up @@ -68,6 +69,8 @@ def get_arg_parser():
help='enable SSL connections')
group.add_argument('-W', '--workers', type=int, metavar='w',
help='maximum number of worker threads per party')
group.add_argument('-E', '--event-loop', type=int, metavar='e',
help='0=asyncio_default 1=uvloop/winloop 2=WindowsSelector')

group = parser.add_argument_group('MPyC parameters')
group.add_argument('-L', '--bit-length', type=int, metavar='l',
Expand Down Expand Up @@ -161,4 +164,16 @@ def get_arg_parser():
if not env_no_gmpy2:
os.environ['MPYC_NOGMPY'] = '1' # NB: MPYC_NOGMPY also set for subprocesses

# Set event loop policy early (e.g., before mpyc.__main__.py is imported).
if options.event_loop == 1:
if sys.platform.startswith('win32'):
from winloop import EventLoopPolicy
else:
from uvloop import EventLoopPolicy
asyncio.set_event_loop_policy(EventLoopPolicy())
elif options.event_loop == 2 and sys.platform.startswith('win32'):
# override default asyncio.WindowsProactorEventLoopPolicy
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
logging.debug(f'Event loop policy: {type(asyncio.get_event_loop_policy())}')

del options, env_max_workers, env_no_numpy, env_no_gmpy2
31 changes: 30 additions & 1 deletion mpyc/asyncoro.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ def connection_made(self, transport):
to the peer as well as any PRSS keys.
"""
self.transport = transport
if 'winloop' in str(self.runtime._loop):
# Workaround to avoid problems with handling of high/low watermarks when
# running MPyC demos with large communication complexity.
# For example, "python np_lpsolver.py -i5 -M3 -E1"
# gives "OSError: [Errno 4088] Unknown error" (4088 stands for UV_EAGAIN).
# Winloop 0.1.0 default setting, with FLOW_CONTROL_HIGH_WATER = 64:
# high = 65536 = FLOW_CONTROL_HIGH_WATER * 1024
# low = 16 = FLOW_CONTROL_HIGH_WATER // 4
# Workaround:
self.transport.set_write_buffer_limits(high=2**18)
# With high=2**18, problem still present for cases with even larger communication
# complexity, like "python np_lpsolver.py -i5 -M3 -E1", and setting
# high=2**22 resolves this case as well.
# Alternative workaround: set high=0 to disable use of high/low watermarks.
# NB: these workarounds do not work with SSL,
# for example, "python np_lpsolver.py -i5 -M3 -E1 --ssl"
# keeps giving "OSError: [Errno 4088] Unknown error" whatever we set for high and low.
print(f'workaround for Winloop: {self.transport.get_write_buffer_limits()=}')
if self.peer_pid is not None: # this party is client (peer is server)
rt = self.runtime
pid_keys = [rt.pid.to_bytes(2, 'little')] # send pid
Expand Down Expand Up @@ -141,7 +159,18 @@ def connection_lost(self, exc):
Otherwise, if the connection is lost unexpectedly, exc may indicate the cause.
"""
if exc:
raise exc
if 'winloop' in str(self.runtime._loop):
# Workaround to suppress seemingly spurious UV_EOF (4095) exception from winloop.
# For example, "python helloworld.py -M2 --output-file -E1"
# creates a file "party2_1.log" with the error present.
# Does not happen over SSL, which makes sense as for SSL
# self.transport.can_write_eof() == False.
if exc.errno == 4095: # NB: exc is an OSError in Winloop 0.1.0
print(f'suppressed UV_EOF error in connection_lost(): {type(exc)=} {exc=}')
else:
raise exc
else:
raise exc

rt = self.runtime
rt.parties[self.peer_pid].protocol = None
Expand Down

0 comments on commit a11ff6a

Please sign in to comment.