Skip to content

Commit 7bb8f24

Browse files
authored
Merge branch 'master' into example-fix-314
2 parents 1605bc3 + a308f75 commit 7bb8f24

File tree

16 files changed

+375
-256
lines changed

16 files changed

+375
-256
lines changed

tests/test_context.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,9 +512,10 @@ async def write_over():
512512
proto.transport.write(b'q' * 16384)
513513
count += 1
514514
else:
515-
proto.transport.write(b'q' * 16384)
516515
proto.transport.set_write_buffer_limits(high=256, low=128)
517-
count += 1
516+
while not proto.transport.get_write_buffer_size():
517+
proto.transport.write(b'q' * 16384)
518+
count += 1
518519
return count
519520

520521
s = self.loop.run_in_executor(None, accept)

tests/test_dns.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ def test_getaddrinfo_22(self):
217217
self._test_getaddrinfo(payload, 80)
218218
self._test_getaddrinfo(payload, 80, type=socket.SOCK_STREAM)
219219

220+
def test_getaddrinfo_broadcast(self):
221+
self._test_getaddrinfo('<broadcast>', 80)
222+
self._test_getaddrinfo('<broadcast>', 80, type=socket.SOCK_STREAM)
223+
220224
######
221225

222226
def test_getnameinfo_1(self):

tests/test_fs_event.py

Lines changed: 67 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,87 @@
11
import asyncio
2+
import contextlib
23
import os.path
34
import tempfile
45

56
from uvloop import _testbase as tb
67
from uvloop.loop import FileSystemEvent
78

89

9-
class Test_UV_FS_EVENT_CHANGE(tb.UVTestCase):
10-
async def _file_writer(self):
11-
f = await self.q.get()
12-
while True:
13-
f.write('hello uvloop\n')
14-
f.flush()
15-
x = await self.q.get()
16-
if x is None:
17-
return
18-
19-
def fs_event_setup(self):
20-
self.change_event_count = 0
21-
self.fname = ''
22-
self.q = asyncio.Queue()
23-
24-
def event_cb(self, ev_fname: bytes, evt: FileSystemEvent):
25-
_d, fn = os.path.split(self.fname)
26-
self.assertEqual(ev_fname, fn)
27-
self.assertEqual(evt, FileSystemEvent.CHANGE)
28-
self.change_event_count += 1
29-
if self.change_event_count < 4:
30-
self.q.put_nowait(0)
31-
else:
32-
self.q.put_nowait(None)
10+
class Test_UV_FS_Event(tb.UVTestCase):
11+
def setUp(self):
12+
super().setUp()
13+
self.exit_stack = contextlib.ExitStack()
14+
self.tmp_dir = self.exit_stack.enter_context(
15+
tempfile.TemporaryDirectory()
16+
)
17+
18+
def tearDown(self):
19+
self.exit_stack.close()
20+
super().tearDown()
3321

3422
def test_fs_event_change(self):
35-
self.fs_event_setup()
36-
37-
async def run(write_task):
38-
self.q.put_nowait(tf)
39-
try:
40-
await asyncio.wait_for(write_task, 4)
41-
except asyncio.TimeoutError:
42-
write_task.cancel()
43-
44-
with tempfile.NamedTemporaryFile('wt') as tf:
45-
self.fname = tf.name.encode()
46-
h = self.loop._monitor_fs(tf.name, self.event_cb)
23+
change_event_count = 0
24+
filename = "fs_event_change.txt"
25+
path = os.path.join(self.tmp_dir, filename)
26+
q = asyncio.Queue()
27+
28+
with open(path, 'wt') as f:
29+
async def file_writer():
30+
while True:
31+
f.write('hello uvloop\n')
32+
f.flush()
33+
x = await q.get()
34+
if x is None:
35+
return
36+
37+
def event_cb(ev_fname: bytes, evt: FileSystemEvent):
38+
nonlocal change_event_count
39+
self.assertEqual(ev_fname, filename.encode())
40+
self.assertEqual(evt, FileSystemEvent.CHANGE)
41+
change_event_count += 1
42+
if change_event_count < 4:
43+
q.put_nowait(0)
44+
else:
45+
q.put_nowait(None)
46+
47+
h = self.loop._monitor_fs(path, event_cb)
48+
self.loop.run_until_complete(
49+
asyncio.sleep(0.1) # let monitor start
50+
)
4751
self.assertFalse(h.cancelled())
4852

49-
self.loop.run_until_complete(run(
50-
self.loop.create_task(self._file_writer())))
53+
self.loop.run_until_complete(asyncio.wait_for(file_writer(), 4))
5154
h.cancel()
5255
self.assertTrue(h.cancelled())
5356

54-
self.assertEqual(self.change_event_count, 4)
55-
56-
57-
class Test_UV_FS_EVENT_RENAME(tb.UVTestCase):
58-
async def _file_renamer(self):
59-
await self.q.get()
60-
os.rename(os.path.join(self.dname, self.changed_name),
61-
os.path.join(self.dname, self.changed_name + "-new"))
62-
await self.q.get()
63-
64-
def fs_event_setup(self):
65-
self.dname = ''
66-
self.changed_name = "hello_fs_event.txt"
67-
self.changed_set = {self.changed_name, self.changed_name + '-new'}
68-
self.q = asyncio.Queue()
69-
70-
def event_cb(self, ev_fname: bytes, evt: FileSystemEvent):
71-
ev_fname = ev_fname.decode()
72-
self.assertEqual(evt, FileSystemEvent.RENAME)
73-
self.changed_set.remove(ev_fname)
74-
if len(self.changed_set) == 0:
75-
self.q.put_nowait(None)
57+
self.assertEqual(change_event_count, 4)
7658

7759
def test_fs_event_rename(self):
78-
self.fs_event_setup()
79-
80-
async def run(write_task):
81-
self.q.put_nowait(0)
82-
try:
83-
await asyncio.wait_for(write_task, 4)
84-
except asyncio.TimeoutError:
85-
write_task.cancel()
86-
87-
with tempfile.TemporaryDirectory() as td_name:
88-
self.dname = td_name
89-
f = open(os.path.join(td_name, self.changed_name), 'wt')
60+
orig_name = "hello_fs_event.txt"
61+
new_name = "hello_fs_event_rename.txt"
62+
changed_set = {orig_name, new_name}
63+
event = asyncio.Event()
64+
65+
async def file_renamer():
66+
os.rename(os.path.join(self.tmp_dir, orig_name),
67+
os.path.join(self.tmp_dir, new_name))
68+
await event.wait()
69+
70+
def event_cb(ev_fname: bytes, evt: FileSystemEvent):
71+
ev_fname = ev_fname.decode()
72+
self.assertEqual(evt, FileSystemEvent.RENAME)
73+
changed_set.discard(ev_fname)
74+
if len(changed_set) == 0:
75+
event.set()
76+
77+
with open(os.path.join(self.tmp_dir, orig_name), 'wt') as f:
9078
f.write('hello!')
91-
f.close()
92-
h = self.loop._monitor_fs(td_name, self.event_cb)
93-
self.assertFalse(h.cancelled())
79+
h = self.loop._monitor_fs(self.tmp_dir, event_cb)
80+
self.loop.run_until_complete(asyncio.sleep(0.5)) # let monitor start
81+
self.assertFalse(h.cancelled())
9482

95-
self.loop.run_until_complete(run(
96-
self.loop.create_task(self._file_renamer())))
97-
h.cancel()
98-
self.assertTrue(h.cancelled())
83+
self.loop.run_until_complete(asyncio.wait_for(file_renamer(), 4))
84+
h.cancel()
85+
self.assertTrue(h.cancelled())
9986

100-
self.assertEqual(len(self.changed_set), 0)
87+
self.assertEqual(len(changed_set), 0)

tests/test_process.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,12 @@ async def cancel_make_transport():
685685
self.loop.run_until_complete(cancel_make_transport())
686686

687687
def test_cancel_post_init(self):
688+
if sys.version_info >= (3, 13) and self.implementation == 'asyncio':
689+
# https://github.com/python/cpython/issues/103847#issuecomment-3736561321
690+
# This test started to flake on CPython 3.13 and later,
691+
# so we skip it for asyncio tests until the issue is resolved.
692+
self.skipTest('flaky test on CPython 3.13+')
693+
688694
async def cancel_make_transport():
689695
coro = self.loop.subprocess_exec(asyncio.SubprocessProtocol,
690696
*self.PROGRAM_BLOCKED)

tests/test_tcp.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ async def client(addr):
405405
self.assertEqual(await reader.readexactly(2), b'OK')
406406

407407
re = r'(a bytes-like object)|(must be byte-ish)'
408-
if sys.version_info >= (3, 14):
408+
if sys.version_info >= (3, 13, 9):
409409
re += r'|(must be a bytes, bytearray, or memoryview object)'
410410
with self.assertRaisesRegex(TypeError, re):
411411
writer.write('AAAA')
@@ -1226,21 +1226,16 @@ def resume_writing(self):
12261226
t, p = await self.loop.create_connection(Protocol, *addr)
12271227

12281228
t.write(b'q' * 512)
1229-
self.assertEqual(t.get_write_buffer_size(), 512)
1230-
12311229
t.set_write_buffer_limits(low=16385)
1232-
self.assertFalse(paused)
12331230
self.assertEqual(t.get_write_buffer_limits(), (16385, 65540))
12341231

12351232
with self.assertRaisesRegex(ValueError, 'high.*must be >= low'):
12361233
t.set_write_buffer_limits(high=0, low=1)
12371234

12381235
t.set_write_buffer_limits(high=1024, low=128)
1239-
self.assertFalse(paused)
12401236
self.assertEqual(t.get_write_buffer_limits(), (128, 1024))
12411237

12421238
t.set_write_buffer_limits(high=256, low=128)
1243-
self.assertTrue(paused)
12441239
self.assertEqual(t.get_write_buffer_limits(), (128, 256))
12451240

12461241
t.close()

tests/test_udp.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,22 @@ def test_udp_sendto_dns(self):
378378
s_transport.close()
379379
self.loop.run_until_complete(asyncio.sleep(0.01))
380380

381+
def test_udp_sendto_broadcast(self):
382+
coro = self.loop.create_datagram_endpoint(
383+
asyncio.DatagramProtocol,
384+
local_addr=('127.0.0.1', 0),
385+
family=socket.AF_INET)
386+
387+
s_transport, server = self.loop.run_until_complete(coro)
388+
389+
try:
390+
s_transport.sendto(b'aaaa', ('<broadcast>', 80))
391+
except ValueError as exc:
392+
raise AssertionError('sendto raises {}.'.format(exc))
393+
394+
s_transport.close()
395+
self.loop.run_until_complete(asyncio.sleep(0.01))
396+
381397
def test_send_after_close(self):
382398
coro = self.loop.create_datagram_endpoint(
383399
asyncio.DatagramProtocol,

uvloop/cbhandles.pyx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ cdef class Handle:
99
cdef inline _set_loop(self, Loop loop):
1010
self.loop = loop
1111
if UVLOOP_DEBUG:
12-
loop._debug_cb_handles_total += 1
13-
loop._debug_cb_handles_count += 1
12+
system.__atomic_fetch_add(
13+
&loop._debug_cb_handles_total, 1, system.__ATOMIC_RELAXED)
14+
system.__atomic_fetch_add(
15+
&loop._debug_cb_handles_count, 1, system.__ATOMIC_RELAXED)
1416
if loop._debug:
1517
self._source_traceback = extract_stack()
1618

@@ -21,7 +23,8 @@ cdef class Handle:
2123

2224
def __dealloc__(self):
2325
if UVLOOP_DEBUG and self.loop is not None:
24-
self.loop._debug_cb_handles_count -= 1
26+
system.__atomic_fetch_sub(
27+
&self.loop._debug_cb_handles_count, 1, system.__ATOMIC_RELAXED)
2528
if self.loop is None:
2629
raise RuntimeError('Handle.loop is None in Handle.__dealloc__')
2730

@@ -174,8 +177,10 @@ cdef class TimerHandle:
174177
self._cancelled = 0
175178

176179
if UVLOOP_DEBUG:
177-
self.loop._debug_cb_timer_handles_total += 1
178-
self.loop._debug_cb_timer_handles_count += 1
180+
system.__atomic_fetch_add(
181+
&self.loop._debug_cb_timer_handles_total, 1, system.__ATOMIC_RELAXED)
182+
system.__atomic_fetch_add(
183+
&self.loop._debug_cb_timer_handles_count, 1, system.__ATOMIC_RELAXED)
179184

180185
if context is None:
181186
context = Context_CopyCurrent()
@@ -205,7 +210,8 @@ cdef class TimerHandle:
205210

206211
def __dealloc__(self):
207212
if UVLOOP_DEBUG:
208-
self.loop._debug_cb_timer_handles_count -= 1
213+
system.__atomic_fetch_sub(
214+
&self.loop._debug_cb_timer_handles_count, 1, system.__ATOMIC_RELAXED)
209215
if self.timer is not None:
210216
raise RuntimeError('active TimerHandle is deallacating')
211217

uvloop/handles/stream.pxd

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
cdef enum ProtocolType:
2+
SIMPLE = 0 # User Protocol doesn't support asyncio.BufferedProtocol
3+
BUFFERED = 1 # User Protocol supports asyncio.BufferedProtocol
4+
SSL_PROTOCOL = 2 # Our own SSLProtocol
5+
6+
17
cdef class UVStream(UVBaseTransport):
28
cdef:
39
uv.uv_shutdown_t _shutdown_req
410
bint __shutting_down
511
bint __reading
612
bint __read_error_close
713

8-
bint __buffered
14+
ProtocolType __protocol_type
915
object _protocol_get_buffer
1016
object _protocol_buffer_updated
1117

@@ -16,6 +22,8 @@ cdef class UVStream(UVBaseTransport):
1622
Py_buffer _read_pybuf
1723
bint _read_pybuf_acquired
1824

25+
cpdef write(self, object buf)
26+
1927
# All "inline" methods are final
2028

2129
cdef inline _init(self, Loop loop, object protocol, Server server,
@@ -39,8 +47,8 @@ cdef class UVStream(UVBaseTransport):
3947

4048
# _exec_write() is the method that does the actual send, and _try_write()
4149
# is a fast-path used in _exec_write() to send a single chunk.
42-
cdef inline _exec_write(self)
43-
cdef inline _try_write(self, object data)
50+
cdef inline bint _exec_write(self) except -1
51+
cdef inline Py_ssize_t _try_write(self, object data) except -2
4452

4553
cdef _close(self)
4654

0 commit comments

Comments
 (0)