Preserve interpreter-boundary diagnostics and syslog restrictions (#9365)

* Preserve Unicode in thread unraisable messages

Keep the callable representation as WTF-8 for unraisable hooks and audit callbacks. Preserve the existing String entry point and stderr fallback; this does not claim lossless stderr rendering.

Assisted-by: OpenAI Codex:model unavailable

* Use the WTF-8 string conversion constructor

Use the existing From<&str> implementation to copy the message prefix without changing Unicode semantics.

Assisted-by: OpenAI Codex:model unavailable

* Preserve cross-interpreter marshal error causes

Wrap failures from existing code/function serialization helpers as NotShareableError, matching the CPython 3.15 marshal boundary while retaining the original exception cause. No new test C API or lifecycle behavior.

Assisted-by: OpenAI Codex:model-unavailable

* Enforce syslog interpreter boundaries and sync queue cleanup

Reject subinterpreter openlog/closelog before audit or host mutation, and
reject implicit syslog opening after audit when the process logger is not
already open, matching CPython 3.15 guard placement and error text.
Already-open logging and setlogmask remain unchanged.

Migrate concurrent/interpreters/_queues.py through scripts/update_lib to
CPython d220409a13b77de7a6c30abe16ea90db01528edc, removing unused eager
pickle import and exception targets. Native serialization imports pickle
on demand; module import timing and debug locals may nevertheless differ.

No new fixtures or interpreter lifecycle mechanisms are introduced.

Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
youknowdot authored and GitHub committed 2026-10-26 14:15:38 +09:00
1 parent a90f7b9b9b
commit 1052e613ed
5 files changed
+45 -20

No files matched your search

+3 -4
View File
@@ -1,6 +1,5 @@
"""Cross-interpreter Queues High Level Module."""
import pickle
import queue
import time
import weakref
@@ -225,7 +224,7 @@ class Queue:
while True:
try:
_queues.put(self._id, obj, unboundop)
except QueueFull as exc:
except QueueFull:
if timeout is not None and time.time() >= end:
raise # re-raise
time.sleep(_delay)
@@ -260,7 +259,7 @@ class Queue:
while True:
try:
obj, unboundop = _queues.get(self._id)
except QueueEmpty as exc:
except QueueEmpty:
if timeout is not None and time.time() >= end:
raise # re-raise
time.sleep(_delay)
@@ -279,7 +278,7 @@ class Queue:
"""
try:
obj, unboundop = _queues.get(self._id)
except QueueEmpty as exc:
except QueueEmpty:
raise # re-raise
if unboundop is not None:
assert obj is None, repr(obj)
+11
View File
@@ -76,6 +76,9 @@ mod syslog {
#[pyfunction]
fn openlog(args: OpenLogArgs, vm: &VirtualMachine) -> PyResult<()> {
if !vm.state.is_main_interpreter() {
return Err(vm.new_runtime_error("subinterpreter can't use syslog.openlog()"));
}
let logoption = args.logoption;
let facility = args.facility;
let ident = match args.ident.clone() {
@@ -116,6 +119,11 @@ mod syslog {
vm.audit("syslog.syslog", || (priority, msg.clone()))?;
if !host_syslog::is_open() {
if !vm.state.is_main_interpreter() {
return Err(vm.new_runtime_error(
"subinterpreter can't use syslog.syslog() until the syslog is opened by the main interpreter",
));
}
openlog(
OpenLogArgs {
facility: LOG_USER,
@@ -132,6 +140,9 @@ mod syslog {
#[pyfunction]
fn closelog(vm: &VirtualMachine) -> PyResult<()> {
if !vm.state.is_main_interpreter() {
return Err(vm.new_runtime_error("subinterpreter can't use syslog.closelog()"));
}
vm.audit("syslog.closelog", || ())?;
host_syslog::closelog();
+6 -6
View File
@@ -805,12 +805,12 @@ pub(crate) mod _thread {
func: &ArgCallable,
vm: &VirtualMachine,
) {
let msg = func
.as_ref()
.repr(vm)
.ok()
.map(|repr| format!("Exception ignored in thread started by {}", repr.as_wtf8()));
vm.run_unraisable(exc, msg, vm.ctx.none());
let msg = func.as_ref().repr(vm).ok().map(|repr| {
let mut message = Wtf8Buf::from("Exception ignored in thread started by ");
message.push_wtf8(repr.as_wtf8());
vm.ctx.new_str(message)
});
vm.run_unraisable_with_message(exc, msg, vm.ctx.none());
}
fn run_thread(func: ArgCallable, args: FuncArgs, vm: &VirtualMachine) {
+15 -9
View File
@@ -2,7 +2,7 @@
//!
//! Interpreters do not share `PyObject` graphs. Shareable values are converted
//! to an interpreter-neutral payload and rebuilt in the destination.
// cspell:ignore unpickler
// cspell:ignore unpickler unmarshalled
#[cfg(not(feature = "threading"))]
use crate::protocol::BufferDescriptor;
@@ -351,17 +351,23 @@ impl SharedValue {
}
fn marshal_dumps(obj: &PyObject, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
let dumps = vm.import("marshal", 0)?.get_attr("dumps", vm)?;
let bytes = dumps.call((obj.to_owned(),), vm)?;
let bytes = bytes
.downcast::<PyBytes>()
.map_err(|_| vm.new_type_error("marshal.dumps() did not return bytes"))?;
Ok(bytes.as_bytes().to_vec())
let dump = || -> PyResult<Vec<u8>> {
let dumps = vm.import("marshal", 0)?.get_attr("dumps", vm)?;
let bytes = dumps.call((obj.to_owned(),), vm)?;
let bytes = bytes
.downcast::<PyBytes>()
.map_err(|_| vm.new_type_error("marshal.dumps() did not return bytes"))?;
Ok(bytes.as_bytes().to_vec())
};
dump().map_err(|cause| not_shareable_error_from(vm, "object could not be marshalled", cause))
}
fn marshal_loads(data: &[u8], vm: &VirtualMachine) -> PyResult {
let loads = vm.import("marshal", 0)?.get_attr("loads", vm)?;
loads.call((vm.ctx.new_bytes(data.to_vec()),), vm)
let load = || -> PyResult {
let loads = vm.import("marshal", 0)?.get_attr("loads", vm)?;
loads.call((vm.ctx.new_bytes(data.to_vec()),), vm)
};
load().map_err(|cause| not_shareable_error_from(vm, "object could not be unmarshalled", cause))
}
/// `_memoryview_from_xid`.
+10 -1
View File
@@ -1816,7 +1816,16 @@ impl VirtualMachine {
#[cold]
pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
let msg = msg.map(|msg| self.ctx.new_str(msg));
self.run_unraisable_with_message(e, msg.map(|msg| self.ctx.new_str(msg)), object);
}
#[cold]
pub(crate) fn run_unraisable_with_message(
&self,
e: PyBaseExceptionRef,
msg: Option<PyStrRef>,
object: PyObjectRef,
) {
let write_original = || {
let msg = msg.as_ref().map(|msg| msg.to_string_lossy());
self.write_unraisable_to_stderr(&e, msg.as_deref(), &object);