asynkit 0.16.2__tar.gz → 0.16.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {asynkit-0.16.2/src/asynkit.egg-info → asynkit-0.16.4}/PKG-INFO +1 -1
  2. {asynkit-0.16.2 → asynkit-0.16.4}/pyproject.toml +1 -1
  3. {asynkit-0.16.2 → asynkit-0.16.4}/setup.py +2 -2
  4. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/_cext/corostart.c +206 -125
  5. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/coroutine.py +1 -0
  6. {asynkit-0.16.2 → asynkit-0.16.4/src/asynkit.egg-info}/PKG-INFO +1 -1
  7. {asynkit-0.16.2 → asynkit-0.16.4}/LICENSE +0 -0
  8. {asynkit-0.16.2 → asynkit-0.16.4}/README.md +0 -0
  9. {asynkit-0.16.2 → asynkit-0.16.4}/setup.cfg +0 -0
  10. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/__init__.py +0 -0
  11. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/compat.py +0 -0
  12. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/experimental/__init__.py +0 -0
  13. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/experimental/anyio.py +0 -0
  14. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/experimental/interrupt.py +0 -0
  15. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/experimental/priority.py +0 -0
  16. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/loop/__init__.py +0 -0
  17. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/loop/default.py +0 -0
  18. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/loop/eventloop.py +0 -0
  19. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/loop/extensions.py +0 -0
  20. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/loop/schedulingloop.py +0 -0
  21. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/loop/types.py +0 -0
  22. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/monitor.py +0 -0
  23. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/py.typed +0 -0
  24. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/scheduling.py +0 -0
  25. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit/tools.py +0 -0
  26. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit.egg-info/SOURCES.txt +0 -0
  27. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit.egg-info/dependency_links.txt +0 -0
  28. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit.egg-info/not-zip-safe +0 -0
  29. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit.egg-info/requires.txt +0 -0
  30. {asynkit-0.16.2 → asynkit-0.16.4}/src/asynkit.egg-info/top_level.txt +0 -0
  31. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_anyio.py +0 -0
  32. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_compat_eager.py +0 -0
  33. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_coro.py +0 -0
  34. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_df.py +0 -0
  35. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_eager_task_factory.py +0 -0
  36. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_eager_task_factory_context.py +0 -0
  37. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_implementation_params.py +0 -0
  38. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_loop.py +0 -0
  39. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_monitor.py +0 -0
  40. {asynkit-0.16.2 → asynkit-0.16.4}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asynkit
3
- Version: 0.16.2
3
+ Version: 0.16.4
4
4
  Summary: A toolkit for Python coroutines
5
5
  Author-email: Kristján Valur Jónsson <sweskman@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "asynkit"
3
- version = "0.16.2"
3
+ version = "0.16.4"
4
4
  description = "A toolkit for Python coroutines"
5
5
  authors = [
6
6
  {name = "Kristján Valur Jónsson", email = "sweskman@gmail.com"}
@@ -81,7 +81,7 @@ def get_compile_args():
81
81
  [
82
82
  "/Zi", # Debug symbols
83
83
  "/Od", # No optimization
84
- "/DDEBUG", # Debug macro
84
+ "/UNDEBUG", # Undefine NDEBUG to enable asserts
85
85
  "/W3", # Warning level 3
86
86
  ]
87
87
  )
@@ -90,7 +90,7 @@ def get_compile_args():
90
90
  [
91
91
  "-g", # Debug symbols
92
92
  "-O0", # No optimization
93
- "-DDEBUG", # Debug macro
93
+ "-UNDEBUG", # Undefine NDEBUG to enable asserts
94
94
  "-Wall", # All warnings
95
95
  "-Wextra", # Extra warnings
96
96
  ]
@@ -1,11 +1,16 @@
1
1
  /*
2
- * asynkit C Extension - CoroStart with CoroStartWrapper
2
+ * asynkit C Extension - CoroStartBase with CoroStartWrapper
3
3
  *
4
- * Phase 3: Create CoroStartWrapper that implements both iterator and coroutine
5
- * protocols following the PyCoroWrapper pattern discovered in CPython source
6
- *
7
- * Updated: 2025-10-29 - Updated to continued() and pending() API methods (renamed from
8
- * consumed/suspended)
4
+ * This module implements a C version of CoroStartBase from asynkit.
5
+ * The purpose of this is to optimize the execution of the coroutine protocol.
6
+ * In the Python code, this is done with a complicated generator function which
7
+ * implements the protocol in python to act as an intermediate between the event
8
+ * loop and the user coroutine. This function is necessarily entered twice for
9
+ * each yield point of the coroutine.
10
+ * In C it is possible to do this much more efficiently by directly implementing
11
+ * the PyIter_Send() protocol.
12
+ * This virtually eliminates the runtime overhead of driving a coroutine through
13
+ * the CoroStart mechanism.
9
14
  */
10
15
 
11
16
  #define PY_SSIZE_T_CLEAN
@@ -48,6 +53,7 @@ static PyObject *corostart_close(PyObject *_self);
48
53
 
49
54
  /* Helper function forward declarations */
50
55
  static PyObject *invalid_state_error(void);
56
+ static PyObject *extract_stopiteration_value(void);
51
57
 
52
58
  /* Module initialization function */
53
59
  PyMODINIT_FUNC PyInit__cext(void);
@@ -78,7 +84,7 @@ typedef struct CoroStartObject {
78
84
  /* Type-specific fields go here */
79
85
  PyObject *wrapped_coro;
80
86
  PyObject *context;
81
- PySendResult initial_result; // Result from initial PyIter_Send call */
87
+ PySendResult initial_result; // Result from initial PyIter_Send call
82
88
  PyObject *s_value; // completed value (if not exception)
83
89
  PyObject *s_exc_type; // exception type (if completed with exception)
84
90
  PyObject *s_exc_value; // exception value
@@ -91,17 +97,38 @@ static int corostart_traverse(CoroStartObject *self, visitproc visit, void *arg)
91
97
  static int corostart_clear(CoroStartObject *self);
92
98
  static PyObject *corostart_await(CoroStartObject *self);
93
99
 
94
- /* State checking macros for CoroStart objects */
95
- #define IS_DONE(self) ((self)->initial_result != PYGEN_NEXT)
96
- #define IS_CONTINUED(self) ((self)->s_value == NULL && (self)->s_exc_type == NULL)
97
- #define IS_PENDING(self) (!IS_DONE(self) && !IS_CONTINUED(self))
100
+ /* State checking macros for CoroStart objects
101
+ * DONE guarantees that s_value or s_exc_type is set
102
+ * CONTINUED means all fields are NULL (exception or value has
103
+ * been consumed by the PyIter_Send)
104
+ * PENDING is derived. it is not Done, and not yet consumed.
105
+ * these are the three valid states, further validation is done
106
+ * in the invariant checker.
107
+ */
108
+
109
+ #define _IS_DONE(self) ((self)->initial_result != PYGEN_NEXT)
110
+ #define _IS_CONTINUED(self) ((self)->s_value == NULL && (self)->s_exc_type == NULL)
111
+ #define _IS_PENDING(self) (!_IS_DONE(self) && !_IS_CONTINUED(self))
112
+
113
+ #define IS_DONE(self) \
114
+ (assert_corostart_invariant_impl((self), __LINE__), _IS_DONE(self))
115
+ #define IS_CONTINUED(self) \
116
+ (assert_corostart_invariant_impl((self), __LINE__), _IS_CONTINUED(self))
117
+ #define IS_PENDING(self) \
118
+ (assert_corostart_invariant_impl((self), __LINE__), _IS_PENDING(self))
119
+
120
+ /* Macro to call invariant checker with current line number */
121
+ #define ASSERT_COROSTART_INVARIANT(self) \
122
+ assert_corostart_invariant_impl((self), __LINE__)
98
123
 
99
124
  /* State invariant checker for debugging and validation */
100
- static inline void assert_corostart_invariant(CoroStartObject *self)
125
+ static inline void assert_corostart_invariant_impl(CoroStartObject *self,
126
+ int line_number)
101
127
  {
102
128
  #ifdef NDEBUG
103
129
  /* Skip checks in release builds */
104
130
  (void) self;
131
+ (void) line_number;
105
132
  #else
106
133
  /* Validate state model invariants:
107
134
  * 1. Exactly one of three states: done, pending, or continued
@@ -109,26 +136,52 @@ static inline void assert_corostart_invariant(CoroStartObject *self)
109
136
  * 3. pending: s_value != NULL (and s_exc_type == NULL)
110
137
  * 4. continued: all fields are NULL
111
138
  */
112
- int is_done = IS_DONE(self);
113
- int is_pending = IS_PENDING(self);
114
- int is_continued = IS_CONTINUED(self);
139
+ int is_done = _IS_DONE(self);
140
+ int is_pending = _IS_PENDING(self);
141
+ int is_continued = _IS_CONTINUED(self);
115
142
 
116
143
  /* Exactly one state should be true */
117
144
  int state_count = is_done + is_pending + is_continued;
145
+ if(state_count != 1) {
146
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
147
+ }
118
148
  assert(state_count == 1 && "CoroStart must be in exactly one state");
119
149
 
120
150
  /* Additional consistency checks */
121
151
  if(is_done) {
122
- /* done() state - should not have s_value */
123
- assert(self->s_value == NULL && "done() state should not have s_value");
124
- }
125
- if(is_pending) {
126
- /* pending() state - should not have exception fields */
127
- assert(self->s_exc_type == NULL && "pending() state should not have exception");
128
- assert(self->s_exc_value == NULL &&
129
- "pending() state should not have exception value");
130
- assert(self->s_exc_traceback == NULL &&
131
- "pending() state should not have exception traceback");
152
+ /* done() state can have either s_value (PYGEN_RETURN) or exception
153
+ * (PYGEN_ERROR) */
154
+ if(!(self->initial_result == PYGEN_RETURN ||
155
+ self->initial_result == PYGEN_ERROR)) {
156
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
157
+ }
158
+ assert(self->initial_result == PYGEN_RETURN ||
159
+ self->initial_result == PYGEN_ERROR);
160
+ // either s_value is non-NULL or s_exc_type is non-NULL (exclusive or)
161
+ if(!((self->s_value != NULL) ^ (self->s_exc_type != NULL))) {
162
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
163
+ }
164
+ assert((self->s_value != NULL) ^ (self->s_exc_type != NULL));
165
+ } else if(is_pending) {
166
+ /* pending() state - should only have s_value set */
167
+ if(!(self->initial_result == PYGEN_NEXT)) {
168
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
169
+ }
170
+ assert(self->initial_result == PYGEN_NEXT);
171
+ if(!(self->s_value != NULL && self->s_exc_type == NULL)) {
172
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
173
+ }
174
+ assert(self->s_value != NULL && self->s_exc_type == NULL);
175
+ } else {
176
+ /* continued() state - all fields should be NULL */
177
+ if(!(self->initial_result == PYGEN_NEXT)) {
178
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
179
+ }
180
+ assert(self->initial_result == PYGEN_NEXT);
181
+ if(!(self->s_value == NULL && self->s_exc_type == NULL)) {
182
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
183
+ }
184
+ assert(self->s_value == NULL && self->s_exc_type == NULL);
132
185
  }
133
186
  #endif
134
187
  }
@@ -149,6 +202,8 @@ static int corostart_start(CoroStartObject *self)
149
202
  }
150
203
 
151
204
  /* Call coro.send(None) using PyIter_Send API */
205
+ /* STATE MODIFICATION: Sets initial_result and may set s_value or exception fields
206
+ */
152
207
  self->initial_result = PyIter_Send(self->wrapped_coro, Py_None, &self->s_value);
153
208
 
154
209
  TRACE_LOG("PyIter_Send returned: %d (NEXT=1, RETURN=0, ERROR=-1)",
@@ -157,16 +212,22 @@ static int corostart_start(CoroStartObject *self)
157
212
  switch(self->initial_result) {
158
213
  case PYGEN_NEXT:
159
214
  /* Coroutine yielded a value */
215
+ /* STATE: PENDING (s_value set by PyIter_Send) */
160
216
  TRACE_LOG("PYGEN_NEXT: coroutine yielded a value");
217
+ assert(IS_PENDING(self));
161
218
  break;
162
219
  case PYGEN_RETURN:
163
220
  /* Coroutine completed normally - create StopIteration */
221
+ /* STATE: DONE (s_value set by PyIter_Send) */
164
222
  TRACE_LOG("PYGEN_RETURN: coroutine completed normally");
223
+ assert(IS_DONE(self));
165
224
  break;
166
225
  case PYGEN_ERROR:
167
226
  /* Exception occurred - PyIter_Send already set the exception */
227
+ /* STATE MODIFICATION: Transition to DONE with exception */
168
228
  TRACE_LOG("PYGEN_ERROR: exception occurred");
169
229
  PyErr_Fetch(&self->s_exc_type, &self->s_exc_value, &self->s_exc_traceback);
230
+ assert(IS_DONE(self));
170
231
  break;
171
232
  }
172
233
 
@@ -184,7 +245,7 @@ static int corostart_start(CoroStartObject *self)
184
245
  TRACE_LOG("Coroutine completed or raised exception");
185
246
  }
186
247
 
187
- assert_corostart_invariant(self);
248
+ ASSERT_COROSTART_INVARIANT(self);
188
249
  return 0; /* Success (we handled the exception) */
189
250
  }
190
251
 
@@ -343,31 +404,37 @@ static PySendResult corostart_wrapper_am_send_slot(PyObject *_self,
343
404
  if(corostart->s_value != NULL) {
344
405
  // We have a result
345
406
  TRACE_LOG("Coroutine completed with result");
407
+ /* STATE MODIFICATION: Transfer ownership, transition to CONTINUED */
346
408
  *result = corostart->s_value;
347
- corostart->s_value =
348
- NULL; /* Clear and transfer ownership - marks as continued */
409
+ corostart->s_value = NULL;
410
+ corostart->initial_result = PYGEN_NEXT; // Mark as continued
411
+ assert(IS_CONTINUED(corostart));
349
412
  return PYGEN_RETURN;
350
413
  }
351
414
 
352
415
  // we have an exception, restore it and return error
416
+ /* STATE MODIFICATION: Transfer exception, transition to CONTINUED */
353
417
  PyErr_Restore(corostart->s_exc_type,
354
418
  corostart->s_exc_value,
355
419
  corostart->s_exc_traceback);
356
- /* Clear the fields (PyErr_Restore steals references) - marks as continued
357
- */
420
+ /* Clear (PyErr_Restore steals references) - marks as continued */
358
421
  corostart->s_exc_type = NULL;
359
422
  corostart->s_exc_value = NULL;
360
423
  corostart->s_exc_traceback = NULL;
424
+ corostart->initial_result = PYGEN_NEXT; // Mark as continued
361
425
  *result = NULL;
426
+ assert(IS_CONTINUED(corostart));
362
427
  return PYGEN_ERROR;
363
428
  }
364
429
 
365
430
  // we are pending
366
431
  TRACE_LOG("Coroutine yielded during start - returning yielded value");
367
432
  /* Coroutine yielded a value - return it and clear state to mark as continued */
433
+ /* STATE MODIFICATION: Transfer value, transition to CONTINUED */
368
434
  *result = corostart->s_value;
369
- corostart->s_value =
370
- NULL; /* Clear and transfer ownership - marks as continued */
435
+ corostart->s_value = NULL;
436
+ corostart->initial_result = PYGEN_NEXT; // Mark as continued
437
+ assert(IS_CONTINUED(corostart));
371
438
  return PYGEN_NEXT;
372
439
  }
373
440
 
@@ -393,6 +460,10 @@ static PySendResult corostart_wrapper_am_send_slot(PyObject *_self,
393
460
  if(corostart->context != NULL) {
394
461
  if(PyContext_Exit(corostart->context) < 0) {
395
462
  if(send_result != PYGEN_ERROR) {
463
+ // Api dictates we must clear result if we return PYGEN_ERROR
464
+ // this error takes precedence over any send result we might
465
+ // have gotten. If there was an error from send, it will
466
+ // be chained automatically by Python.
396
467
  Py_CLEAR(*result);
397
468
  }
398
469
  return PYGEN_ERROR;
@@ -548,7 +619,6 @@ static PyType_Spec corostart_spec = {
548
619
  static PyObject *corostart_done(PyObject *_self)
549
620
  {
550
621
  CoroStartObject *self = (CoroStartObject *) _self;
551
- assert_corostart_invariant(self);
552
622
  // Return True if we have completed (indicated by having an exception)
553
623
  // Either StopIteration (normal completion) or any other exception (error)
554
624
  if(IS_DONE(self)) {
@@ -562,7 +632,6 @@ static PyObject *corostart_done(PyObject *_self)
562
632
  static PyObject *corostart_continued(PyObject *_self, PyObject *Py_UNUSED(args))
563
633
  {
564
634
  CoroStartObject *self = (CoroStartObject *) _self;
565
- assert_corostart_invariant(self);
566
635
  // Return True if the coroutine has been continued (awaited) after initial start
567
636
  // In C implementation, continued means all start result fields are NULL
568
637
  if(IS_CONTINUED(self)) {
@@ -574,7 +643,6 @@ static PyObject *corostart_continued(PyObject *_self, PyObject *Py_UNUSED(args))
574
643
  static PyObject *corostart_pending(PyObject *_self, PyObject *Py_UNUSED(args))
575
644
  {
576
645
  CoroStartObject *self = (CoroStartObject *) _self;
577
- assert_corostart_invariant(self);
578
646
  // Return True if the coroutine is pending, waiting for async operation
579
647
  // This means we have a yielded value (s_value != NULL)
580
648
  if(IS_PENDING(self)) {
@@ -639,34 +707,37 @@ static PyObject *corostart_exception(PyObject *_self)
639
707
  Py_RETURN_NONE;
640
708
  }
641
709
 
642
-
643
710
  // Return the exception (not re-raise it like result() does)
644
- if(self->s_exc_value && PyObject_IsInstance(self->s_exc_value, self->s_exc_type)) {
645
- // s_exc_value is an actual exception instance
646
- Py_INCREF(self->s_exc_value);
647
- return self->s_exc_value;
648
- } else {
649
- // s_exc_value is the raw value, need to construct exception
650
- PyObject *exc_instance = PyObject_CallFunction(self->s_exc_type,
651
- "O",
652
- self->s_exc_value
653
- ? self->s_exc_value
654
- : Py_None);
655
- return exc_instance;
711
+ if(self->s_exc_value) {
712
+ int res = PyObject_IsInstance(self->s_exc_value, self->s_exc_type);
713
+ if(res < 0) {
714
+ return NULL; // error during IsInstance check
715
+ }
716
+ if(res == 1) {
717
+ // s_exc_value is an actual exception instance
718
+ return Py_NewRef(self->s_exc_value);
719
+ }
656
720
  }
721
+ // s_exc_value is the raw value, need to construct exception
722
+ // if it is null, it is omitted by the following call
723
+ // any failure is propagated to the caller.
724
+ return PyObject_CallFunctionObjArgs(self->s_exc_type, self->s_exc_value, NULL);
657
725
  }
658
726
 
727
+
728
+ // Throw an exception into the coroutine
729
+ // set the state accordingly. any error except validation will set the state to done
730
+ // with the exception.
659
731
  static PyObject *corostart__throw(PyObject *_self, PyObject *exc)
660
732
  {
661
733
  CoroStartObject *self = (CoroStartObject *) _self;
662
- assert_corostart_invariant(self);
734
+ ASSERT_COROSTART_INVARIANT(self);
663
735
 
664
736
  // Convert exception type to instance if needed
665
737
  PyObject *value;
666
738
  if(PyExceptionInstance_Check(exc)) {
667
739
  // exc is already an exception instance
668
- value = exc;
669
- Py_INCREF(value);
740
+ value = Py_NewRef(exc);
670
741
  } else if(PyExceptionClass_Check(exc)) {
671
742
  // exc is an exception type, instantiate it
672
743
  value = PyObject_CallFunction(exc, NULL);
@@ -678,9 +749,8 @@ static PyObject *corostart__throw(PyObject *_self, PyObject *exc)
678
749
  return NULL;
679
750
  }
680
751
 
681
- PyObject *result;
682
-
683
752
  // Call throw() with context support
753
+ PyObject *result;
684
754
  if(self->context != NULL) {
685
755
  /* Enter context */
686
756
  if(PyContext_Enter(self->context) < 0) {
@@ -699,86 +769,89 @@ static PyObject *corostart__throw(PyObject *_self, PyObject *exc)
699
769
  } else {
700
770
  result = PyObject_CallMethod(self->wrapped_coro, "throw", "O", value);
701
771
  }
772
+ Py_DECREF(value); // done with this
773
+
774
+ /* STATE MODIFICATION: Clear all state fields before setting new state */
775
+ Py_CLEAR(self->s_value);
776
+ Py_CLEAR(self->s_exc_type);
777
+ Py_CLEAR(self->s_exc_value);
778
+ Py_CLEAR(self->s_exc_traceback);
702
779
 
703
780
  if(result != NULL) {
781
+ /* STATE MODIFICATION: Transition to PENDING with new yielded value */
704
782
  // Coroutine yielded a value - update state to pending with the new value
705
- Py_CLEAR(self->s_value);
706
- Py_CLEAR(self->s_exc_type);
707
- Py_CLEAR(self->s_exc_value);
708
- Py_CLEAR(self->s_exc_traceback);
709
783
  self->s_value = result; // Store new yielded value (becomes pending state)
710
- self->initial_result =
711
- PYGEN_NEXT; // Update initial_result for new state macros
712
-
713
- Py_DECREF(value);
784
+ self->initial_result = PYGEN_NEXT;
785
+ assert(IS_PENDING(self));
714
786
  Py_RETURN_NONE;
715
787
  }
716
788
 
717
789
  // Check if we got StopIteration (normal completion)
718
790
  if(PyErr_ExceptionMatches(PyExc_StopIteration)) {
719
- PyObject *exc_type, *exc_value, *exc_traceback;
720
- PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
721
-
722
- // Coroutine finished synchronously - update state to done
723
- Py_CLEAR(self->s_value);
724
- Py_CLEAR(self->s_exc_type);
725
- Py_CLEAR(self->s_exc_value);
726
- Py_CLEAR(self->s_exc_traceback);
727
-
728
- // For PYGEN_RETURN, store the return value in s_value (like corostart_start
729
- // does) Extract the value from StopIteration - it might be an instance or raw
730
- // value
731
- if(exc_value && PyObject_IsInstance(exc_value, exc_type)) {
732
- // exc_value is an actual StopIteration instance - extract .value
733
- PyObject *return_value = PyObject_GetAttrString(exc_value, "value");
734
- if(return_value == NULL) {
735
- PyErr_Clear();
736
- return_value = Py_NewRef(Py_None);
737
- }
738
- self->s_value = return_value;
739
- Py_DECREF(exc_value); // Clean up the StopIteration instance
791
+ /* STATE MODIFICATION: Transition to DONE with return value */
792
+ self->s_value = extract_stopiteration_value();
793
+ if(self->s_value == NULL) {
794
+ /* STATE MODIFICATION: extract failed, transition to DONE with exception */
795
+ // something wrong with extract_stopiteration_value, store it as regular
796
+ // exception
797
+ PyErr_Fetch(&self->s_exc_type, &self->s_exc_value, &self->s_exc_traceback);
798
+ self->initial_result = PYGEN_ERROR;
740
799
  } else {
741
- // exc_value is the raw value (Python optimization)
742
- if(exc_value) {
743
- self->s_value = exc_value; // Transfer ownership from PyErr_Fetch
744
- } else {
745
- self->s_value = Py_NewRef(Py_None);
746
- }
800
+ self->initial_result = PYGEN_RETURN;
747
801
  }
748
- self->initial_result = PYGEN_RETURN; // Update for new state macros
749
-
750
- // Clean up the exception objects we don't need
751
- Py_DECREF(exc_type);
752
- Py_XDECREF(exc_traceback);
753
-
754
- Py_DECREF(value);
755
- Py_RETURN_NONE;
802
+ } else {
803
+ /* STATE MODIFICATION: Transition to DONE with exception */
804
+ // any other exception
805
+ PyErr_Fetch(&self->s_exc_type, &self->s_exc_value, &self->s_exc_traceback);
806
+ self->initial_result = PYGEN_ERROR;
756
807
  }
757
808
 
758
- // Any other exception - update state to done with the exception
759
- PyObject *exc_type, *exc_value, *exc_traceback;
760
- PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
809
+ assert(IS_DONE(self));
810
+ Py_RETURN_NONE;
811
+ }
761
812
 
762
- Py_CLEAR(self->s_value);
763
- Py_CLEAR(self->s_exc_type);
764
- Py_CLEAR(self->s_exc_value);
765
- Py_CLEAR(self->s_exc_traceback);
766
813
 
767
- // Set done state with the exception
768
- self->s_exc_type = exc_type;
769
- self->s_exc_value = exc_value;
770
- self->s_exc_traceback = exc_traceback;
771
- self->initial_result = PYGEN_ERROR; // Update for new state macros
814
+ /* extract the value of a stopiteration. Assumes that we have
815
+ * a StopIteration error state
816
+ */
817
+ static PyObject *extract_stopiteration_value(void)
818
+ {
819
+ PyObject *exc_type, *exc_value, *exc_traceback;
772
820
 
773
- Py_DECREF(value);
774
- Py_RETURN_NONE;
821
+ PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
822
+ assert(exc_type != NULL);
823
+ assert(PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration));
824
+
825
+ if(exc_value != NULL && PyObject_IsInstance(exc_value, exc_type)) {
826
+ // exc_value is an actual StopIteration instance - extract .value
827
+ PyObject *return_value = PyObject_GetAttrString(exc_value, "value");
828
+ if(return_value == NULL) {
829
+ // handle the error case. re-set the original error
830
+ PyErr_Restore(exc_type, exc_value, exc_traceback);
831
+ } else {
832
+ // clear the original error
833
+ Py_DECREF(exc_type);
834
+ Py_DECREF(exc_value);
835
+ Py_XDECREF(exc_traceback);
836
+ }
837
+ return return_value;
838
+ } else {
839
+ // exc_value is the raw value (internal C python optimization)
840
+ // or NULL if StopIteration was raised with no value
841
+ Py_DECREF(exc_type);
842
+ Py_XDECREF(exc_traceback);
843
+ if(exc_value == NULL) {
844
+ // No return value - return None
845
+ Py_RETURN_NONE;
846
+ }
847
+ return exc_value; // Transfer ownership from PyErr_Fetch
848
+ }
775
849
  }
776
850
 
777
-
778
851
  static PyObject *corostart_close(PyObject *_self)
779
852
  {
780
853
  CoroStartObject *self = (CoroStartObject *) _self;
781
- assert_corostart_invariant(self);
854
+ ASSERT_COROSTART_INVARIANT(self);
782
855
 
783
856
  PyObject *result;
784
857
 
@@ -802,14 +875,16 @@ static PyObject *corostart_close(PyObject *_self)
802
875
  result = PyObject_CallMethod(self->wrapped_coro, "close", NULL);
803
876
  }
804
877
 
878
+ /* STATE MODIFICATION: Transition to CONTINUED state */
805
879
  // Transition to continued() state so that subsequent await attempts
806
880
  // trigger the "cannot reuse already awaited coroutine" error in __await__
807
881
  Py_CLEAR(self->s_value);
808
882
  Py_CLEAR(self->s_exc_type);
809
883
  Py_CLEAR(self->s_exc_value);
810
884
  Py_CLEAR(self->s_exc_traceback);
885
+ self->initial_result = PYGEN_NEXT; // Mark as not-done to enter CONTINUED state
811
886
 
812
- assert_corostart_invariant(self);
887
+ assert(IS_CONTINUED(self));
813
888
  return result;
814
889
  }
815
890
 
@@ -847,7 +922,7 @@ static PyObject *corostart_new(PyTypeObject *type, PyObject *args, PyObject *kwa
847
922
  Py_XINCREF(cs->context);
848
923
  }
849
924
 
850
- /* Initialize start result fields */
925
+ /* STATE INITIALIZATION: All state fields start as NULL */
851
926
  cs->s_value = NULL;
852
927
  cs->s_exc_type = NULL;
853
928
  cs->s_exc_value = NULL;
@@ -860,7 +935,7 @@ static PyObject *corostart_new(PyTypeObject *type, PyObject *args, PyObject *kwa
860
935
  return NULL;
861
936
  }
862
937
 
863
- assert_corostart_invariant(cs);
938
+ ASSERT_COROSTART_INVARIANT(cs);
864
939
  return (PyObject *) cs;
865
940
  }
866
941
 
@@ -874,20 +949,26 @@ static PyObject *cext_get_build_info(PyObject *_self)
874
949
  return NULL;
875
950
  }
876
951
 
877
- #ifdef DEBUG
878
- PyDict_SetItemString(info, "build_type", PyUnicode_FromString("debug"));
879
- PyDict_SetItemString(info, "debug_enabled", Py_True);
880
- #else
881
- PyDict_SetItemString(info, "build_type", PyUnicode_FromString("optimized"));
882
- PyDict_SetItemString(info, "debug_enabled", Py_False);
883
- #endif
952
+ PyObject *temp;
953
+ int res;
884
954
 
885
- #ifdef NDEBUG
886
- PyDict_SetItemString(info, "ndebug_enabled", Py_True);
955
+ #ifndef NDEBUG
956
+ const char *build_type = "debug";
887
957
  #else
888
- PyDict_SetItemString(info, "ndebug_enabled", Py_False);
958
+ const char *build_type = "optimized";
889
959
  #endif
890
960
 
961
+ temp = PyUnicode_FromString(build_type);
962
+ if(temp == NULL) {
963
+ Py_DECREF(info);
964
+ return NULL;
965
+ }
966
+ res = PyDict_SetItemString(info, "build_type", temp);
967
+ Py_DECREF(temp);
968
+ if(res < 0) {
969
+ Py_DECREF(info);
970
+ return NULL;
971
+ }
891
972
  return info;
892
973
  }
893
974
 
@@ -83,6 +83,7 @@ def get_implementation_info() -> dict[str, Any]:
83
83
  "performance_info": (
84
84
  "Baseline performance (install with build tools for 4x boost)"
85
85
  ),
86
+ "build_info": {},
86
87
  "c_extension_available": False,
87
88
  }
88
89
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asynkit
3
- Version: 0.16.2
3
+ Version: 0.16.4
4
4
  Summary: A toolkit for Python coroutines
5
5
  Author-email: Kristján Valur Jónsson <sweskman@gmail.com>
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes