Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
35b0fab
start unifying uvloop & winloop together
Vizonex Mar 17, 2026
6827264
Accomplishment: Uvloop now compiles on Windows
Vizonex Mar 18, 2026
0bb1a68
cleanup errors.pyx comments
Vizonex Mar 18, 2026
77bc6dc
forgot about setup.py let's add it
Vizonex Mar 18, 2026
acd5a8b
siginterrupt is not avalible on windows so make it optional
Vizonex Mar 18, 2026
994560c
try placing __forking variables outside of system OS check
Vizonex Mar 18, 2026
d3c2b4a
windows doesn't have childwatchers so disable it
Vizonex Mar 18, 2026
898b310
add gil technqiue for windows to simulate forking
Vizonex Mar 18, 2026
114ae18
add a non-deprecated packaging tool
Vizonex Mar 18, 2026
4037ba8
start introducing windows to the testsuite
Vizonex Mar 18, 2026
7fb3f43
skip fork test in signals if windows
Vizonex Mar 18, 2026
2ed0e62
merge more things from winloop on over to uvloop in test_signals
Vizonex Mar 18, 2026
46bc155
just port all the code over
Vizonex Mar 18, 2026
3d1fc4d
merge more tests
Vizonex Mar 18, 2026
921370b
merge both winloop & uvloop's error handling ways together
Vizonex Mar 18, 2026
250a656
merge more tests
Vizonex Mar 18, 2026
a42c101
merge more of winloop into uvloop, add comment about streams returnin…
Vizonex Mar 18, 2026
aca987b
force trigger workflow temporarly
Vizonex Mar 18, 2026
d8eac6a
Make All versions up to 3.12 work successfully. (#1) (Excluding Windo…
Vizonex Mar 19, 2026
9bd6dd6
temporary fix for working with windows in debug mode
Vizonex Mar 20, 2026
37e54a2
Merge branch 'windows' of https://github.com/Vizonex/uvloop into windows
Vizonex Mar 20, 2026
297be3e
All versions now work excluding debugs for windows in 3.13, 3.14 & 3.14t
Vizonex Mar 20, 2026
3778759
Add __Pyx_MonitoringEventTypes_CyGen_count macro (#3)
Vizonex Mar 21, 2026
2ccaf83
Fix subprocess_shell code (Windows Side) (#4)
Vizonex Mar 21, 2026
0a35c9f
Moved rest of the missing items back to stdlib.pxi (#6)
Vizonex Mar 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- ci
pull_request:
branches:
- windows
- master

jobs:
Expand All @@ -24,7 +25,8 @@ jobs:
- "3.13"
- "3.14"
- "3.14t"
os: [ubuntu-latest, macos-latest]
# TODO: (Vizonex) windows-11-arm
os: [ubuntu-latest, macos-latest, windows-latest]

env:
PIP_DISABLE_PIP_VERSION_CHECK: 1
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ _default: compile

clean:
rm -fr dist/ doc/_build/ *.egg-info uvloop/loop.*.pyd uvloop/loop_d.*.pyd
rm -fr uvloop/*.c uvloop/*.html uvloop/*.so
rm -fr uvloop/*.c uvloop/*.html uvloop/*.so uvloop/*.pyd
rm -fr uvloop/handles/*.html uvloop/includes/*.html
find . -name '__pycache__' | xargs rm -rf

Expand All @@ -34,8 +34,10 @@ setup-build:
compile: clean setup-build


# NOTE: --debug will not work on windows since it asks for a non-existant _d.lib file.
# TODO: Fix workflows for missing debug binaries in the future.
debug: clean
$(PYTHON) setup.py build_ext --inplace --debug \
$(PYTHON) setup.py build_ext --inplace \
--cython-always \
--cython-annotate \
--cython-directives="linetrace=True" \
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ test = [
'mypy>=0.800',
]
dev = [
'packaging',
'setuptools>=60',
'Cython~=3.0',
]
Expand Down
102 changes: 78 additions & 24 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
if vi < (3, 8):
raise RuntimeError('uvloop requires Python 3.8 or greater')

if sys.platform in ('win32', 'cygwin', 'cli'):
raise RuntimeError('uvloop does not support Windows at the moment')
# TODO: Remove Completely because Winloop Author is mergeing his project to uvloop.
# if sys.platform in ('win32', 'cygwin', 'cli'):
# raise RuntimeError('uvloop does not support Windows at the moment')

import os
import os.path
Expand All @@ -28,6 +29,9 @@
LIBUV_DIR = str(_ROOT / 'vendor' / 'libuv')
LIBUV_BUILD_DIR = str(_ROOT / 'build' / 'libuv-{}'.format(MACHINE))

# NOTE: Mingw was added by another contributor in the winloop project.
MINGW = bool(os.environ.get("MINGW_PREFIX", ""))


def _libuv_build_env():
env = os.environ.copy()
Expand Down Expand Up @@ -83,7 +87,9 @@ class uvloop_build_ext(build_ext):

def initialize_options(self):
super().initialize_options()
self.use_system_libuv = False
# Use mingw if prefix was given for it otherwise it
# will always be false.
self.use_system_libuv = MINGW
self.cython_always = False
self.cython_annotate = None
self.cython_directives = None
Expand All @@ -108,7 +114,8 @@ def finalize_options(self):
need_cythonize = True

if need_cythonize:
import pkg_resources
from packaging.requirements import Requirement
from packaging.version import Version

# Double check Cython presence in case setup_requires
# didn't go into effect (most likely because someone
Expand All @@ -118,17 +125,21 @@ def finalize_options(self):
import Cython
except ImportError:
raise RuntimeError(
'please install {} to compile uvloop from source'.format(
CYTHON_DEPENDENCY))
"please install {} to compile uvloop from source".format(
CYTHON_DEPENDENCY
)
)

cython_dep = pkg_resources.Requirement.parse(CYTHON_DEPENDENCY)
if Cython.__version__ not in cython_dep:
cython_dep = Requirement(CYTHON_DEPENDENCY)
if not cython_dep.specifier.contains(Version(Cython.__version__)):
raise RuntimeError(
'uvloop requires {}, got Cython=={}'.format(
"uvloop requires {}, got Cython=={}".format(
CYTHON_DEPENDENCY, Cython.__version__
))
)
)

from Cython.Build import cythonize


directives = {}
if self.cython_directives:
Expand Down Expand Up @@ -190,6 +201,15 @@ def build_libuv(self):
cwd=LIBUV_BUILD_DIR, env=env, check=True)

def build_extensions(self):
if sys.platform == "win32" and not MINGW:
path = pathlib.Path("vendor", "libuv", "src")
c_files = [p.as_posix() for p in path.iterdir() if p.suffix == ".c"]
c_files += [
p.as_posix() for p in (path / "win").iterdir() if p.suffix == ".c"
]
self.extensions[-1].sources += c_files
super().build_extensions()
return
if self.use_system_libuv:
self.compiler.add_library('uv')

Expand Down Expand Up @@ -229,28 +249,62 @@ def build_extensions(self):
raise RuntimeError(
'unable to read the version from uvloop/_version.py')

if sys.platform == "win32":
from Cython.Build import cythonize
from Cython.Compiler.Main import default_options

default_options["compile_time_env"] = dict(DEFAULT_FREELIST_SIZE=250)
ext = cythonize(
[
Extension(
"uvloop.loop",
sources=["uvloop/loop.pyx"],
include_dirs=[]
if MINGW
else [
"vendor/libuv/src",
"vendor/libuv/src/win",
"vendor/libuv/include",
],
# subset of libuv Windows libraries:
extra_link_args=[
(f"-l{lib}" if MINGW else f"{lib}.lib")
for lib in (
"Shell32",
"Ws2_32",
"Advapi32",
"iphlpapi",
"Userenv",
"User32",
"Dbghelp",
"Ole32",
)
],
define_macros=[("WIN32_LEAN_AND_MEAN", 1), ("_WIN32_WINNT", "0x0602")],
),
]
)
else:
ext = [
Extension(
"uvloop.loop",
sources=[
"uvloop/loop.pyx",
],
extra_compile_args=MODULES_CFLAGS,
),
]

setup_requires = []

if not (_ROOT / 'uvloop' / 'loop.c').exists() or '--cython-always' in sys.argv:
if not (_ROOT / "uvloop" / "loop.c").exists() or "--cython-always" in sys.argv:
# No Cython output, require Cython to build.
setup_requires.append(CYTHON_DEPENDENCY)


setup(
version=VERSION,
cmdclass={
'sdist': uvloop_sdist,
'build_ext': uvloop_build_ext
},
ext_modules=[
Extension(
"uvloop.loop",
sources=[
"uvloop/loop.pyx",
],
extra_compile_args=MODULES_CFLAGS
),
],
cmdclass={"sdist": uvloop_sdist, "build_ext": uvloop_build_ext},
ext_modules=ext,
setup_requires=setup_requires,
)
2 changes: 1 addition & 1 deletion tests/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def suite():
return test_suite


if __name__ == '__main__':
if __name__ == "__main__":
runner = unittest.runner.TextTestRunner()
result = runner.run(suite())
sys.exit(not result.wasSuccessful())
33 changes: 17 additions & 16 deletions tests/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,28 @@ class _TestAioHTTP:

def test_aiohttp_basic_1(self):

PAYLOAD = '<h1>It Works!</h1>' * 10000
PAYLOAD = "<h1>It Works!</h1>" * 10000

async def on_request(request):
return aiohttp.web.Response(text=PAYLOAD)

asyncio.set_event_loop(self.loop)
app = aiohttp.web.Application()
app.router.add_get('/', on_request)
app.router.add_get("/", on_request)

runner = aiohttp.web.AppRunner(app)
self.loop.run_until_complete(runner.setup())
site = aiohttp.web.TCPSite(runner, '0.0.0.0', '0')
site = aiohttp.web.TCPSite(runner, "0.0.0.0", "0")
self.loop.run_until_complete(site.start())
port = site._server.sockets[0].getsockname()[1]

async def test():
# Make sure we're using the correct event loop.
self.assertIs(asyncio.get_event_loop(), self.loop)

for addr in (('localhost', port),
('127.0.0.1', port)):
for addr in (("localhost", port), ("127.0.0.1", port)):
async with aiohttp.ClientSession() as client:
async with client.get('http://{}:{}'.format(*addr)) as r:
async with client.get("http://{}:{}".format(*addr)) as r:
self.assertEqual(r.status, 200)
result = await r.text()
self.assertEqual(result, PAYLOAD)
Expand All @@ -49,42 +48,43 @@ async def test():
self.loop.run_until_complete(runner.cleanup())

def test_aiohttp_graceful_shutdown(self):
if self.implementation == 'asyncio' and sys.version_info >= (3, 12, 0):
if self.implementation == "asyncio" and sys.version_info >= (3, 12, 0):
# In Python 3.12.0, asyncio.Server.wait_closed() waits for all
# existing connections to complete, before aiohttp sends
# on_shutdown signals.
# https://github.com/aio-libs/aiohttp/issues/7675#issuecomment-1752143748
# https://github.com/python/cpython/pull/98582
raise unittest.SkipTest('bug in aiohttp: #7675')
raise unittest.SkipTest("bug in aiohttp: #7675")

async def websocket_handler(request):
ws = aiohttp.web.WebSocketResponse()
await ws.prepare(request)
request.app['websockets'].add(ws)
request.app["websockets"].add(ws)
try:
async for msg in ws:
await ws.send_str(msg.data)
finally:
request.app['websockets'].discard(ws)
request.app["websockets"].discard(ws)
return ws

async def on_shutdown(app):
for ws in set(app['websockets']):
for ws in set(app["websockets"]):
await ws.close(
code=aiohttp.WSCloseCode.GOING_AWAY,
message='Server shutdown')
message="Server shutdown",
)

asyncio.set_event_loop(self.loop)
app = aiohttp.web.Application()
app.router.add_get('/', websocket_handler)
app.router.add_get("/", websocket_handler)
app.on_shutdown.append(on_shutdown)
app['websockets'] = weakref.WeakSet()
app["websockets"] = weakref.WeakSet()

runner = aiohttp.web.AppRunner(app)
self.loop.run_until_complete(runner.setup())
site = aiohttp.web.TCPSite(
runner,
'0.0.0.0',
"0.0.0.0",
0,
# https://github.com/aio-libs/aiohttp/pull/7188
shutdown_timeout=0.1,
Expand All @@ -95,7 +95,8 @@ async def on_shutdown(app):
async def client():
async with aiohttp.ClientSession() as client:
async with client.ws_connect(
'http://127.0.0.1:{}'.format(port)) as ws:
"http://127.0.0.1:{}".format(port)
) as ws:
await ws.send_str("hello")
async for msg in ws:
assert msg.data == "hello"
Expand Down
Loading
Loading