-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
small enhancements to Task #13963
small enhancements to Task #13963
Conversation
if throw_internal is running from the segv_handler, there might not be enough stack to run arbitrary functions this change, therefore, makes stack overflow in tasks more reliable
the task->last field was unnecessary, and could result in a task staying alive in the gc unnecessarily :runnable was almost equivalent to :waiting, except that it might "be restarted unpredictably" (via the task->last field)
only ca9d5e7 for the backport label, right? |
@tkelman yes seeing there has been no objections, i'll merge this in a few hours |
@@ -1010,7 +1010,6 @@ export | |||
Condition, | |||
consume, | |||
current_task, | |||
istaskstarted, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This removes an exported function without a deprecation. Is there now no way to tell that a task has started?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
C should be able to still compute this if needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we should have this function call such C code, and then put a proper deprecation warning on it if we're actually going to remove it. I don't see why though. Is there a good reason to remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In various intermediate versions of the PR, it wasn't possible, so I eliminated it. Later on, I didn't see an obvious need to reimplement it. Plus, the only usage of it in Base (in serialize) appears to be a race condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
restored in 07ef29f
I don't understand the claim that the waiting state is "almost the same as" the runnable state. In the waiting state, a task cannot be restarted because it's waiting for an event. In the runnable state, that event has happened, and the task could be run if chosen by a scheduler. Also, task states are mentioned in the control flow chapter of the manual so that needs to be updated if we keep this change. |
# if a finished task accidentally gets into the queue, wait() | ||
# could return. in that case just take the next task off the queue. | ||
while true | ||
wait() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure this loop is no longer necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. calling yieldto on this task will cause jl_switchto to call jl_throw
that would be the |
Ok, in that case maybe the |
@@ -348,13 +348,13 @@ end | |||
|
|||
function serialize(s::SerializationState, t::Task) | |||
serialize_cycle(s, t) && return | |||
if istaskstarted(t) && !istaskdone(t) | |||
if !istaskdone(t) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the old version of this code, it was possible to make a task and then serialize it right away (a slightly strange thing to do, but it could fall out of reasonable code quasi-accidentally). Is that no longer possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I just don't see how it could safely fall out of reasonable code. This condition introduces behavior whereby you can create copies of a Task, but only until it starts running. However, since the next few lines call serialize (and thus yield), the Task could start running while this function is going leading to inconsistent serialization results. (I realize the same could be said for all of the serialize methods, since we don't buffer / deepcopy the whole tree first, but perhaps such is life)
I think there are ways of avoiding this, depending on the desired behavior. A stable result is guaranteed once the task is finished, however, making that state the only state that is definitely safe to serialize.
The new states aren't necessary all that similar to the old states: it's certain possible to split |
Ok, that's all fine, but "runnable" is just not the right word to describe a task that's blocked on an event. |
If |
The way a scheduler is traditionally described is that you have a set of tasks or threads. Some subset of those are waiting for external events and therefore cannot run. The rest are "runnable", and the scheduler picks something from that set to actually run. The set of tasks the scheduler might pick from is obviously important, so it makes sense to have a label for it. A crucial distinction is whether the scheduler is the reason the task is not currently running. That can be fixed or changed using a different scheduling algorithm. But if you're waiting for an outside event, the scheduler can't control whether you run. In ordinary conversation, if a task is blocked on I/O wouldn't you say it's "waiting" or "blocked"? |
Yes, I simply still do not see the point is distinguishing between
Exactly. When the scheduler is the reason a task is not running, the state is |
I don't know, I would argue that a task that's currently running and one that's waiting for I/O have meaningfully different statuses. If we were looking at a task in a debugger wouldn't that be an important distinction? In your last sentence you wrote "...tasks that are waiting for an event...". So why not call that state "waiting"? |
because it adds unnecessary state to the system and you still haven't given a use case for it. i doubt i would use this in a debugger, since it won't necessarily return the most accurate or useful answer, whereas the debugger can quickly and accurately test jl_current_task and check the backtrace to print the right answer. |
I believe this was a use case: Line 156 in 34a834f
|
@vtjnash – calling a blocked task "runnable" is confusing and wrong. Eliminating a state is fine, but you can't call blocked tasks runnable. |
"blocked" is not a task state that is managed by the scheduler. While a blocked task is both runnable (not scheduled, but it is still executable) and waiting. You can change the state of a task to whatever you want when it is not in the scheduler; the system really couldn't care less. However, to put it in the scheduler, it must be in the |
This can't be called "runnable" since it's often not actually runnable. Come up with a better name. |
Name bug is still open here, yeah? |
This is the simpler stuff (3 independent items) that I pulled out of #13099:
task->last
. it wasn't necessary for anything, and so it just adds complexity and delays gc