Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 37 additions & 1 deletion tests/test_signals.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -310,7 +310,7 @@ async def coro():
with self.assertRaisesRegex(TypeError, 'coroutines cannot be used'):
self.loop.add_signal_handler(signal.SIGHUP, coro)

def test_wakeup_fd_unchanged(self):
def test_signals_wakeup_fd_unchanged(self):
async def runner():
PROG = R"""\
import uvloop
Expand DownExpand Up@@ -349,6 +349,42 @@ async def f(): pass

self.loop.run_until_complete(runner())

def test_signals_fork_in_thread(self):
# Refs #452, when forked from a thread, the main-thread-only signal
# operations failed thread ID checks because we didn't update
# MAIN_THREAD_ID after fork. It's now a lazy value set when needed and
# cleared after fork.
PROG = R"""\
import asyncio
import multiprocessing
import signal
import sys
import threading
import uvloop

multiprocessing.set_start_method('fork')

def subprocess():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)

def run():
loop = """ + self.NEW_LOOP + """
loop.add_signal_handler(signal.SIGINT, lambda *a: None)
p = multiprocessing.Process(target=subprocess)
t = threading.Thread(target=p.start)
t.start()
t.join()
p.join()
sys.exit(p.exitcode)

run()
"""

subprocess.check_call([
sys.executable, b'-W', b'ignore', b'-c', PROG,
])


class Test_UV_Signals(_TestSignal, tb.UVTestCase):
NEW_LOOP = 'uvloop.new_event_loop()'
Expand Down
11 changes: 11 additions & 0 deletions uvloop/includes/fork_handler.h
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
volatile uint64_t MAIN_THREAD_ID = 0;
volatile int8_t MAIN_THREAD_ID_SET = 0;

typedef void (*OnForkHandler)();

Expand All@@ -9,6 +11,10 @@ Note: Fork handler needs to be in C (not cython) otherwise it would require
GIL to be present, but some forks can exec non-python processes.
*/
void handleAtFork(void) {
// Reset the MAIN_THREAD_ID on fork, because the main thread ID is not
// always the same after fork, especially when forked from within a thread.
MAIN_THREAD_ID_SET = 0;

if (__forkHandler != NULL) {
__forkHandler();
}
Expand All@@ -25,3 +31,8 @@ void resetForkHandler(void)
{
__forkHandler = NULL;
}

void setMainThreadID(uint64_t id) {
MAIN_THREAD_ID = id;
MAIN_THREAD_ID_SET = 1;
}
2 changes: 1 addition & 1 deletion uvloop/includes/stdlib.pxi
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,8 +135,8 @@ cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL

cdef uint64_t MAIN_THREAD_ID = <uint64_t><int64_t>threading.main_thread().ident
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread

cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
Expand Down
5 changes: 5 additions & 0 deletions uvloop/includes/system.pxd
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
from libc.stdint cimport int8_t, uint64_t

cdef extern from "arpa/inet.h" nogil:

int ntohl(int)
Expand DownExpand Up@@ -85,7 +87,10 @@ cdef extern from "includes/compat.h" nogil:

cdef extern from "includes/fork_handler.h":

uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)
1 change: 0 additions & 1 deletion uvloop/loop.pxd
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,6 @@ cdef class Loop:
bint _stopping

uint64_t _thread_id
bint _thread_is_main

object _task_factory
object _exception_handler
Expand Down
13 changes: 6 additions & 7 deletions uvloop/loop.pyx
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,7 +142,6 @@ cdef class Loop:

self._closed = 0
self._debug = 0
self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -216,7 +215,11 @@ cdef class Loop:
self._servers = set()

cdef inline _is_main_thread(self):
return MAIN_THREAD_ID == PyThread_get_thread_ident()
cdef uint64_t main_thread_id = system.MAIN_THREAD_ID
if system.MAIN_THREAD_ID_SET == 0:
main_thread_id = <uint64_t><int64_t>threading_main_thread().ident
system.setMainThreadID(main_thread_id)
return main_thread_id == PyThread_get_thread_ident()

def __init__(self):
self.set_debug((not sys_ignore_environment
Expand DownExpand Up@@ -520,7 +523,6 @@ cdef class Loop:
self._last_error = None

self._thread_id = PyThread_get_thread_ident()
self._thread_is_main = MAIN_THREAD_ID == self._thread_id
self._running = 1

self.handler_check__exec_writes.start()
Expand All@@ -541,7 +543,6 @@ cdef class Loop:

self._pause_signals()

self._thread_is_main = 0
self._thread_id = 0
self._running = 0
self._stopping = 0
Expand DownExpand Up@@ -3287,16 +3288,14 @@ cdef Loop __forking_loop = None


cdef void __get_fork_handler() nogil:
global __forking
global __forking_loop

with gil:
if (__forking and __forking_loop is not None and
__forking_loop.active_process_handler is not None):
__forking_loop.active_process_handler._after_fork()

cdef __install_atfork():
global __atfork_installed

if __atfork_installed:
return
__atfork_installed = 1
Expand Down