Skip to content

Commit

Permalink
Use uvloop (winloop) as default loop, if available.
Browse files Browse the repository at this point in the history
  • Loading branch information
lschoe committed Jan 26, 2024
1 parent a223a38 commit af58e2c
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 43 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ jobs:
- python: 3.9
env:
- MPYC_NOPRSS=1
install: pip install numpy==1.23.* gmpy2
install: pip install numpy==1.23.* gmpy2 uvloop
- python: pypy3.9-7.3.9
install: pip install numpy==1.24.*
- python: 3.10
env:
- MPYC_NONUMPY=1
- MPYC_NOGMPY=1
- MPYC_NOUVLOOP=1
- python: 3.11
install: pip install numpy==1.25.* gmpy2
- python: 3.12
install: pip install numpy
install: pip install numpy uvloop
before_install:
- pip install --upgrade pip
- pip install coverage
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Use `pip install numpy` to enable support for secure NumPy arrays in MPyC, along

Use `pip install gmpy2` to run MPyC with the package [gmpy2](https://pypi.org/project/gmpy2/) for considerably better performance.

Use `pip install uvloop` (or `pip install winloop` on Windows) to replace Python's default asyncio event loop in MPyC for generally improved performance.

### Some Tips

- Try `run-all.sh` or `run-all.bat` in the `demos` directory to have a quick look at all pure Python demos.
Expand Down
33 changes: 22 additions & 11 deletions mpyc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
and statistics (securely mimicking Python’s statistics module).
"""

__version__ = '0.9.7'
__version__ = '0.9.8'
__license__ = 'MIT License'

import os
Expand Down Expand Up @@ -69,8 +69,6 @@ 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 All @@ -89,6 +87,8 @@ def get_arg_parser():
help='disable use of gmpy2 package')
group.add_argument('--no-numpy', action='store_true',
help='disable use of numpy package')
group.add_argument('--no-uvloop', action='store_true',
help='disable use of uvloop (winloop) package')
group.add_argument('--no-prss', action='store_true',
help='disable use of PRSS (pseudorandom secret sharing)')
group.add_argument('--mix32-64bit', action='store_true',
Expand Down Expand Up @@ -164,16 +164,27 @@ 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'):
# Load uvloop (winloop) if available and not disabled.
env_no_uvloop = int(os.getenv('MPYC_NOUVLOOP', '0'))
EventLoopPolicy = None
if importlib.util.find_spec('winloop' if sys.platform.startswith('win32') else 'uvloop'):
# uvloop (winloop) package available
if options.no_uvloop or env_no_uvloop:
logging.info(f'Use of package uvloop (winloop) inside MPyC disabled.')
elif sys.platform.startswith('win32'):
from winloop import EventLoopPolicy
logging.debug(f'Load winloop')
else:
from uvloop import EventLoopPolicy
asyncio.set_event_loop_policy(EventLoopPolicy())
elif options.event_loop == 2 and sys.platform.startswith('win32'):
from uvloop import EventLoopPolicy, _version
logging.debug(f'Load uvloop version {_version.__version__}')
del _version
# Hidden feature to set selector loop on Windows (only via MPYC_NOUVLOOP).
if env_no_uvloop == 2 and sys.platform.startswith('win32'):
# override default asyncio.WindowsProactorEventLoopPolicy
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
EventLoopPolicy = asyncio.WindowsSelectorEventLoopPolicy
if EventLoopPolicy:
# set event loop policy early (e.g., before mpyc.__main__.py is imported)
asyncio.set_event_loop_policy(EventLoopPolicy())
logging.debug(f'Event loop policy: {type(asyncio.get_event_loop_policy())}')

del options, env_max_workers, env_no_numpy, env_no_gmpy2
del options, env_max_workers, env_no_numpy, env_no_gmpy2, env_no_uvloop, EventLoopPolicy
31 changes: 1 addition & 30 deletions mpyc/asyncoro.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,6 @@ 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 @@ -158,18 +140,7 @@ def connection_lost(self, exc):
Otherwise, if the connection is lost unexpectedly, exc may indicate the cause.
"""
if 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
raise exc

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

0 comments on commit af58e2c

Please sign in to comment.