-
Notifications
You must be signed in to change notification settings - Fork 248
/
PyGreenlet.cpp
738 lines (648 loc) · 22.9 KB
/
PyGreenlet.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
#ifndef PYGREENLET_CPP
#define PYGREENLET_CPP
/*****************
The Python slot functions for TGreenlet.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h" // PyMemberDef
#include "greenlet_internal.hpp"
#include "TThreadStateDestroy.cpp"
#include "TGreenlet.hpp"
// #include "TUserGreenlet.cpp"
// #include "TMainGreenlet.cpp"
// #include "TBrokenGreenlet.cpp"
#include "greenlet_refs.hpp"
#include "greenlet_slp_switch.hpp"
#include "greenlet_thread_support.hpp"
#include "TGreenlet.hpp"
#include "TGreenletGlobals.cpp"
#include "TThreadStateDestroy.cpp"
#include "PyGreenlet.hpp"
// #include "TGreenlet.cpp"
// #include "TExceptionState.cpp"
// #include "TPythonState.cpp"
// #include "TStackState.cpp"
using greenlet::LockGuard;
using greenlet::LockInitError;
using greenlet::PyErrOccurred;
using greenlet::Require;
using greenlet::g_handle_exit;
using greenlet::single_result;
using greenlet::Greenlet;
using greenlet::UserGreenlet;
using greenlet::MainGreenlet;
using greenlet::BrokenGreenlet;
using greenlet::ThreadState;
using greenlet::PythonState;
static PyGreenlet*
green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds))
{
PyGreenlet* o =
(PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict);
if (o) {
new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current());
assert(Py_REFCNT(o) == 1);
}
return o;
}
// green_init is used in the tp_init slot. So it's important that
// it can be called directly from CPython. Thus, we don't use
// BorrowedGreenlet and BorrowedObject --- although in theory
// these should be binary layout compatible, that may not be
// guaranteed to be the case (32-bit linux ppc possibly).
static int
green_init(PyGreenlet* self, PyObject* args, PyObject* kwargs)
{
PyArgParseParam run;
PyArgParseParam nparent;
static const char* kwlist[] = {
"run",
"parent",
NULL
};
// recall: The O specifier does NOT increase the reference count.
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "|OO:green", (char**)kwlist, &run, &nparent)) {
return -1;
}
if (run) {
if (green_setrun(self, run, NULL)) {
return -1;
}
}
if (nparent && !nparent.is_None()) {
return green_setparent(self, nparent, NULL);
}
return 0;
}
static int
green_traverse(PyGreenlet* self, visitproc visit, void* arg)
{
// We must only visit referenced objects, i.e. only objects
// Py_INCREF'ed by this greenlet (directly or indirectly):
//
// - stack_prev is not visited: holds previous stack pointer, but it's not
// referenced
// - frames are not visited as we don't strongly reference them;
// alive greenlets are not garbage collected
// anyway. This can be a problem, however, if this greenlet is
// never allowed to finish, and is referenced from the frame: we
// have an uncollectible cycle in that case. Note that the
// frame object itself is also frequently not even tracked by the GC
// starting with Python 3.7 (frames are allocated by the
// interpreter untracked, and only become tracked when their
// evaluation is finished if they have a refcount > 1). All of
// this is to say that we should probably strongly reference
// the frame object. Doing so, while always allowing GC on a
// greenlet, solves several leaks for us.
Py_VISIT(self->dict);
if (!self->pimpl) {
// Hmm. I have seen this at interpreter shutdown time,
// I think. That's very odd because this doesn't go away until
// we're ``green_dealloc()``, at which point we shouldn't be
// traversed anymore.
return 0;
}
return self->pimpl->tp_traverse(visit, arg);
}
static int
green_is_gc(PyObject* _self)
{
BorrowedGreenlet self(_self);
int result = 0;
/* Main greenlet can be garbage collected since it can only
become unreachable if the underlying thread exited.
Active greenlets --- including those that are suspended ---
cannot be garbage collected, however.
*/
if (self->main() || !self->active()) {
result = 1;
}
// The main greenlet pointer will eventually go away after the thread dies.
if (self->was_running_in_dead_thread()) {
// Our thread is dead! We can never run again. Might as well
// GC us. Note that if a tuple containing only us and other
// immutable objects had been scanned before this, when we
// would have returned 0, the tuple will take itself out of GC
// tracking and never be investigated again. So that could
// result in both us and the tuple leaking due to an
// unreachable/uncollectible reference. The same goes for
// dictionaries.
//
// It's not a great idea to be changing our GC state on the
// fly.
result = 1;
}
return result;
}
static int
green_clear(PyGreenlet* self)
{
/* Greenlet is only cleared if it is about to be collected.
Since active greenlets are not garbage collectable, we can
be sure that, even if they are deallocated during clear,
nothing they reference is in unreachable or finalizers,
so even if it switches we are relatively safe. */
// XXX: Are we responsible for clearing weakrefs here?
Py_CLEAR(self->dict);
return self->pimpl->tp_clear();
}
/**
* Returns 0 on failure (the object was resurrected) or 1 on success.
**/
static int
_green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self)
{
/* Hacks hacks hacks copied from instance_dealloc() */
/* Temporarily resurrect the greenlet. */
assert(self.REFCNT() == 0);
Py_SET_REFCNT(self.borrow(), 1);
/* Save the current exception, if any. */
PyErrPieces saved_err;
try {
// BY THE TIME WE GET HERE, the state may actually be going
// away
// if we're shutting down the interpreter and freeing thread
// entries,
// this could result in freeing greenlets that were leaked. So
// we can't try to read the state.
self->deallocing_greenlet_in_thread(
self->thread_state()
? static_cast<ThreadState*>(GET_THREAD_STATE())
: nullptr);
}
catch (const PyErrOccurred&) {
PyErr_WriteUnraisable(self.borrow_o());
/* XXX what else should we do? */
}
/* Check for no resurrection must be done while we keep
* our internal reference, otherwise PyFile_WriteObject
* causes recursion if using Py_INCREF/Py_DECREF
*/
if (self.REFCNT() == 1 && self->active()) {
/* Not resurrected, but still not dead!
XXX what else should we do? we complain. */
PyObject* f = PySys_GetObject("stderr");
Py_INCREF(self.borrow_o()); /* leak! */
if (f != NULL) {
PyFile_WriteString("GreenletExit did not kill ", f);
PyFile_WriteObject(self.borrow_o(), f, 0);
PyFile_WriteString("\n", f);
}
}
/* Restore the saved exception. */
saved_err.PyErrRestore();
/* Undo the temporary resurrection; can't use DECREF here,
* it would cause a recursive call.
*/
assert(self.REFCNT() > 0);
Py_ssize_t refcnt = self.REFCNT() - 1;
Py_SET_REFCNT(self.borrow_o(), refcnt);
if (refcnt != 0) {
/* Resurrected! */
_Py_NewReference(self.borrow_o());
Py_SET_REFCNT(self.borrow_o(), refcnt);
/* Better to use tp_finalizer slot (PEP 442)
* and call ``PyObject_CallFinalizerFromDealloc``,
* but that's only supported in Python 3.4+; see
* Modules/_io/iobase.c for an example.
*
* The following approach is copied from iobase.c in CPython 2.7.
* (along with much of this function in general). Here's their
* comment:
*
* When called from a heap type's dealloc, the type will be
* decref'ed on return (see e.g. subtype_dealloc in typeobject.c). */
if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) {
Py_INCREF(self.TYPE());
}
PyObject_GC_Track((PyObject*)self);
_Py_DEC_REFTOTAL;
#ifdef COUNT_ALLOCS
--Py_TYPE(self)->tp_frees;
--Py_TYPE(self)->tp_allocs;
#endif /* COUNT_ALLOCS */
return 0;
}
return 1;
}
static void
green_dealloc(PyGreenlet* self)
{
PyObject_GC_UnTrack(self);
BorrowedGreenlet me(self);
if (me->active()
&& me->started()
&& !me->main()) {
if (!_green_dealloc_kill_started_non_main_greenlet(me)) {
return;
}
}
if (self->weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject*)self);
}
Py_CLEAR(self->dict);
if (self->pimpl) {
// In case deleting this, which frees some memory,
// somehow winds up calling back into us. That's usually a
//bug in our code.
Greenlet* p = self->pimpl;
self->pimpl = nullptr;
delete p;
}
// and finally we're done. self is now invalid.
Py_TYPE(self)->tp_free((PyObject*)self);
}
static OwnedObject
internal_green_throw(BorrowedGreenlet self, PyErrPieces& err_pieces)
{
PyObject* result = nullptr;
err_pieces.PyErrRestore();
assert(PyErr_Occurred());
if (self->started() && !self->active()) {
/* dead greenlet: turn GreenletExit into a regular return */
result = g_handle_exit(OwnedObject()).relinquish_ownership();
}
self->args() <<= result;
return single_result(self->g_switch());
}
PyDoc_STRVAR(
green_switch_doc,
"switch(*args, **kwargs)\n"
"\n"
"Switch execution to this greenlet.\n"
"\n"
"If this greenlet has never been run, then this greenlet\n"
"will be switched to using the body of ``self.run(*args, **kwargs)``.\n"
"\n"
"If the greenlet is active (has been run, but was switch()'ed\n"
"out before leaving its run function), then this greenlet will\n"
"be resumed and the return value to its switch call will be\n"
"None if no arguments are given, the given argument if one\n"
"argument is given, or the args tuple and keyword args dict if\n"
"multiple arguments are given.\n"
"\n"
"If the greenlet is dead, or is the current greenlet then this\n"
"function will simply return the arguments using the same rules as\n"
"above.\n");
static PyObject*
green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs)
{
using greenlet::SwitchingArgs;
SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs));
self->pimpl->may_switch_away();
self->pimpl->args() <<= switch_args;
// If we're switching out of a greenlet, and that switch is the
// last thing the greenlet does, the greenlet ought to be able to
// go ahead and die at that point. Currently, someone else must
// manually switch back to the greenlet so that we "fall off the
// end" and can perform cleanup. You'd think we'd be able to
// figure out that this is happening using the frame's ``f_lasti``
// member, which is supposed to be an index into
// ``frame->f_code->co_code``, the bytecode string. However, in
// recent interpreters, ``f_lasti`` tends not to be updated thanks
// to things like the PREDICT() macros in ceval.c. So it doesn't
// really work to do that in many cases. For example, the Python
// code:
// def run():
// greenlet.getcurrent().parent.switch()
// produces bytecode of len 16, with the actual call to switch()
// being at index 10 (in Python 3.10). However, the reported
// ``f_lasti`` we actually see is...5! (Which happens to be the
// second byte of the CALL_METHOD op for ``getcurrent()``).
try {
//OwnedObject result = single_result(self->pimpl->g_switch());
OwnedObject result(single_result(self->pimpl->g_switch()));
#ifndef NDEBUG
// Note that the current greenlet isn't necessarily self. If self
// finished, we went to one of its parents.
assert(!self->pimpl->args());
const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current();
// It's possible it's never been switched to.
assert(!current->args());
#endif
PyObject* p = result.relinquish_ownership();
if (!p && !PyErr_Occurred()) {
// This shouldn't be happening anymore, so the asserts
// are there for debug builds. Non-debug builds
// crash "gracefully" in this case, although there is an
// argument to be made for killing the process in all
// cases --- for this to be the case, our switches
// probably nested in an incorrect way, so the state is
// suspicious. Nothing should be corrupt though, just
// confused at the Python level. Letting this propagate is
// probably good enough.
assert(p || PyErr_Occurred());
throw PyErrOccurred(
mod_globs->PyExc_GreenletError,
"Greenlet.switch() returned NULL without an exception set."
);
}
return p;
}
catch(const PyErrOccurred&) {
return nullptr;
}
}
PyDoc_STRVAR(
green_throw_doc,
"Switches execution to this greenlet, but immediately raises the\n"
"given exception in this greenlet. If no argument is provided, the "
"exception\n"
"defaults to `greenlet.GreenletExit`. The normal exception\n"
"propagation rules apply, as described for `switch`. Note that calling "
"this\n"
"method is almost equivalent to the following::\n"
"\n"
" def raiser():\n"
" raise typ, val, tb\n"
" g_raiser = greenlet(raiser, parent=g)\n"
" g_raiser.switch()\n"
"\n"
"except that this trick does not work for the\n"
"`greenlet.GreenletExit` exception, which would not propagate\n"
"from ``g_raiser`` to ``g``.\n");
static PyObject*
green_throw(PyGreenlet* self, PyObject* args)
{
PyArgParseParam typ(mod_globs->PyExc_GreenletExit);
PyArgParseParam val;
PyArgParseParam tb;
if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) {
return nullptr;
}
assert(typ.borrow() || val.borrow());
self->pimpl->may_switch_away();
try {
// Both normalizing the error and the actual throw_greenlet
// could throw PyErrOccurred.
PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow());
return internal_green_throw(self, err_pieces).relinquish_ownership();
}
catch (const PyErrOccurred&) {
return nullptr;
}
}
static int
green_bool(PyGreenlet* self)
{
return self->pimpl->active();
}
/**
* CAUTION: Allocates memory, may run GC and arbitrary Python code.
*/
static PyObject*
green_getdict(PyGreenlet* self, void* UNUSED(context))
{
if (self->dict == NULL) {
self->dict = PyDict_New();
if (self->dict == NULL) {
return NULL;
}
}
Py_INCREF(self->dict);
return self->dict;
}
static int
green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context))
{
PyObject* tmp;
if (val == NULL) {
PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted");
return -1;
}
if (!PyDict_Check(val)) {
PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary");
return -1;
}
tmp = self->dict;
Py_INCREF(val);
self->dict = val;
Py_XDECREF(tmp);
return 0;
}
static bool
_green_not_dead(BorrowedGreenlet self)
{
// XXX: Where else should we do this?
// Probably on entry to most Python-facing functions?
if (self->was_running_in_dead_thread()) {
self->deactivate_and_free();
return false;
}
return self->active() || !self->started();
}
static PyObject*
green_getdead(PyGreenlet* self, void* UNUSED(context))
{
if (_green_not_dead(self)) {
Py_RETURN_FALSE;
}
else {
Py_RETURN_TRUE;
}
}
static PyObject*
green_get_stack_saved(PyGreenlet* self, void* UNUSED(context))
{
return PyLong_FromSsize_t(self->pimpl->stack_saved());
}
static PyObject*
green_getrun(PyGreenlet* self, void* UNUSED(context))
{
try {
OwnedObject result(BorrowedGreenlet(self)->run());
return result.relinquish_ownership();
}
catch(const PyErrOccurred&) {
return nullptr;
}
}
static int
green_setrun(PyGreenlet* self, PyObject* nrun, void* UNUSED(context))
{
try {
BorrowedGreenlet(self)->run(nrun);
return 0;
}
catch(const PyErrOccurred&) {
return -1;
}
}
static PyObject*
green_getparent(PyGreenlet* self, void* UNUSED(context))
{
return BorrowedGreenlet(self)->parent().acquire_or_None();
}
static int
green_setparent(PyGreenlet* self, PyObject* nparent, void* UNUSED(context))
{
try {
BorrowedGreenlet(self)->parent(nparent);
}
catch(const PyErrOccurred&) {
return -1;
}
return 0;
}
static PyObject*
green_getcontext(const PyGreenlet* self, void* UNUSED(context))
{
const Greenlet *const g = self->pimpl;
try {
OwnedObject result(g->context());
return result.relinquish_ownership();
}
catch(const PyErrOccurred&) {
return nullptr;
}
}
static int
green_setcontext(PyGreenlet* self, PyObject* nctx, void* UNUSED(context))
{
try {
BorrowedGreenlet(self)->context(nctx);
return 0;
}
catch(const PyErrOccurred&) {
return -1;
}
}
static PyObject*
green_getframe(PyGreenlet* self, void* UNUSED(context))
{
const PythonState::OwnedFrame& top_frame = BorrowedGreenlet(self)->top_frame();
return top_frame.acquire_or_None();
}
static PyObject*
green_getstate(PyGreenlet* self)
{
PyErr_Format(PyExc_TypeError,
"cannot serialize '%s' object",
Py_TYPE(self)->tp_name);
return nullptr;
}
static PyObject*
green_repr(PyGreenlet* _self)
{
BorrowedGreenlet self(_self);
/*
Return a string like
<greenlet.greenlet at 0xdeadbeef [current][active started]|dead main>
The handling of greenlets across threads is not super good.
We mostly use the internal definitions of these terms, but they
generally should make sense to users as well.
*/
PyObject* result;
int never_started = !self->started() && !self->active();
const char* const tp_name = Py_TYPE(self)->tp_name;
if (_green_not_dead(self)) {
/* XXX: The otid= is almost useless because you can't correlate it to
any thread identifier exposed to Python. We could use
PyThreadState_GET()->thread_id, but we'd need to save that in the
greenlet, or save the whole PyThreadState object itself.
As it stands, its only useful for identifying greenlets from the same thread.
*/
const char* state_in_thread;
if (self->was_running_in_dead_thread()) {
// The thread it was running in is dead!
// This can happen, especially at interpreter shut down.
// It complicates debugging output because it may be
// impossible to access the current thread state at that
// time. Thus, don't access the current thread state.
state_in_thread = " (thread exited)";
}
else {
state_in_thread = GET_THREAD_STATE().state().is_current(self)
? " current"
: (self->started() ? " suspended" : "");
}
result = PyUnicode_FromFormat(
"<%s object at %p (otid=%p)%s%s%s%s>",
tp_name,
self.borrow_o(),
self->thread_state(),
state_in_thread,
self->active() ? " active" : "",
never_started ? " pending" : " started",
self->main() ? " main" : ""
);
}
else {
result = PyUnicode_FromFormat(
"<%s object at %p (otid=%p) %sdead>",
tp_name,
self.borrow_o(),
self->thread_state(),
self->was_running_in_dead_thread()
? "(thread exited) "
: ""
);
}
return result;
}
static PyMethodDef green_methods[] = {
{
.ml_name="switch",
.ml_meth=reinterpret_cast<PyCFunction>(green_switch),
.ml_flags=METH_VARARGS | METH_KEYWORDS,
.ml_doc=green_switch_doc
},
{.ml_name="throw", .ml_meth=(PyCFunction)green_throw, .ml_flags=METH_VARARGS, .ml_doc=green_throw_doc},
{.ml_name="__getstate__", .ml_meth=(PyCFunction)green_getstate, .ml_flags=METH_NOARGS, .ml_doc=NULL},
{.ml_name=NULL, .ml_meth=NULL} /* sentinel */
};
static PyGetSetDef green_getsets[] = {
/* name, getter, setter, doc, context pointer */
{.name="__dict__", .get=(getter)green_getdict, .set=(setter)green_setdict},
{.name="run", .get=(getter)green_getrun, .set=(setter)green_setrun},
{.name="parent", .get=(getter)green_getparent, .set=(setter)green_setparent},
{.name="gr_frame", .get=(getter)green_getframe },
{
.name="gr_context",
.get=(getter)green_getcontext,
.set=(setter)green_setcontext
},
{.name="dead", .get=(getter)green_getdead},
{.name="_stack_saved", .get=(getter)green_get_stack_saved},
{.name=NULL}
};
static PyMemberDef green_members[] = {
{.name=NULL}
};
static PyNumberMethods green_as_number = {
.nb_bool=(inquiry)green_bool,
};
PyTypeObject PyGreenlet_Type = {
.ob_base=PyVarObject_HEAD_INIT(NULL, 0)
.tp_name="greenlet.greenlet", /* tp_name */
.tp_basicsize=sizeof(PyGreenlet), /* tp_basicsize */
/* methods */
.tp_dealloc=(destructor)green_dealloc, /* tp_dealloc */
.tp_repr=(reprfunc)green_repr, /* tp_repr */
.tp_as_number=&green_as_number, /* tp_as _number*/
.tp_flags=G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
.tp_doc="greenlet(run=None, parent=None) -> greenlet\n\n"
"Creates a new greenlet object (without running it).\n\n"
" - *run* -- The callable to invoke.\n"
" - *parent* -- The parent greenlet. The default is the current "
"greenlet.", /* tp_doc */
.tp_traverse=(traverseproc)green_traverse, /* tp_traverse */
.tp_clear=(inquiry)green_clear, /* tp_clear */
.tp_weaklistoffset=offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */
.tp_methods=green_methods, /* tp_methods */
.tp_members=green_members, /* tp_members */
.tp_getset=green_getsets, /* tp_getset */
.tp_dictoffset=offsetof(PyGreenlet, dict), /* tp_dictoffset */
.tp_init=(initproc)green_init, /* tp_init */
.tp_alloc=PyType_GenericAlloc, /* tp_alloc */
.tp_new=(newfunc)green_new, /* tp_new */
.tp_free=PyObject_GC_Del, /* tp_free */
.tp_is_gc=(inquiry)green_is_gc, /* tp_is_gc */
};
#endif
// Local Variables:
// flycheck-clang-include-path: ("/opt/local/Library/Frameworks/Python.framework/Versions/3.8/include/python3.8")
// End: