Skip to content

Commit

Permalink
Assume standard threading exists, which is should without Python 2.7/…
Browse files Browse the repository at this point in the history
…Win.

We are firmly in the 'need c++11 or greater' camp now.
  • Loading branch information
jamadden committed Jun 20, 2023
1 parent f3574c4 commit 80d01fd
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 241 deletions.
29 changes: 0 additions & 29 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,35 +92,6 @@ jobs:
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
run: |
twine upload --skip-existing dist/*
# XXX: Is non-standard thread mode even needed?
test_non_standard_thread:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.11"]
os: [ubuntu-20.04, macos-latest]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: setup.py
- name: Install dependencies
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -U twine
- name: Install greenlet
env:
CPPFLAGS: "-DG_USE_STANDARD_THREADING=0 -UNDEBUG -Ofast"
run: |
python setup.py bdist_wheel
python -m pip install -U -v -e ".[test,docs]"
- name: Test
run: |
python -c 'import greenlet._greenlet as G; assert not G.GREENLET_USE_STANDARD_THREADING'
python -m unittest discover -v greenlet.tests
CodeQL:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
- Remove support for end-of-life Python versions, including Python
2.7, Python 3.5 and Python 3.6.
- Require a compiler that supports ``noinline`` directives. See
`issue 271 <https://github.com/python-greenlet/greenlet/issues/266>`_
`issue 271
<https://github.com/python-greenlet/greenlet/issues/266>`_.
- Require a compiler that supports C++11.


2.0.2 (2023-01-28)
Expand Down
71 changes: 9 additions & 62 deletions src/greenlet/greenlet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -700,65 +700,16 @@ struct ThreadState_DestroyNoGIL

};

// The intent when GET_THREAD_STATE() is used multiple times in a function is to
// take a reference to it in a local variable, to avoid the
// thread-local indirection. On some platforms (macOS),
// accessing a thread-local involves a function call (plus an initial
// function call in each function that uses a thread local); in
// contrast, static volatile variables are at some pre-computed offset.

#if G_USE_STANDARD_THREADING == 1
// The intent when GET_THREAD_STATE() is needed multiple times in a
// function is to take a reference to its return value in a local
// variable, to avoid the thread-local indirection. On some platforms
// (macOS), accessing a thread-local involves a function call (plus an
// initial function call in each function that uses a thread local);
// in contrast, static volatile variables are at some pre-computed
// offset.
typedef greenlet::ThreadStateCreator<ThreadState_DestroyNoGIL> ThreadStateCreator;
static G_THREAD_LOCAL_VAR ThreadStateCreator g_thread_state_global;
static thread_local ThreadStateCreator g_thread_state_global;
#define GET_THREAD_STATE() g_thread_state_global
#else
// if we're not using standard threading, we're using
// the Python thread-local dictionary to perform our cleanup,
// which means we're deallocated when holding the GIL. The
// thread state is valid enough still for us to destroy
// stuff.
typedef greenlet::ThreadStateCreator<ThreadState_DestroyWithGIL> ThreadStateCreator;
#define G_THREAD_STATE_DICT_CLEANUP_TYPE
#include "greenlet_thread_state_dict_cleanup.hpp"
typedef greenlet::refs::OwnedReference<PyGreenletCleanup> OwnedGreenletCleanup;
// RECALL: legacy thread-local objects (__thread on GCC, __declspec(thread) on
// MSVC) can't have constructors or destructors, they have to be
// constant. So we indirect through a pointer and a function.
static G_THREAD_LOCAL_VAR ThreadStateCreator* _g_thread_state_global_ptr = nullptr;
static ThreadStateCreator& GET_THREAD_STATE()
{
if (!_g_thread_state_global_ptr) {
// NOTE: If any of this fails, we'll probably go on to hard
// crash the process, because we're returning a reference to a
// null pointer. we've called Py_FatalError(), but have no way
// to communicate that to the caller. Since these should
// essentially never fail unless the entire process is borked,
// a hard crash with a decent C++ backtrace from the exception
// is much more useful.
_g_thread_state_global_ptr = new ThreadStateCreator();
if (!_g_thread_state_global_ptr) {
throw PyFatalError("greenlet: Failed to create greenlet thread state.");
}

OwnedGreenletCleanup cleanup(OwnedGreenletCleanup::consuming(PyType_GenericAlloc(&PyGreenletCleanup_Type, 0)));
if (!cleanup) {
throw PyFatalError("greenlet: Failed to create greenlet thread state cleanup.");
}

cleanup->thread_state_creator = _g_thread_state_global_ptr;
assert(PyObject_GC_IsTracked(cleanup.borrow_o()));

PyObject* ts_dict_w = PyThreadState_GetDict();
if (!ts_dict_w) {
throw PyFatalError("greenlet: Failed to get Python thread state.");
}
if (PyDict_SetItemString(ts_dict_w, "__greenlet_cleanup", cleanup.borrow_o()) < 0) {
throw PyFatalError("greenlet: Failed to save cleanup key in Python thread state.");
}
}
return *_g_thread_state_global_ptr;
}
#endif


Greenlet::Greenlet(PyGreenlet* p)
Expand Down Expand Up @@ -3148,10 +3099,6 @@ greenlet_internal_mod_init() noexcept

Require(PyType_Ready(&PyGreenlet_Type));

#if G_USE_STANDARD_THREADING == 0
Require(PyType_Ready(&PyGreenletCleanup_Type));
#endif

mod_globs = new GreenletGlobals;
ThreadState::init();

Expand All @@ -3164,7 +3111,7 @@ greenlet_internal_mod_init() noexcept
// The macros are eithre 0 or 1; the 0 case can be interpreted
// the same as NULL, which is ambiguous with a pointer.
m.PyAddObject("GREENLET_USE_CONTEXT_VARS", (long)GREENLET_PY37);
m.PyAddObject("GREENLET_USE_STANDARD_THREADING", (long)G_USE_STANDARD_THREADING);
m.PyAddObject("GREENLET_USE_STANDARD_THREADING", 1L);

OwnedObject clocks_per_sec = OwnedObject::consuming(PyLong_FromSsize_t(CLOCKS_PER_SEC));
m.PyAddObject("CLOCKS_PER_SEC", clocks_per_sec);
Expand Down
1 change: 0 additions & 1 deletion src/greenlet/greenlet_compiler_compat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
# define G_FP_TMPL_STATIC
# endif


# define G_NO_COPIES_OF_CLS(Cls) private: \
Cls(const Cls& other) = delete; \
Cls& operator=(const Cls& other) = delete
Expand Down
22 changes: 2 additions & 20 deletions src/greenlet/greenlet_thread_state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -532,30 +532,12 @@ class ThreadStateCreator

};

#if G_USE_STANDARD_THREADING == 1

// We can't use the PythonAllocator for this, because we push to it
// from the thread state destructor, which doesn't have the GIL,
// and Python's allocators can only be called with the GIL.
typedef std::vector<ThreadState*> cleanup_queue_t;
#else
class cleanup_queue_t {
public:
inline ssize_t size() const { return 0; };
inline bool empty() const { return true; };
inline void pop_back()
{
throw std::out_of_range("empty queue.");
};
inline ThreadState* back()
{
throw std::out_of_range("empty queue.");
};
inline void push_back(ThreadState* g)
{
throw std::out_of_range("empty queue.");
};
};
#endif

}; // namespace greenlet

#endif
133 changes: 10 additions & 123 deletions src/greenlet/greenlet_thread_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,22 @@

/**
* Defines various utility functions to help greenlet integrate well
* with threads. When possible, we use portable C++ 11 threading; when
* not possible, we will use platform specific APIs if needed and
* available. (Currently, this is only for Python 2.7 on Windows.)
* with threads. This used to be needed when we supported Python
* 2.7 on Windows, which used a very old compiler. We wrote an
* alternative implementation using Python APIs and POSIX or Windows
* APIs, but that's no longer needed. So this file is a shadow of its
* former self --- but may be needed in the future.
*/

#include <stdexcept>
#include "greenlet_compiler_compat.hpp"
#include <thread>
#include <mutex>

// Allow setting this to 0 on the command line so that we
// can test these code paths on compilers that otherwise support
// standard threads.
#ifndef G_USE_STANDARD_THREADING
#if __cplusplus >= 201103
// Cool. We should have standard support
# define G_USE_STANDARD_THREADING 1
#elif defined(_MSC_VER)
// MSVC doesn't use a modern version of __cplusplus automatically, you
// have to opt-in to update it with /Zc:__cplusplus, but that's not
// available on our old version of visual studio for Python 2.7
# if _MSC_VER <= 1500
// Python 2.7 on Windows. Use the Python thread state and native Win32 APIs.
# define G_USE_STANDARD_THREADING 0
# else
// Assume we have a compiler that supports it. The Appveyor compilers
// we use all do have standard support
# define G_USE_STANDARD_THREADING 1
# endif
#elif defined(__GNUC__) || defined(__clang__)
// All tested versions either do, or can with the right --std argument, support what we need
# define G_USE_STANDARD_THREADING 1
#else
# define G_USE_STANDARD_THREADING 0
#endif
#endif /* G_USE_STANDARD_THREADING */
#include "greenlet_compiler_compat.hpp"

namespace greenlet {
typedef std::mutex Mutex;
typedef std::lock_guard<Mutex> LockGuard;
class LockInitError : public std::runtime_error
{
public:
Expand All @@ -48,97 +28,4 @@ namespace greenlet {
};


#if G_USE_STANDARD_THREADING == 1
# define G_THREAD_LOCAL_SUPPORTS_DESTRUCTOR 1
# include <thread>
# include <mutex>
# define G_THREAD_LOCAL_VAR thread_local
namespace greenlet {
typedef std::mutex Mutex;
typedef std::lock_guard<Mutex> LockGuard;
};
#else
// NOTE: At this writing, the mutex isn't currently required;
// we don't use a shared cleanup queue or Py_AddPendingCall in this
// model, we rely on the thread state dictionary for cleanup.
# if defined(_MSC_VER)
// We should only hit this case for Python 2.7 on Windows.
# define G_THREAD_LOCAL_VAR __declspec(thread)
# include <windows.h>
namespace greenlet {
class Mutex
{
CRITICAL_SECTION _mutex;
G_NO_COPIES_OF_CLS(Mutex);
public:
Mutex()
{
InitializeCriticalSection(&this->_mutex);
};

void Lock()
{
EnterCriticalSection(&this->_mutex);
};

void UnLock()
{
LeaveCriticalSection(&this->_mutex);
};
};
};
# elif (defined(__GNUC__) || defined(__clang__)) || (defined(__SUNPRO_C))
// GCC, clang, SunStudio all use __thread for thread-local variables.
// For locks, we can use PyThread APIs, officially added in 3.2, but
// present back to 2.7
# define G_THREAD_LOCAL_VAR __thread
# include "pythread.h"
namespace greenlet {
class Mutex
{
PyThread_type_lock _mutex;
G_NO_COPIES_OF_CLS(Mutex);
public:
Mutex()
{
this->_mutex = PyThread_allocate_lock();
if (!this->_mutex) {
throw LockInitError("Failed to initialize mutex.");
}
};

void Lock()
{
PyThread_acquire_lock(this->_mutex, WAIT_LOCK);
};

void UnLock()
{
PyThread_release_lock(this->_mutex);
};
};
};
# else
# error Unable to declare thread-local variables.
# endif
// the RAII lock keeper for all non-standard threading platforms.
namespace greenlet {
class LockGuard
{
Mutex& _mutex;
G_NO_COPIES_OF_CLS(LockGuard);
public:
LockGuard(Mutex& m) : _mutex(m)
{
this->_mutex.Lock();
};
~LockGuard()
{
this->_mutex.UnLock();
};
};

};
#endif /* G_USE_STANDARD_THREADING == 1 */

#endif /* GREENLET_THREAD_SUPPORT_HPP */
7 changes: 2 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ envlist =

# XXX: Is non-standard thread mode still needed? Check on that.
[testenv]
setenv =
ns: CPPFLAGS=-DG_USE_STANDARD_THREADING=0
commands =
ns: python -c 'import greenlet._greenlet as G; assert not G.GREENLET_USE_STANDARD_THREADING'
!ns: python -c 'import greenlet._greenlet as G; assert G.GREENLET_USE_STANDARD_THREADING'
python -c 'import greenlet._greenlet as G; assert G.GREENLET_USE_STANDARD_THREADING'
python -m unittest discover -v greenlet.tests
!ns: sphinx-build -b doctest -d docs/_build/doctrees-{envname} docs docs/_build/doctest-{envname}
sphinx-build -b doctest -d docs/_build/doctrees-{envname} docs docs/_build/doctest-{envname}
sitepackages = False
extras =
test
Expand Down

0 comments on commit 80d01fd

Please sign in to comment.