Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Add support for PEP 567 context variables to tasklets #243

Merged
merged 14 commits into from
May 18, 2021

Conversation

akruis
Copy link

@akruis akruis commented Apr 30, 2021

A possible implementation for #239.

Anselm Kruis added 2 commits May 1, 2021 01:40
… function

Use a Py_LOCAL_INLINE function instead of a complicated macro. It is
much simpler to debug. In the next commit, we will context switching
to this function.
Add a private context attribute and appropriate methods to class
tasklet. Document the changes in the manual.
New methods: tasklet.set_context(context), tasklet.context_run(...)
New readonly attribute: tasklet.context_id
@akruis akruis requested a review from kristjanvalur April 30, 2021 23:57
Anselm Kruis added 3 commits May 4, 2021 22:52
Add/improve pickling of the context of tasklets.
New pickle flag "PICKLEFLAGS_PICKLE_CONTEXT", new undocumented
function stackless._tasklet_get_unpicklable_state()
@akruis
Copy link
Author

akruis commented May 5, 2021

I'm now fairly happy with this pull request.
@kristjanvalur I really appreciate your comments.

@kristjanvalur
Copy link
Collaborator

I'll try to give it a good read tonight.

@@ -134,6 +134,7 @@ typedef struct _tasklet {
int recursion_depth;
PyObject *def_globals;
PyObject *tsk_weakreflist;
PyObject *context; /* if running: the saved context, otherwise the context for the tasklet */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a saved context? If it is the context of a previous tasklet, surely that tasklet's context is stored in that tasklet's
context member.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In C-Python the thread state (PyThreadState or short ts for the current thread state) contains a counted reference to a Context object: ts->context. This reference may be NULL.

In Stackless Python each tasklet has its own counted reference to a Context object: tasklet->context.

Now, if you switch from one tasklet to another the reference count of the involved context objects must not change. (During a tasklet switch a recursion into the interpreter is not allowed and therefore Py_DECREF is not allowed.) Therefore its is only possible to "exchange" context between variables.

  1. exchange the values of old_tasklet->context and ts->context
  2. exchange the values of new_tasklet->context and ts->context

If you create the main tasklet in slp_initialize_main_and_current() only one exchange happens:

  1. exchange the values of main_tasklet->context and ts->context

Same on exit of the main tasklet in schedule_task_destruct().

  1. exchange the values of main_tasklet->context and ts->context

The effect of this schema is, that the value of tasklet->context depends on the state of the tasklet.

  • if tasklet is current: tasklet->context is the value of ts->context before the main tasklet was created. ts->context is the context of the current tasklet.
  • if tasklet is not current: tasklet->context is the context of the tasklet.

To answer your question: the "saved" context is the value of ts->context when the main-tasklet was created.
Of course I have to improve the comment.

Copy link
Collaborator

@kristjanvalur kristjanvalur May 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to create a context when the main tasklet is created? how about this:

  1. main tasklet is created and entered. it inherits the threads context. ts->stackless->current == ts->stackless->main_tasklet, and ts->context is possibly non null and main_tasklet->context == NULL. (sorry if my recollection of stackless ts is a bit rusty)
  2. create a new tasklet. It gets a copy of the ts->context as described.
  3. switch to another tasklet. . old_tasklet->context = ts->context, and ts->context = new_tasklet->context

I'm probably missing some fine detail, though, but it still seems to me that the main tasklet's context should be the same as the thread's context that created it. That way, a runnint tasklet has a context of NULL, and a sleeping context has a possibly non-null context. I don't see why we need to keep the context of the main thread before initializing the main tasklet as a separate thing, because the main tasklet is the entry context of the thread.

Copy link
Author

@akruis akruis May 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are perfectly right. Keeping the context of the main thread is just a side effect of the implementation. And the implementation - the exchange values between thread-state and tasklet logic - was just an attempt to keep the reference counting correct without too much thinking about it: the exchange operation simply does not affect reference counts and there was already suitable macro/inline-function (SLP_EXCHANGE_EXCINFO). But your suggestion most likely also works an is much simpler. See 71d8e0e

c = ts_->context; \
ts_->context = t_->context; \
t_->context = c; \
ts_->context_ver++; \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See previous comment about why the currently runnint tasklet has a "saved" context? Also, what is context_ver?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abaut context_ver:

A context is a Mapping from ContextVar objects to their values. A ContextVar caches its current value and the current context_ver. To invalidate the cache context_ver must be incremented on each change of ts->context. See

var->var_cached_tsver = ts->context_ver;

Copy link
Collaborator

@kristjanvalur kristjanvalur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow. This is a major addition, probably the biggest change in many years.
I didn't review the C code in detail or the tests, I trust these run fine. Also, my C python eyes have become a bit rusty. But I did have some questions about context and tasklets.

@kristjanvalur
Copy link
Collaborator

Also, great work, Anselm!

@akruis
Copy link
Author

akruis commented May 8, 2021

Also, great work, Anselm!

Thank's. But your remarks make it even better. Many thanks for your review. It is really hard to write good quality code, if you're alone.

Anselm Kruis added 3 commits May 9, 2021 00:06
- better documentation of the concept
- fix a left-over from the time, when setting the context of a main-
  tasklet was not supported
- fix the context handling, when the main tasklet starts/ends. Copy the
  context from/to the thread state.
- fix the context handling, when the main tasklet starts/ends. Copy the
  context from/to the thread state. Fix the test case.
@akruis
Copy link
Author

akruis commented May 8, 2021

I fixed the issues found by Kristján and one other issue.

Anselm Kruis added 2 commits May 9, 2021 14:02
Use stackless._stackless._test_outside instead of
stackless.test_outside.
@kristjanvalur
Copy link
Collaborator

Couple of things I'd like explained.

  1. Its the saved tasklet of the main before task initialization above. why we need a sepaate context for the main tasklet.
  2. Comments mention that contexts are useful for tasklets in conjunction with async. I agree that context are useful (previously we had stacklesslib.tasklet_local or something, based on threading.local), and having a uniform storage like this, which works for threads, co-routines, and tasklets, is good. But do you see a specific use case where we have to do manual manipulation of tasklet contexts?

contexts managed by stackless are great, by the way, since we (probably) don't need to manipulate them, or have these "run in context" functions like a async hub has to do (i.e. manually switching context). ... Maybe this can be added to the doc, explainin that there really needs to be no manual management of contexts when using tasklets.

Simplify context switching as sugested by Kristján.
@akruis
Copy link
Author

akruis commented May 18, 2021

Couple of things I'd like explained.
1. Its the saved tasklet of the main before task initialization above. why we need a sepaate context for the main tasklet.

Good point. I simplified the code in 71d8e0e. (When I coded the first version I just copied the approach used for the exception
state without thinking much about the requirements.)

2. Comments mention that contexts are useful for tasklets in conjunction with async.  I agree that context are useful (previously we had stacklesslib.tasklet_local or something, based on threading.local), and having a uniform storage like this, which works for threads, co-routines, and tasklets, is good.  But do you see a specific use case where we have to do manual manipulation of tasklet contexts?

contexts managed by stackless are great, by the way, since we (probably) don't need to manipulate them, or have these "run in context" functions like a async hub has to do (i.e. manually switching context). ... Maybe this can be added to the doc, explainin that there really needs to be no manual management of contexts when using tasklets.

I already have a valid use case for tasklet.context_run(). This method is required, if you have to execute something on behalf of a tasklet. In the example at https://github.com/akruis/slp_coroutines/blob/03333e72356a06835b742b4e78b88c544a3a00bb/src/slp_coroutine.py#L109-L112 the tasklet wants you to await a a coroutine.

I can imagine a requirement for tasklet.set_context()

  • if you rebind a tasklet, i.e. if you have a pool of worker tasklets and
  • after unpickling a tasklet

For tasklet.context_id probably debugging is the only valid use case.

Copy link
Collaborator

@kristjanvalur kristjanvalur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff. sorry for my late review, Busy with things :)

@akruis
Copy link
Author

akruis commented May 18, 2021

I'm busy too, during the day. Stackless is an evening project. It gets me away from watching youtube all the time. There isn't much you can do in the evenings these days.
Again many thanks for reviewing.

@akruis akruis merged commit 032a566 into stackless-dev:3.7-slp May 18, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants