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
17 changes: 11 additions & 6 deletions graalpython/com.oracle.graal.python.cext/src/capi.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -272,9 +272,13 @@ PyObject* _Py_NotImplementedStructReference;
*/
THREAD_LOCAL PyThreadState *tstate_current = NULL;

static void initialize_globals() {
// store the thread state into a thread local variable
tstate_current = GraalPyPrivate_ThreadState_Get(&tstate_current);
PyAPI_FUNC(PyThreadState **) GraalPyPrivate_InitThreadStateCurrent(PyThreadState *tstate) {
tstate_current = tstate;
return &tstate_current;
}

static void initialize_globals(PyThreadState *tstate) {
GraalPyPrivate_InitThreadStateCurrent(tstate);
_Py_NoneStructReference = GraalPyPrivate_None();
_Py_NotImplementedStructReference = GraalPyPrivate_NotImplemented();
_Py_EllipsisObjectReference = GraalPyPrivate_Ellipsis();
Expand Down Expand Up @@ -667,7 +671,7 @@ Py_LOCAL_SYMBOL TruffleContext* TRUFFLE_CONTEXT;
*/
Py_LOCAL_SYMBOL int8_t *_graalpy_finalizing = NULL;

PyAPI_FUNC(void) initialize_graal_capi(TruffleEnv* env, void **builtin_closures, GCState *gc) {
PyAPI_FUNC(PyThreadState **) initialize_graal_capi(TruffleEnv* env, void **builtin_closures, GCState *gc, PyThreadState *tstate) {
clock_t t = clock();

if (env) {
Expand Down Expand Up @@ -706,7 +710,7 @@ PyAPI_FUNC(void) initialize_graal_capi(TruffleEnv* env, void **builtin_closures,

initialize_builtin_types_and_structs();
// initialize global variables like '_Py_NoneStruct', etc.
initialize_globals();
initialize_globals(tstate);
initialize_exceptions();
initialize_hashes();
initialize_bufferprocs();
Expand All @@ -717,6 +721,7 @@ PyAPI_FUNC(void) initialize_graal_capi(TruffleEnv* env, void **builtin_closures,
Py_FileSystemDefaultEncoding = "utf-8"; // strdup(PyUnicode_AsUTF8(GraalPyPrivate_FileSystemDefaultEncoding()));

GraalPyPrivate_Log(PY_TRUFFLE_LOG_FINE, "initialize_graal_capi: %fs", ((double) (clock() - t)) / CLOCKS_PER_SEC);
return &tstate_current;
}

/*
Expand Down
13 changes: 10 additions & 3 deletions graalpython/com.oracle.graal.python.cext/src/pystate.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* Copyright (c) 2024, 2025, Oracle and/or its affiliates.
/* Copyright (c) 2024, 2026, Oracle and/or its affiliates.
* Copyright (C) 1996-2024 Python Software Foundation
*
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
Expand Down Expand Up @@ -84,9 +84,16 @@ static inline PyThreadState *
_get_thread_state() {
PyThreadState *ts = tstate_current;
if (UNLIKELY(ts == NULL)) {
ts = GraalPyPrivate_ThreadState_Get(&tstate_current);
tstate_current = ts;
/*
* Very unlikely fallback: this can happen if another thread initializes the C API while
* the current thread is attached to Python but blocked and therefore misses eager
* initialization of its native 'tstate_current' TLS slot.
*/
ts = GraalPyPrivate_ThreadState_Get(&tstate_current);
assert(ts != NULL);
tstate_current = ts;
}
assert(ts != NULL);
return ts;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
# SOFTWARE.

import datetime
import os
import subprocess
import sys
import textwrap
import time
import unittest

class DateTest(unittest.TestCase):
Expand Down Expand Up @@ -542,12 +547,29 @@ def test_strptime(self):
actual = datetime.datetime.strptime("+00:00 GMT", "%z %Z")
self.assertEqual(actual.tzinfo.tzname(None), "GMT")

import time
timezone_name = time.localtime().tm_zone
self.assertIsNotNone(timezone_name)
actual = datetime.datetime.strptime(f"+00:00 {timezone_name}", "%z %Z")
self.assertEqual(actual.tzinfo.tzname(None), timezone_name)

if hasattr(time, "tzset") and sys.executable:
proc = subprocess.run(
[sys.executable, "-c", textwrap.dedent("""\
import datetime
import time

time.tzset()
timezone_name = time.localtime().tm_zone
actual = datetime.datetime.strptime(f"+00:00 {timezone_name}", "%z %Z")
assert actual.tzinfo.tzname(None) == timezone_name
""")],
env={**os.environ, "TZ": "Etc/GMT-1"},
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
self.assertEqual(proc.returncode, 0, proc.stderr)

# time zone name without utc offset is ignored
actual = datetime.datetime.strptime("UTC", "%Z")
self.assertIsNone(actual.tzinfo)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -59,6 +59,13 @@ def test_inet_aton_errs(self):
self.assertRaises(OSError, lambda : socket.inet_aton('255.255.256.1'))
self.assertRaises(TypeError, lambda : socket.inet_aton(255))


class TestHostLookupErrors(unittest.TestCase):
def test_gethostbyname_ex_invalid_host_raises_gaierror(self):
with self.assertRaises(socket.gaierror):
socket.gethostbyname_ex("nonexistent.invalid")


def test_get_name_info():
import socket
try :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ private static int doExec(Node node, PythonContext context, PythonModule extensi
return 0;
}

if (!context.hasCApiContext()) {
if (context.getCApiState() != PythonContext.CApiState.INITIALIZED) {
throw PRaiseNode.raiseStatic(node, PythonBuiltinClassType.SystemError, ErrorMessages.CAPI_NOT_YET_INITIALIZED);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -385,7 +385,7 @@ static Object get(VirtualFrame frame, Object nameObj,
addrInfoCursorLib.release(cursor);
}
} catch (GetAddrInfoException e) {
throw constructAndRaiseNode.get(inliningTarget).executeWithArgsOnly(frame, SocketHError, new Object[]{e.getMessageAsTruffleString()});
throw constructAndRaiseNode.get(inliningTarget).executeWithArgsOnly(frame, SocketGAIError, new Object[]{e.getErrorCode(), e.getMessageAsTruffleString()});
} catch (PosixException e) {
throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
Expand Down Expand Up @@ -71,16 +71,13 @@
import com.oracle.graal.python.runtime.GilNode;
import com.oracle.graal.python.runtime.PythonContext;
import com.oracle.graal.python.runtime.PythonContext.PythonThreadState;
import com.oracle.graal.python.runtime.object.PFactory;
import com.oracle.graal.python.util.OverflowException;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.ThreadLocalAction;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.dsl.Bind;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;

Expand Down Expand Up @@ -116,19 +113,39 @@ static Object restore(
}
}

@CApiBuiltin(ret = PyThreadState, args = {Pointer}, call = Ignored)
/**
* Very unlikely fallback for threads that were already attached when another thread initialized
* the C API, but were blocked at that time and therefore could not process the thread-local
* action that eagerly initializes their native 'tstate_current' TLS slot.
*/
@CApiBuiltin(ret = PyThreadState, args = {Pointer}, acquireGil = false, call = Ignored)
abstract static class GraalPyPrivate_ThreadState_Get extends CApiUnaryBuiltinNode {
private static final TruffleLogger LOGGER = CApiContext.getLogger(GraalPyPrivate_ThreadState_Get.class);

@Specialization(limit = "1")
static Object get(Object tstateCurrentPtr,
@Bind Node inliningTarget,
@Bind PythonContext context,
@CachedLibrary("tstateCurrentPtr") InteropLibrary lib) {
PythonThreadState pythonThreadState = context.getThreadState(context.getLanguage(inliningTarget));
if (!lib.isNull(tstateCurrentPtr)) {
pythonThreadState.setNativeThreadLocalVarPointer(tstateCurrentPtr);
@Specialization
@TruffleBoundary
static Object get(Object tstateCurrentPtr) {
PythonContext context = PythonContext.get(null);
PythonThreadState threadState = context.getThreadState(context.getLanguage());

/*
* The C caller may have observed 'tstate_current == NULL' before entering this upcall.
* While entering this builtin, the same thread may process a queued thread-local action
* from C API initialization and initialize its native thread state eagerly. So the
* fallback decision made in C can be stale by the time we get here.
*/
if (threadState.isNativeThreadStateInitialized()) {
LOGGER.fine(() -> String.format("Lazy initialization attempt of native thread state for thread %s aborted. Was initialized in the meantime.", Thread.currentThread()));
Object nativeThreadState = PThreadState.getNativeThreadState(threadState);
assert nativeThreadState != null;
return nativeThreadState;
}
return PThreadState.getOrCreateNativeThreadState(pythonThreadState);

LOGGER.fine(() -> "Lazy (fallback) initialization of native thread state for thread " + Thread.currentThread());
assert PThreadState.getNativeThreadState(threadState) == null;
Object nativeThreadState = PThreadState.getOrCreateNativeThreadState(threadState);
threadState.setNativeThreadLocalVarPointer(tstateCurrentPtr);
return nativeThreadState;
}
}

Expand All @@ -151,12 +168,7 @@ static PDict get(
@Bind Node inliningTarget,
@Bind PythonContext context) {
PythonThreadState threadState = context.getThreadState(context.getLanguage(inliningTarget));
PDict threadStateDict = threadState.getDict();
if (threadStateDict == null) {
threadStateDict = PFactory.createDict(context.getLanguage());
threadState.setDict(threadStateDict);
}
return threadStateDict;
return PThreadState.getOrCreateThreadStateDict(context, threadState);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2310,22 +2310,14 @@ private static Object parse(String string, String format, PythonContext context,
TimeZone timeZone = TimeModuleBuiltins.getGlobalTimeZone(context);
String zoneName = timeZone.getDisplayName(false, TimeZone.SHORT);
String zoneNameDaylightSaving = timeZone.getDisplayName(true, TimeZone.SHORT);
String matchedZoneName = matchTimeZoneName(string, i, zoneName, zoneNameDaylightSaving, "UTC", "GMT");

if (string.startsWith("UTC", i)) {
builder.setTimeZoneName("UTC");
i += 3;
} else if (string.startsWith("GMT", i)) {
builder.setTimeZoneName("GMT");
i += 3;
} else if (string.startsWith(zoneName, i)) {
builder.setTimeZoneName(zoneName);
i += zoneName.length();
} else if (string.startsWith(zoneNameDaylightSaving, i)) {
builder.setTimeZoneName(zoneNameDaylightSaving);
i += zoneNameDaylightSaving.length();
} else {
if (matchedZoneName == null) {
throw PRaiseNode.raiseStatic(inliningTarget, ValueError, ErrorMessages.TIME_DATA_S_DOES_NOT_MATCH_FORMAT_S, string, format);
}

builder.setTimeZoneName(matchedZoneName);
i += matchedZoneName.length();
}
case 'j' -> {
var pos = new ParsePosition(i);
Expand Down Expand Up @@ -2487,6 +2479,16 @@ private static Integer parseDigits(String source, int from, int digitsCount) {
return result;
}

private static String matchTimeZoneName(String string, int from, String... candidates) {
String matched = null;
for (String candidate : candidates) {
if (candidate != null && string.startsWith(candidate, from) && (matched == null || candidate.length() > matched.length())) {
matched = candidate;
}
}
return matched;
}

@TruffleBoundary
private static Integer parseDigitsUpTo(String source, ParsePosition from, int maxDigitsCount) {
int result = 0;
Expand Down
Loading
Loading