asynkit 0.16.2__tar.gz → 0.16.3__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.3}/PKG-INFO +1 -1
  2. {asynkit-0.16.2 → asynkit-0.16.3}/pyproject.toml +1 -1
  3. {asynkit-0.16.2 → asynkit-0.16.3}/setup.py +2 -0
  4. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/_cext/corostart.c +205 -110
  5. {asynkit-0.16.2 → asynkit-0.16.3/src/asynkit.egg-info}/PKG-INFO +1 -1
  6. {asynkit-0.16.2 → asynkit-0.16.3}/LICENSE +0 -0
  7. {asynkit-0.16.2 → asynkit-0.16.3}/README.md +0 -0
  8. {asynkit-0.16.2 → asynkit-0.16.3}/setup.cfg +0 -0
  9. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/__init__.py +0 -0
  10. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/compat.py +0 -0
  11. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/coroutine.py +0 -0
  12. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/experimental/__init__.py +0 -0
  13. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/experimental/anyio.py +0 -0
  14. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/experimental/interrupt.py +0 -0
  15. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/experimental/priority.py +0 -0
  16. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/loop/__init__.py +0 -0
  17. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/loop/default.py +0 -0
  18. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/loop/eventloop.py +0 -0
  19. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/loop/extensions.py +0 -0
  20. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/loop/schedulingloop.py +0 -0
  21. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/loop/types.py +0 -0
  22. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/monitor.py +0 -0
  23. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/py.typed +0 -0
  24. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/scheduling.py +0 -0
  25. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit/tools.py +0 -0
  26. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit.egg-info/SOURCES.txt +0 -0
  27. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit.egg-info/dependency_links.txt +0 -0
  28. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit.egg-info/not-zip-safe +0 -0
  29. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit.egg-info/requires.txt +0 -0
  30. {asynkit-0.16.2 → asynkit-0.16.3}/src/asynkit.egg-info/top_level.txt +0 -0
  31. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_anyio.py +0 -0
  32. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_compat_eager.py +0 -0
  33. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_coro.py +0 -0
  34. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_df.py +0 -0
  35. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_eager_task_factory.py +0 -0
  36. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_eager_task_factory_context.py +0 -0
  37. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_implementation_params.py +0 -0
  38. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_loop.py +0 -0
  39. {asynkit-0.16.2 → asynkit-0.16.3}/tests/test_monitor.py +0 -0
  40. {asynkit-0.16.2 → asynkit-0.16.3}/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.3
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.3"
4
4
  description = "A toolkit for Python coroutines"
5
5
  authors = [
6
6
  {name = "Kristján Valur Jónsson", email = "sweskman@gmail.com"}
@@ -82,6 +82,7 @@ def get_compile_args():
82
82
  "/Zi", # Debug symbols
83
83
  "/Od", # No optimization
84
84
  "/DDEBUG", # Debug macro
85
+ "/UNDEBUG", # Undefine NDEBUG to enable asserts
85
86
  "/W3", # Warning level 3
86
87
  ]
87
88
  )
@@ -91,6 +92,7 @@ def get_compile_args():
91
92
  "-g", # Debug symbols
92
93
  "-O0", # No optimization
93
94
  "-DDEBUG", # Debug macro
95
+ "-UNDEBUG", # Undefine NDEBUG to enable asserts
94
96
  "-Wall", # All warnings
95
97
  "-Wextra", # Extra warnings
96
98
  ]
@@ -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,36 @@ 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
+ */
106
+
107
+ #define _IS_DONE(self) ((self)->initial_result != PYGEN_NEXT)
108
+ #define _IS_CONTINUED(self) ((self)->s_value == NULL && (self)->s_exc_type == NULL)
109
+ #define _IS_PENDING(self) (!_IS_DONE(self) && !_IS_CONTINUED(self))
110
+
111
+ #define IS_DONE(self) \
112
+ (assert_corostart_invariant_impl((self), __LINE__), _IS_DONE(self))
113
+ #define IS_CONTINUED(self) \
114
+ (assert_corostart_invariant_impl((self), __LINE__), _IS_CONTINUED(self))
115
+ #define IS_PENDING(self) \
116
+ (assert_corostart_invariant_impl((self), __LINE__), _IS_PENDING(self))
117
+
118
+ /* Macro to call invariant checker with current line number */
119
+ #define ASSERT_COROSTART_INVARIANT(self) \
120
+ assert_corostart_invariant_impl((self), __LINE__)
98
121
 
99
122
  /* State invariant checker for debugging and validation */
100
- static inline void assert_corostart_invariant(CoroStartObject *self)
123
+ static inline void assert_corostart_invariant_impl(CoroStartObject *self,
124
+ int line_number)
101
125
  {
102
126
  #ifdef NDEBUG
103
127
  /* Skip checks in release builds */
104
128
  (void) self;
129
+ (void) line_number;
105
130
  #else
106
131
  /* Validate state model invariants:
107
132
  * 1. Exactly one of three states: done, pending, or continued
@@ -109,24 +134,56 @@ static inline void assert_corostart_invariant(CoroStartObject *self)
109
134
  * 3. pending: s_value != NULL (and s_exc_type == NULL)
110
135
  * 4. continued: all fields are NULL
111
136
  */
112
- int is_done = IS_DONE(self);
113
- int is_pending = IS_PENDING(self);
114
- int is_continued = IS_CONTINUED(self);
137
+ int is_done = _IS_DONE(self);
138
+ int is_pending = _IS_PENDING(self);
139
+ int is_continued = _IS_CONTINUED(self);
115
140
 
116
141
  /* Exactly one state should be true */
117
142
  int state_count = is_done + is_pending + is_continued;
143
+ if(state_count != 1) {
144
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
145
+ }
118
146
  assert(state_count == 1 && "CoroStart must be in exactly one state");
119
147
 
120
148
  /* Additional consistency checks */
121
149
  if(is_done) {
122
- /* done() state - should not have s_value */
123
- assert(self->s_value == NULL && "done() state should not have s_value");
150
+ /* done() state can have either s_value (PYGEN_RETURN) or exception
151
+ * (PYGEN_ERROR) */
152
+ if(self->initial_result == PYGEN_RETURN) {
153
+ if(!(self->s_value != NULL)) {
154
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
155
+ }
156
+ assert(self->s_value != NULL && "PYGEN_RETURN should have s_value");
157
+ if(!(self->s_exc_type == NULL)) {
158
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
159
+ }
160
+ assert(self->s_exc_type == NULL &&
161
+ "PYGEN_RETURN should not have exception");
162
+ } else if(self->initial_result == PYGEN_ERROR) {
163
+ if(!(self->s_value == NULL)) {
164
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
165
+ }
166
+ assert(self->s_value == NULL && "PYGEN_ERROR should not have s_value");
167
+ if(!(self->s_exc_type != NULL)) {
168
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
169
+ }
170
+ assert(self->s_exc_type != NULL && "PYGEN_ERROR should have exception");
171
+ }
124
172
  }
125
173
  if(is_pending) {
126
174
  /* pending() state - should not have exception fields */
175
+ if(!(self->s_exc_type == NULL)) {
176
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
177
+ }
127
178
  assert(self->s_exc_type == NULL && "pending() state should not have exception");
179
+ if(!(self->s_exc_value == NULL)) {
180
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
181
+ }
128
182
  assert(self->s_exc_value == NULL &&
129
183
  "pending() state should not have exception value");
184
+ if(!(self->s_exc_traceback == NULL)) {
185
+ fprintf(stderr, __FILE__ ":%d: ", line_number);
186
+ }
130
187
  assert(self->s_exc_traceback == NULL &&
131
188
  "pending() state should not have exception traceback");
132
189
  }
@@ -149,6 +206,8 @@ static int corostart_start(CoroStartObject *self)
149
206
  }
150
207
 
151
208
  /* Call coro.send(None) using PyIter_Send API */
209
+ /* STATE MODIFICATION: Sets initial_result and may set s_value or exception fields
210
+ */
152
211
  self->initial_result = PyIter_Send(self->wrapped_coro, Py_None, &self->s_value);
153
212
 
154
213
  TRACE_LOG("PyIter_Send returned: %d (NEXT=1, RETURN=0, ERROR=-1)",
@@ -157,16 +216,22 @@ static int corostart_start(CoroStartObject *self)
157
216
  switch(self->initial_result) {
158
217
  case PYGEN_NEXT:
159
218
  /* Coroutine yielded a value */
219
+ /* STATE: PENDING (s_value set by PyIter_Send) */
160
220
  TRACE_LOG("PYGEN_NEXT: coroutine yielded a value");
221
+ assert(IS_PENDING(self));
161
222
  break;
162
223
  case PYGEN_RETURN:
163
224
  /* Coroutine completed normally - create StopIteration */
225
+ /* STATE: DONE (s_value set by PyIter_Send) */
164
226
  TRACE_LOG("PYGEN_RETURN: coroutine completed normally");
227
+ assert(IS_DONE(self));
165
228
  break;
166
229
  case PYGEN_ERROR:
167
230
  /* Exception occurred - PyIter_Send already set the exception */
231
+ /* STATE MODIFICATION: Transition to DONE with exception */
168
232
  TRACE_LOG("PYGEN_ERROR: exception occurred");
169
233
  PyErr_Fetch(&self->s_exc_type, &self->s_exc_value, &self->s_exc_traceback);
234
+ assert(IS_DONE(self));
170
235
  break;
171
236
  }
172
237
 
@@ -184,7 +249,7 @@ static int corostart_start(CoroStartObject *self)
184
249
  TRACE_LOG("Coroutine completed or raised exception");
185
250
  }
186
251
 
187
- assert_corostart_invariant(self);
252
+ ASSERT_COROSTART_INVARIANT(self);
188
253
  return 0; /* Success (we handled the exception) */
189
254
  }
190
255
 
@@ -343,31 +408,37 @@ static PySendResult corostart_wrapper_am_send_slot(PyObject *_self,
343
408
  if(corostart->s_value != NULL) {
344
409
  // We have a result
345
410
  TRACE_LOG("Coroutine completed with result");
411
+ /* STATE MODIFICATION: Transfer ownership, transition to CONTINUED */
346
412
  *result = corostart->s_value;
347
- corostart->s_value =
348
- NULL; /* Clear and transfer ownership - marks as continued */
413
+ corostart->s_value = NULL;
414
+ corostart->initial_result = PYGEN_NEXT; // Mark as continued
415
+ assert(IS_CONTINUED(corostart));
349
416
  return PYGEN_RETURN;
350
417
  }
351
418
 
352
419
  // we have an exception, restore it and return error
420
+ /* STATE MODIFICATION: Transfer exception, transition to CONTINUED */
353
421
  PyErr_Restore(corostart->s_exc_type,
354
422
  corostart->s_exc_value,
355
423
  corostart->s_exc_traceback);
356
- /* Clear the fields (PyErr_Restore steals references) - marks as continued
357
- */
424
+ /* Clear (PyErr_Restore steals references) - marks as continued */
358
425
  corostart->s_exc_type = NULL;
359
426
  corostart->s_exc_value = NULL;
360
427
  corostart->s_exc_traceback = NULL;
428
+ corostart->initial_result = PYGEN_NEXT; // Mark as continued
361
429
  *result = NULL;
430
+ assert(IS_CONTINUED(corostart));
362
431
  return PYGEN_ERROR;
363
432
  }
364
433
 
365
434
  // we are pending
366
435
  TRACE_LOG("Coroutine yielded during start - returning yielded value");
367
436
  /* Coroutine yielded a value - return it and clear state to mark as continued */
437
+ /* STATE MODIFICATION: Transfer value, transition to CONTINUED */
368
438
  *result = corostart->s_value;
369
- corostart->s_value =
370
- NULL; /* Clear and transfer ownership - marks as continued */
439
+ corostart->s_value = NULL;
440
+ corostart->initial_result = PYGEN_NEXT; // Mark as continued
441
+ assert(IS_CONTINUED(corostart));
371
442
  return PYGEN_NEXT;
372
443
  }
373
444
 
@@ -393,6 +464,10 @@ static PySendResult corostart_wrapper_am_send_slot(PyObject *_self,
393
464
  if(corostart->context != NULL) {
394
465
  if(PyContext_Exit(corostart->context) < 0) {
395
466
  if(send_result != PYGEN_ERROR) {
467
+ // Api dictates we must clear result if we return PYGEN_ERROR
468
+ // this error takes precedence over any send result we might
469
+ // have gotten. If there was an error from send, it will
470
+ // be chained automatically by Python.
396
471
  Py_CLEAR(*result);
397
472
  }
398
473
  return PYGEN_ERROR;
@@ -548,7 +623,6 @@ static PyType_Spec corostart_spec = {
548
623
  static PyObject *corostart_done(PyObject *_self)
549
624
  {
550
625
  CoroStartObject *self = (CoroStartObject *) _self;
551
- assert_corostart_invariant(self);
552
626
  // Return True if we have completed (indicated by having an exception)
553
627
  // Either StopIteration (normal completion) or any other exception (error)
554
628
  if(IS_DONE(self)) {
@@ -562,7 +636,6 @@ static PyObject *corostart_done(PyObject *_self)
562
636
  static PyObject *corostart_continued(PyObject *_self, PyObject *Py_UNUSED(args))
563
637
  {
564
638
  CoroStartObject *self = (CoroStartObject *) _self;
565
- assert_corostart_invariant(self);
566
639
  // Return True if the coroutine has been continued (awaited) after initial start
567
640
  // In C implementation, continued means all start result fields are NULL
568
641
  if(IS_CONTINUED(self)) {
@@ -574,7 +647,6 @@ static PyObject *corostart_continued(PyObject *_self, PyObject *Py_UNUSED(args))
574
647
  static PyObject *corostart_pending(PyObject *_self, PyObject *Py_UNUSED(args))
575
648
  {
576
649
  CoroStartObject *self = (CoroStartObject *) _self;
577
- assert_corostart_invariant(self);
578
650
  // Return True if the coroutine is pending, waiting for async operation
579
651
  // This means we have a yielded value (s_value != NULL)
580
652
  if(IS_PENDING(self)) {
@@ -639,34 +711,32 @@ static PyObject *corostart_exception(PyObject *_self)
639
711
  Py_RETURN_NONE;
640
712
  }
641
713
 
642
-
643
714
  // Return the exception (not re-raise it like result() does)
644
715
  if(self->s_exc_value && PyObject_IsInstance(self->s_exc_value, self->s_exc_type)) {
645
716
  // s_exc_value is an actual exception instance
646
- Py_INCREF(self->s_exc_value);
647
- return self->s_exc_value;
717
+ return Py_NewRef(self->s_exc_value);
648
718
  } else {
649
719
  // 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;
720
+ // if it is null, it is omitted by the following call
721
+ // any failure is propagated to the caller.
722
+ return PyObject_CallFunctionObjArgs(self->s_exc_type, self->s_exc_value, NULL);
656
723
  }
657
724
  }
658
725
 
726
+
727
+ // Throw an exception into the coroutine
728
+ // set the state accordingly. any error except validation will set the state to done
729
+ // with the exception.
659
730
  static PyObject *corostart__throw(PyObject *_self, PyObject *exc)
660
731
  {
661
732
  CoroStartObject *self = (CoroStartObject *) _self;
662
- assert_corostart_invariant(self);
733
+ ASSERT_COROSTART_INVARIANT(self);
663
734
 
664
735
  // Convert exception type to instance if needed
665
736
  PyObject *value;
666
737
  if(PyExceptionInstance_Check(exc)) {
667
738
  // exc is already an exception instance
668
- value = exc;
669
- Py_INCREF(value);
739
+ value = Py_NewRef(exc);
670
740
  } else if(PyExceptionClass_Check(exc)) {
671
741
  // exc is an exception type, instantiate it
672
742
  value = PyObject_CallFunction(exc, NULL);
@@ -678,9 +748,8 @@ static PyObject *corostart__throw(PyObject *_self, PyObject *exc)
678
748
  return NULL;
679
749
  }
680
750
 
681
- PyObject *result;
682
-
683
751
  // Call throw() with context support
752
+ PyObject *result;
684
753
  if(self->context != NULL) {
685
754
  /* Enter context */
686
755
  if(PyContext_Enter(self->context) < 0) {
@@ -699,86 +768,89 @@ static PyObject *corostart__throw(PyObject *_self, PyObject *exc)
699
768
  } else {
700
769
  result = PyObject_CallMethod(self->wrapped_coro, "throw", "O", value);
701
770
  }
771
+ Py_DECREF(value); // done with this
772
+
773
+ /* STATE MODIFICATION: Clear all state fields before setting new state */
774
+ Py_CLEAR(self->s_value);
775
+ Py_CLEAR(self->s_exc_type);
776
+ Py_CLEAR(self->s_exc_value);
777
+ Py_CLEAR(self->s_exc_traceback);
702
778
 
703
779
  if(result != NULL) {
780
+ /* STATE MODIFICATION: Transition to PENDING with new yielded value */
704
781
  // 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
782
  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);
783
+ self->initial_result = PYGEN_NEXT;
784
+ assert(IS_PENDING(self));
714
785
  Py_RETURN_NONE;
715
786
  }
716
787
 
717
788
  // Check if we got StopIteration (normal completion)
718
789
  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
790
+ /* STATE MODIFICATION: Transition to DONE with return value */
791
+ self->s_value = extract_stopiteration_value();
792
+ if(self->s_value == NULL) {
793
+ /* STATE MODIFICATION: extract failed, transition to DONE with exception */
794
+ // something wrong with extract_stopiteration_value, store it as regular
795
+ // exception
796
+ PyErr_Fetch(&self->s_exc_type, &self->s_exc_value, &self->s_exc_traceback);
797
+ self->initial_result = PYGEN_ERROR;
740
798
  } 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
- }
799
+ self->initial_result = PYGEN_RETURN;
747
800
  }
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;
801
+ } else {
802
+ /* STATE MODIFICATION: Transition to DONE with exception */
803
+ // any other exception
804
+ PyErr_Fetch(&self->s_exc_type, &self->s_exc_value, &self->s_exc_traceback);
805
+ self->initial_result = PYGEN_ERROR;
756
806
  }
757
807
 
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);
808
+ assert(IS_DONE(self));
809
+ Py_RETURN_NONE;
810
+ }
761
811
 
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
812
 
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
813
+ /* extract the value of a stopiteration. Assumes that we have
814
+ * a StopIteration error state
815
+ */
816
+ static PyObject *extract_stopiteration_value(void)
817
+ {
818
+ PyObject *exc_type, *exc_value, *exc_traceback;
772
819
 
773
- Py_DECREF(value);
774
- Py_RETURN_NONE;
820
+ PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
821
+ assert(exc_type != NULL);
822
+ assert(PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration));
823
+
824
+ if(exc_value != NULL && PyObject_IsInstance(exc_value, exc_type)) {
825
+ // exc_value is an actual StopIteration instance - extract .value
826
+ PyObject *return_value = PyObject_GetAttrString(exc_value, "value");
827
+ if(return_value == NULL) {
828
+ // handle the error case. re-set the original error
829
+ PyErr_Restore(exc_type, exc_value, exc_traceback);
830
+ } else {
831
+ // clear the original error
832
+ Py_DECREF(exc_type);
833
+ Py_DECREF(exc_value);
834
+ Py_XDECREF(exc_traceback);
835
+ }
836
+ return return_value;
837
+ } else {
838
+ // exc_value is the raw value (internal C python optimization)
839
+ // or NULL if StopIteration was raised with no value
840
+ Py_DECREF(exc_type);
841
+ Py_XDECREF(exc_traceback);
842
+ if(exc_value == NULL) {
843
+ // No return value - return None
844
+ Py_RETURN_NONE;
845
+ }
846
+ return exc_value; // Transfer ownership from PyErr_Fetch
847
+ }
775
848
  }
776
849
 
777
-
778
850
  static PyObject *corostart_close(PyObject *_self)
779
851
  {
780
852
  CoroStartObject *self = (CoroStartObject *) _self;
781
- assert_corostart_invariant(self);
853
+ ASSERT_COROSTART_INVARIANT(self);
782
854
 
783
855
  PyObject *result;
784
856
 
@@ -802,14 +874,16 @@ static PyObject *corostart_close(PyObject *_self)
802
874
  result = PyObject_CallMethod(self->wrapped_coro, "close", NULL);
803
875
  }
804
876
 
877
+ /* STATE MODIFICATION: Transition to CONTINUED state */
805
878
  // Transition to continued() state so that subsequent await attempts
806
879
  // trigger the "cannot reuse already awaited coroutine" error in __await__
807
880
  Py_CLEAR(self->s_value);
808
881
  Py_CLEAR(self->s_exc_type);
809
882
  Py_CLEAR(self->s_exc_value);
810
883
  Py_CLEAR(self->s_exc_traceback);
884
+ self->initial_result = PYGEN_NEXT; // Mark as not-done to enter CONTINUED state
811
885
 
812
- assert_corostart_invariant(self);
886
+ assert(IS_CONTINUED(self));
813
887
  return result;
814
888
  }
815
889
 
@@ -847,7 +921,7 @@ static PyObject *corostart_new(PyTypeObject *type, PyObject *args, PyObject *kwa
847
921
  Py_XINCREF(cs->context);
848
922
  }
849
923
 
850
- /* Initialize start result fields */
924
+ /* STATE INITIALIZATION: All state fields start as NULL */
851
925
  cs->s_value = NULL;
852
926
  cs->s_exc_type = NULL;
853
927
  cs->s_exc_value = NULL;
@@ -860,7 +934,7 @@ static PyObject *corostart_new(PyTypeObject *type, PyObject *args, PyObject *kwa
860
934
  return NULL;
861
935
  }
862
936
 
863
- assert_corostart_invariant(cs);
937
+ ASSERT_COROSTART_INVARIANT(cs);
864
938
  return (PyObject *) cs;
865
939
  }
866
940
 
@@ -874,20 +948,41 @@ static PyObject *cext_get_build_info(PyObject *_self)
874
948
  return NULL;
875
949
  }
876
950
 
951
+ PyObject *temp;
952
+ int res;
953
+
877
954
  #ifdef DEBUG
878
- PyDict_SetItemString(info, "build_type", PyUnicode_FromString("debug"));
879
- PyDict_SetItemString(info, "debug_enabled", Py_True);
955
+ const char *build_type = "debug";
956
+ PyObject *debug_enabled = Py_True;
880
957
  #else
881
- PyDict_SetItemString(info, "build_type", PyUnicode_FromString("optimized"));
882
- PyDict_SetItemString(info, "debug_enabled", Py_False);
958
+ const char *build_type = "optimized";
959
+ PyObject *debug_enabled = Py_False;
883
960
  #endif
884
-
885
961
  #ifdef NDEBUG
886
- PyDict_SetItemString(info, "ndebug_enabled", Py_True);
962
+ PyObject *ndebug_enabled = Py_True;
887
963
  #else
888
- PyDict_SetItemString(info, "ndebug_enabled", Py_False);
964
+ PyObject *ndebug_enabled = Py_False;
889
965
  #endif
890
966
 
967
+ temp = PyUnicode_FromString(build_type);
968
+ if(temp == NULL) {
969
+ Py_DECREF(info);
970
+ return NULL;
971
+ }
972
+ res = PyDict_SetItemString(info, "build_type", temp);
973
+ Py_DECREF(temp);
974
+ if(res < 0) {
975
+ Py_DECREF(info);
976
+ return NULL;
977
+ }
978
+ if(PyDict_SetItemString(info, "debug_enabled", debug_enabled) < 0) {
979
+ Py_DECREF(info);
980
+ return NULL;
981
+ }
982
+ if(PyDict_SetItemString(info, "ndebug_enabled", ndebug_enabled) < 0) {
983
+ Py_DECREF(info);
984
+ return NULL;
985
+ }
891
986
  return info;
892
987
  }
893
988
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asynkit
3
- Version: 0.16.2
3
+ Version: 0.16.3
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