775 lines
25 KiB
C++
775 lines
25 KiB
C++
#ifndef GREENLET_GREENLET_HPP
|
|
#define GREENLET_GREENLET_HPP
|
|
/*
|
|
* Declarations of the core data structures.
|
|
*/
|
|
|
|
#define PY_SSIZE_T_CLEAN
|
|
#include <Python.h>
|
|
|
|
#include "greenlet_compiler_compat.hpp"
|
|
#include "greenlet_refs.hpp"
|
|
#include "greenlet_cpython_compat.hpp"
|
|
#include "greenlet_allocator.hpp"
|
|
|
|
using greenlet::refs::OwnedObject;
|
|
using greenlet::refs::OwnedGreenlet;
|
|
using greenlet::refs::OwnedMainGreenlet;
|
|
using greenlet::refs::BorrowedGreenlet;
|
|
|
|
#if PY_VERSION_HEX < 0x30B00A6
|
|
# define _PyCFrame CFrame
|
|
# define _PyInterpreterFrame _interpreter_frame
|
|
#endif
|
|
|
|
#if GREENLET_PY312
|
|
# include "internal/pycore_frame.h"
|
|
#endif
|
|
|
|
// XXX: TODO: Work to remove all virtual functions
|
|
// for speed of calling and size of objects (no vtable).
|
|
// One pattern is the Curiously Recurring Template
|
|
namespace greenlet
|
|
{
|
|
class ExceptionState
|
|
{
|
|
private:
|
|
G_NO_COPIES_OF_CLS(ExceptionState);
|
|
|
|
// Even though these are borrowed objects, we actually own
|
|
// them, when they're not null.
|
|
// XXX: Express that in the API.
|
|
private:
|
|
_PyErr_StackItem* exc_info;
|
|
_PyErr_StackItem exc_state;
|
|
public:
|
|
ExceptionState();
|
|
void operator<<(const PyThreadState *const tstate) noexcept;
|
|
void operator>>(PyThreadState* tstate) noexcept;
|
|
void clear() noexcept;
|
|
|
|
int tp_traverse(visitproc visit, void* arg) noexcept;
|
|
void tp_clear() noexcept;
|
|
};
|
|
|
|
template<typename T>
|
|
void operator<<(const PyThreadState *const tstate, T& exc);
|
|
|
|
class PythonStateContext
|
|
{
|
|
protected:
|
|
greenlet::refs::OwnedContext _context;
|
|
public:
|
|
inline const greenlet::refs::OwnedContext& context() const
|
|
{
|
|
return this->_context;
|
|
}
|
|
inline greenlet::refs::OwnedContext& context()
|
|
{
|
|
return this->_context;
|
|
}
|
|
|
|
inline void tp_clear()
|
|
{
|
|
this->_context.CLEAR();
|
|
}
|
|
|
|
template<typename T>
|
|
inline static PyObject* context(T* tstate)
|
|
{
|
|
return tstate->context;
|
|
}
|
|
|
|
template<typename T>
|
|
inline static void context(T* tstate, PyObject* new_context)
|
|
{
|
|
tstate->context = new_context;
|
|
tstate->context_ver++;
|
|
}
|
|
};
|
|
class SwitchingArgs;
|
|
class PythonState : public PythonStateContext
|
|
{
|
|
public:
|
|
typedef greenlet::refs::OwnedReference<struct _frame> OwnedFrame;
|
|
private:
|
|
G_NO_COPIES_OF_CLS(PythonState);
|
|
// We own this if we're suspended (although currently we don't
|
|
// tp_traverse into it; that's a TODO). If we're running, it's
|
|
// empty. If we get deallocated and *still* have a frame, it
|
|
// won't be reachable from the place that normally decref's
|
|
// it, so we need to do it (hence owning it).
|
|
OwnedFrame _top_frame;
|
|
#if GREENLET_USE_CFRAME
|
|
_PyCFrame* cframe;
|
|
int use_tracing;
|
|
#endif
|
|
#if GREENLET_PY312
|
|
int py_recursion_depth;
|
|
int c_recursion_depth;
|
|
#else
|
|
int recursion_depth;
|
|
#endif
|
|
int trash_delete_nesting;
|
|
#if GREENLET_PY311
|
|
_PyInterpreterFrame* current_frame;
|
|
_PyStackChunk* datastack_chunk;
|
|
PyObject** datastack_top;
|
|
PyObject** datastack_limit;
|
|
#endif
|
|
#if GREENLET_PY312
|
|
_PyInterpreterFrame* _prev_frame;
|
|
#endif
|
|
|
|
public:
|
|
PythonState();
|
|
// You can use this for testing whether we have a frame
|
|
// or not. It returns const so they can't modify it.
|
|
const OwnedFrame& top_frame() const noexcept;
|
|
|
|
inline void operator<<(const PyThreadState *const tstate) noexcept;
|
|
inline void operator>>(PyThreadState* tstate) noexcept;
|
|
void clear() noexcept;
|
|
|
|
int tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept;
|
|
void tp_clear(bool own_top_frame) noexcept;
|
|
void set_initial_state(const PyThreadState* const tstate) noexcept;
|
|
#if GREENLET_USE_CFRAME
|
|
void set_new_cframe(_PyCFrame& frame) noexcept;
|
|
#endif
|
|
inline void may_switch_away() noexcept;
|
|
inline void will_switch_from(PyThreadState *const origin_tstate) noexcept;
|
|
void did_finish(PyThreadState* tstate) noexcept;
|
|
};
|
|
|
|
class StackState
|
|
{
|
|
// By having only plain C (POD) members, no virtual functions
|
|
// or bases, we get a trivial assignment operator generated
|
|
// for us. However, that's not safe since we do manage memory.
|
|
// So we declare an assignment operator that only works if we
|
|
// don't have any memory allocated. (We don't use
|
|
// std::shared_ptr for reference counting just to keep this
|
|
// object small)
|
|
private:
|
|
char* _stack_start;
|
|
char* stack_stop;
|
|
char* stack_copy;
|
|
intptr_t _stack_saved;
|
|
StackState* stack_prev;
|
|
inline int copy_stack_to_heap_up_to(const char* const stop) noexcept;
|
|
inline void free_stack_copy() noexcept;
|
|
|
|
public:
|
|
/**
|
|
* Creates a started, but inactive, state, using *current*
|
|
* as the previous.
|
|
*/
|
|
StackState(void* mark, StackState& current);
|
|
/**
|
|
* Creates an inactive, unstarted, state.
|
|
*/
|
|
StackState();
|
|
~StackState();
|
|
StackState(const StackState& other);
|
|
StackState& operator=(const StackState& other);
|
|
inline void copy_heap_to_stack(const StackState& current) noexcept;
|
|
inline int copy_stack_to_heap(char* const stackref, const StackState& current) noexcept;
|
|
inline bool started() const noexcept;
|
|
inline bool main() const noexcept;
|
|
inline bool active() const noexcept;
|
|
inline void set_active() noexcept;
|
|
inline void set_inactive() noexcept;
|
|
inline intptr_t stack_saved() const noexcept;
|
|
inline char* stack_start() const noexcept;
|
|
static inline StackState make_main() noexcept;
|
|
#ifdef GREENLET_USE_STDIO
|
|
friend std::ostream& operator<<(std::ostream& os, const StackState& s);
|
|
#endif
|
|
};
|
|
#ifdef GREENLET_USE_STDIO
|
|
std::ostream& operator<<(std::ostream& os, const StackState& s);
|
|
#endif
|
|
|
|
class SwitchingArgs
|
|
{
|
|
private:
|
|
G_NO_ASSIGNMENT_OF_CLS(SwitchingArgs);
|
|
// If args and kwargs are both false (NULL), this is a *throw*, not a
|
|
// switch. PyErr_... must have been called already.
|
|
OwnedObject _args;
|
|
OwnedObject _kwargs;
|
|
public:
|
|
|
|
SwitchingArgs()
|
|
{}
|
|
|
|
SwitchingArgs(const OwnedObject& args, const OwnedObject& kwargs)
|
|
: _args(args),
|
|
_kwargs(kwargs)
|
|
{}
|
|
|
|
SwitchingArgs(const SwitchingArgs& other)
|
|
: _args(other._args),
|
|
_kwargs(other._kwargs)
|
|
{}
|
|
|
|
const OwnedObject& args()
|
|
{
|
|
return this->_args;
|
|
}
|
|
|
|
const OwnedObject& kwargs()
|
|
{
|
|
return this->_kwargs;
|
|
}
|
|
|
|
/**
|
|
* Moves ownership from the argument to this object.
|
|
*/
|
|
SwitchingArgs& operator<<=(SwitchingArgs& other)
|
|
{
|
|
if (this != &other) {
|
|
this->_args = other._args;
|
|
this->_kwargs = other._kwargs;
|
|
other.CLEAR();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Acquires ownership of the argument (consumes the reference).
|
|
*/
|
|
SwitchingArgs& operator<<=(PyObject* args)
|
|
{
|
|
this->_args = OwnedObject::consuming(args);
|
|
this->_kwargs.CLEAR();
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Acquires ownership of the argument.
|
|
*
|
|
* Sets the args to be the given value; clears the kwargs.
|
|
*/
|
|
SwitchingArgs& operator<<=(OwnedObject& args)
|
|
{
|
|
assert(&args != &this->_args);
|
|
this->_args = args;
|
|
this->_kwargs.CLEAR();
|
|
args.CLEAR();
|
|
|
|
return *this;
|
|
}
|
|
|
|
explicit operator bool() const noexcept
|
|
{
|
|
return this->_args || this->_kwargs;
|
|
}
|
|
|
|
inline void CLEAR()
|
|
{
|
|
this->_args.CLEAR();
|
|
this->_kwargs.CLEAR();
|
|
}
|
|
|
|
const std::string as_str() const noexcept
|
|
{
|
|
return PyUnicode_AsUTF8(
|
|
OwnedObject::consuming(
|
|
PyUnicode_FromFormat(
|
|
"SwitchingArgs(args=%R, kwargs=%R)",
|
|
this->_args.borrow(),
|
|
this->_kwargs.borrow()
|
|
)
|
|
).borrow()
|
|
);
|
|
}
|
|
};
|
|
|
|
class ThreadState;
|
|
|
|
class UserGreenlet;
|
|
class MainGreenlet;
|
|
|
|
class Greenlet
|
|
{
|
|
private:
|
|
G_NO_COPIES_OF_CLS(Greenlet);
|
|
private:
|
|
// XXX: Work to remove these.
|
|
friend class ThreadState;
|
|
friend class UserGreenlet;
|
|
friend class MainGreenlet;
|
|
protected:
|
|
ExceptionState exception_state;
|
|
SwitchingArgs switch_args;
|
|
StackState stack_state;
|
|
PythonState python_state;
|
|
Greenlet(PyGreenlet* p, const StackState& initial_state);
|
|
public:
|
|
Greenlet(PyGreenlet* p);
|
|
virtual ~Greenlet();
|
|
|
|
const OwnedObject context() const;
|
|
|
|
// You MUST call this _very_ early in the switching process to
|
|
// prepare anything that may need prepared. This might perform
|
|
// garbage collections or otherwise run arbitrary Python code.
|
|
//
|
|
// One specific use of it is for Python 3.11+, preventing
|
|
// running arbitrary code at unsafe times. See
|
|
// PythonState::may_switch_away().
|
|
inline void may_switch_away()
|
|
{
|
|
this->python_state.may_switch_away();
|
|
}
|
|
|
|
inline void context(refs::BorrowedObject new_context);
|
|
|
|
inline SwitchingArgs& args()
|
|
{
|
|
return this->switch_args;
|
|
}
|
|
|
|
virtual const refs::BorrowedMainGreenlet main_greenlet() const = 0;
|
|
|
|
inline intptr_t stack_saved() const noexcept
|
|
{
|
|
return this->stack_state.stack_saved();
|
|
}
|
|
|
|
// This is used by the macro SLP_SAVE_STATE to compute the
|
|
// difference in stack sizes. It might be nice to handle the
|
|
// computation ourself, but the type of the result
|
|
// varies by platform, so doing it in the macro is the
|
|
// simplest way.
|
|
inline const char* stack_start() const noexcept
|
|
{
|
|
return this->stack_state.stack_start();
|
|
}
|
|
|
|
virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state);
|
|
virtual OwnedObject g_switch() = 0;
|
|
/**
|
|
* Force the greenlet to appear dead. Used when it's not
|
|
* possible to throw an exception into a greenlet anymore.
|
|
*
|
|
* This losses access to the thread state and the main greenlet.
|
|
*/
|
|
virtual void murder_in_place();
|
|
|
|
/**
|
|
* Called when somebody notices we were running in a dead
|
|
* thread to allow cleaning up resources (because we can't
|
|
* raise GreenletExit into it anymore).
|
|
* This is very similar to ``murder_in_place()``, except that
|
|
* it DOES NOT lose the main greenlet or thread state.
|
|
*/
|
|
inline void deactivate_and_free();
|
|
|
|
|
|
// Called when some thread wants to deallocate a greenlet
|
|
// object.
|
|
// The thread may or may not be the same thread the greenlet
|
|
// was running in.
|
|
// The thread state will be null if the thread the greenlet
|
|
// was running in was known to have exited.
|
|
void deallocing_greenlet_in_thread(const ThreadState* current_state);
|
|
|
|
// TODO: Figure out how to make these non-public.
|
|
inline void slp_restore_state() noexcept;
|
|
inline int slp_save_state(char *const stackref) noexcept;
|
|
|
|
inline bool is_currently_running_in_some_thread() const;
|
|
virtual bool belongs_to_thread(const ThreadState* state) const;
|
|
|
|
inline bool started() const
|
|
{
|
|
return this->stack_state.started();
|
|
}
|
|
inline bool active() const
|
|
{
|
|
return this->stack_state.active();
|
|
}
|
|
inline bool main() const
|
|
{
|
|
return this->stack_state.main();
|
|
}
|
|
virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const = 0;
|
|
|
|
virtual const OwnedGreenlet parent() const = 0;
|
|
virtual void parent(const refs::BorrowedObject new_parent) = 0;
|
|
|
|
inline const PythonState::OwnedFrame& top_frame()
|
|
{
|
|
return this->python_state.top_frame();
|
|
}
|
|
|
|
virtual const OwnedObject& run() const = 0;
|
|
virtual void run(const refs::BorrowedObject nrun) = 0;
|
|
|
|
|
|
virtual int tp_traverse(visitproc visit, void* arg);
|
|
virtual int tp_clear();
|
|
|
|
|
|
// Return the thread state that the greenlet is running in, or
|
|
// null if the greenlet is not running or the thread is known
|
|
// to have exited.
|
|
virtual ThreadState* thread_state() const noexcept = 0;
|
|
|
|
// Return true if the greenlet is known to have been running
|
|
// (active) in a thread that has now exited.
|
|
virtual bool was_running_in_dead_thread() const noexcept = 0;
|
|
|
|
// Return a borrowed greenlet that is the Python object
|
|
// this object represents.
|
|
virtual BorrowedGreenlet self() const noexcept = 0;
|
|
|
|
// For testing. If this returns true, we should pretend that
|
|
// slp_switch() failed.
|
|
virtual bool force_slp_switch_error() const noexcept;
|
|
|
|
protected:
|
|
inline void release_args();
|
|
|
|
// The functions that must not be inlined are declared virtual.
|
|
// We also mark them as protected, not private, so that the
|
|
// compiler is forced to call them through a function pointer.
|
|
// (A sufficiently smart compiler could directly call a private
|
|
// virtual function since it can never be overridden in a
|
|
// subclass).
|
|
|
|
// Also TODO: Switch away from integer error codes and to enums,
|
|
// or throw exceptions when possible.
|
|
struct switchstack_result_t
|
|
{
|
|
int status;
|
|
Greenlet* the_new_current_greenlet;
|
|
OwnedGreenlet origin_greenlet;
|
|
|
|
switchstack_result_t()
|
|
: status(0),
|
|
the_new_current_greenlet(nullptr)
|
|
{}
|
|
|
|
switchstack_result_t(int err)
|
|
: status(err),
|
|
the_new_current_greenlet(nullptr)
|
|
{}
|
|
|
|
switchstack_result_t(int err, Greenlet* state, OwnedGreenlet& origin)
|
|
: status(err),
|
|
the_new_current_greenlet(state),
|
|
origin_greenlet(origin)
|
|
{
|
|
}
|
|
|
|
switchstack_result_t(int err, Greenlet* state, const BorrowedGreenlet& origin)
|
|
: status(err),
|
|
the_new_current_greenlet(state),
|
|
origin_greenlet(origin)
|
|
{
|
|
}
|
|
|
|
switchstack_result_t(const switchstack_result_t& other)
|
|
: status(other.status),
|
|
the_new_current_greenlet(other.the_new_current_greenlet),
|
|
origin_greenlet(other.origin_greenlet)
|
|
{}
|
|
|
|
switchstack_result_t& operator=(const switchstack_result_t& other)
|
|
{
|
|
this->status = other.status;
|
|
this->the_new_current_greenlet = other.the_new_current_greenlet;
|
|
this->origin_greenlet = other.origin_greenlet;
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
OwnedObject on_switchstack_or_initialstub_failure(
|
|
Greenlet* target,
|
|
const switchstack_result_t& err,
|
|
const bool target_was_me=false,
|
|
const bool was_initial_stub=false);
|
|
|
|
// Returns the previous greenlet we just switched away from.
|
|
virtual OwnedGreenlet g_switchstack_success() noexcept;
|
|
|
|
|
|
// Check the preconditions for switching to this greenlet; if they
|
|
// aren't met, throws PyErrOccurred. Most callers will want to
|
|
// catch this and clear the arguments
|
|
inline void check_switch_allowed() const;
|
|
class GreenletStartedWhileInPython : public std::runtime_error
|
|
{
|
|
public:
|
|
GreenletStartedWhileInPython() : std::runtime_error("")
|
|
{}
|
|
};
|
|
|
|
protected:
|
|
|
|
|
|
/**
|
|
Perform a stack switch into this greenlet.
|
|
|
|
This temporarily sets the global variable
|
|
``switching_thread_state`` to this greenlet; as soon as the
|
|
call to ``slp_switch`` completes, this is reset to NULL.
|
|
Consequently, this depends on the GIL.
|
|
|
|
TODO: Adopt the stackman model and pass ``slp_switch`` a
|
|
callback function and context pointer; this eliminates the
|
|
need for global variables altogether.
|
|
|
|
Because the stack switch happens in this function, this
|
|
function can't use its own stack (local) variables, set
|
|
before the switch, and then accessed after the switch.
|
|
|
|
Further, you con't even access ``g_thread_state_global``
|
|
before and after the switch from the global variable.
|
|
Because it is thread local some compilers cache it in a
|
|
register/on the stack, notably new versions of MSVC; this
|
|
breaks with strange crashes sometime later, because writing
|
|
to anything in ``g_thread_state_global`` after the switch
|
|
is actually writing to random memory. For this reason, we
|
|
call a non-inlined function to finish the operation. (XXX:
|
|
The ``/GT`` MSVC compiler argument probably fixes that.)
|
|
|
|
It is very important that stack switch is 'atomic', i.e. no
|
|
calls into other Python code allowed (except very few that
|
|
are safe), because global variables are very fragile. (This
|
|
should no longer be the case with thread-local variables.)
|
|
|
|
*/
|
|
// Made virtual to facilitate subclassing UserGreenlet for testing.
|
|
virtual switchstack_result_t g_switchstack(void);
|
|
|
|
class TracingGuard
|
|
{
|
|
private:
|
|
PyThreadState* tstate;
|
|
public:
|
|
TracingGuard()
|
|
: tstate(PyThreadState_GET())
|
|
{
|
|
PyThreadState_EnterTracing(this->tstate);
|
|
}
|
|
|
|
~TracingGuard()
|
|
{
|
|
PyThreadState_LeaveTracing(this->tstate);
|
|
this->tstate = nullptr;
|
|
}
|
|
|
|
inline void CallTraceFunction(const OwnedObject& tracefunc,
|
|
const greenlet::refs::ImmortalEventName& event,
|
|
const BorrowedGreenlet& origin,
|
|
const BorrowedGreenlet& target)
|
|
{
|
|
// TODO: This calls tracefunc(event, (origin, target)). Add a shortcut
|
|
// function for that that's specialized to avoid the Py_BuildValue
|
|
// string parsing, or start with just using "ON" format with PyTuple_Pack(2,
|
|
// origin, target). That seems like what the N format is meant
|
|
// for.
|
|
// XXX: Why does event not automatically cast back to a PyObject?
|
|
// It tries to call the "deleted constructor ImmortalEventName
|
|
// const" instead.
|
|
assert(tracefunc);
|
|
assert(event);
|
|
assert(origin);
|
|
assert(target);
|
|
greenlet::refs::NewReference retval(
|
|
PyObject_CallFunction(
|
|
tracefunc.borrow(),
|
|
"O(OO)",
|
|
event.borrow(),
|
|
origin.borrow(),
|
|
target.borrow()
|
|
));
|
|
if (!retval) {
|
|
throw PyErrOccurred::from_current();
|
|
}
|
|
}
|
|
};
|
|
|
|
static void
|
|
g_calltrace(const OwnedObject& tracefunc,
|
|
const greenlet::refs::ImmortalEventName& event,
|
|
const greenlet::refs::BorrowedGreenlet& origin,
|
|
const BorrowedGreenlet& target);
|
|
private:
|
|
OwnedObject g_switch_finish(const switchstack_result_t& err);
|
|
|
|
};
|
|
|
|
class UserGreenlet : public Greenlet
|
|
{
|
|
private:
|
|
static greenlet::PythonAllocator<UserGreenlet> allocator;
|
|
BorrowedGreenlet _self;
|
|
OwnedMainGreenlet _main_greenlet;
|
|
OwnedObject _run_callable;
|
|
OwnedGreenlet _parent;
|
|
public:
|
|
static void* operator new(size_t UNUSED(count));
|
|
static void operator delete(void* ptr);
|
|
|
|
UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent);
|
|
virtual ~UserGreenlet();
|
|
|
|
virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const;
|
|
virtual bool was_running_in_dead_thread() const noexcept;
|
|
virtual ThreadState* thread_state() const noexcept;
|
|
virtual OwnedObject g_switch();
|
|
virtual const OwnedObject& run() const
|
|
{
|
|
if (this->started() || !this->_run_callable) {
|
|
throw AttributeError("run");
|
|
}
|
|
return this->_run_callable;
|
|
}
|
|
virtual void run(const refs::BorrowedObject nrun);
|
|
|
|
virtual const OwnedGreenlet parent() const;
|
|
virtual void parent(const refs::BorrowedObject new_parent);
|
|
|
|
virtual const refs::BorrowedMainGreenlet main_greenlet() const;
|
|
|
|
virtual BorrowedGreenlet self() const noexcept;
|
|
virtual void murder_in_place();
|
|
virtual bool belongs_to_thread(const ThreadState* state) const;
|
|
virtual int tp_traverse(visitproc visit, void* arg);
|
|
virtual int tp_clear();
|
|
class ParentIsCurrentGuard
|
|
{
|
|
private:
|
|
OwnedGreenlet oldparent;
|
|
UserGreenlet* greenlet;
|
|
G_NO_COPIES_OF_CLS(ParentIsCurrentGuard);
|
|
public:
|
|
ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state);
|
|
~ParentIsCurrentGuard();
|
|
};
|
|
virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state);
|
|
protected:
|
|
virtual switchstack_result_t g_initialstub(void* mark);
|
|
private:
|
|
// This function isn't meant to return.
|
|
// This accepts raw pointers and the ownership of them at the
|
|
// same time. The caller should use ``inner_bootstrap(origin.relinquish_ownership())``.
|
|
void inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run);
|
|
};
|
|
|
|
class BrokenGreenlet : public UserGreenlet
|
|
{
|
|
private:
|
|
static greenlet::PythonAllocator<BrokenGreenlet> allocator;
|
|
public:
|
|
bool _force_switch_error = false;
|
|
bool _force_slp_switch_error = false;
|
|
|
|
static void* operator new(size_t UNUSED(count));
|
|
static void operator delete(void* ptr);
|
|
BrokenGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent)
|
|
: UserGreenlet(p, the_parent)
|
|
{}
|
|
virtual ~BrokenGreenlet()
|
|
{}
|
|
|
|
virtual switchstack_result_t g_switchstack(void);
|
|
virtual bool force_slp_switch_error() const noexcept;
|
|
|
|
};
|
|
|
|
class MainGreenlet : public Greenlet
|
|
{
|
|
private:
|
|
static greenlet::PythonAllocator<MainGreenlet> allocator;
|
|
refs::BorrowedMainGreenlet _self;
|
|
ThreadState* _thread_state;
|
|
G_NO_COPIES_OF_CLS(MainGreenlet);
|
|
public:
|
|
static void* operator new(size_t UNUSED(count));
|
|
static void operator delete(void* ptr);
|
|
|
|
MainGreenlet(refs::BorrowedMainGreenlet::PyType*, ThreadState*);
|
|
virtual ~MainGreenlet();
|
|
|
|
|
|
virtual const OwnedObject& run() const;
|
|
virtual void run(const refs::BorrowedObject nrun);
|
|
|
|
virtual const OwnedGreenlet parent() const;
|
|
virtual void parent(const refs::BorrowedObject new_parent);
|
|
|
|
virtual const refs::BorrowedMainGreenlet main_greenlet() const;
|
|
|
|
virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const;
|
|
virtual bool was_running_in_dead_thread() const noexcept;
|
|
virtual ThreadState* thread_state() const noexcept;
|
|
void thread_state(ThreadState*) noexcept;
|
|
virtual OwnedObject g_switch();
|
|
virtual BorrowedGreenlet self() const noexcept;
|
|
virtual int tp_traverse(visitproc visit, void* arg);
|
|
};
|
|
|
|
// Instantiate one on the stack to save the GC state,
|
|
// and then disable GC. When it goes out of scope, GC will be
|
|
// restored to its original state. Sadly, these APIs are only
|
|
// available on 3.10+; luckily, we only need them on 3.11+.
|
|
#if GREENLET_PY310
|
|
class GCDisabledGuard
|
|
{
|
|
private:
|
|
int was_enabled = 0;
|
|
public:
|
|
GCDisabledGuard()
|
|
: was_enabled(PyGC_IsEnabled())
|
|
{
|
|
PyGC_Disable();
|
|
}
|
|
|
|
~GCDisabledGuard()
|
|
{
|
|
if (this->was_enabled) {
|
|
PyGC_Enable();
|
|
}
|
|
}
|
|
};
|
|
#endif
|
|
|
|
OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept;
|
|
|
|
//TODO: Greenlet::g_switch() should call this automatically on its
|
|
//return value. As it is, the module code is calling it.
|
|
static inline OwnedObject
|
|
single_result(const OwnedObject& results)
|
|
{
|
|
if (results
|
|
&& PyTuple_Check(results.borrow())
|
|
&& PyTuple_GET_SIZE(results.borrow()) == 1) {
|
|
PyObject* result = PyTuple_GET_ITEM(results.borrow(), 0);
|
|
assert(result);
|
|
return OwnedObject::owning(result);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
|
|
static OwnedObject
|
|
g_handle_exit(const OwnedObject& greenlet_result);
|
|
|
|
|
|
template<typename T>
|
|
void operator<<(const PyThreadState *const lhs, T& rhs)
|
|
{
|
|
rhs.operator<<(lhs);
|
|
}
|
|
|
|
} // namespace greenlet ;
|
|
|
|
#endif
|