[Pitch 2] Custom Main and Global Executors

True, it unlikely makes sense to have async entry point on JS environment. However, there are some cases where users can't control if the entry point is async or not. A clear example is testing framework, the entry points of swift-testing and XCTest are provided by their framework (precisely by SwiftPM though)
So it's a bit difficult to say we can ask users not to use async entry points for JS environments unfortunately.

1 Like

I'm already working on removing it and using the type-based approach Joe suggested, which actually works quite nicely.

That's not quite right either. If you are inside a Task then the first one returns the default executor right? So it's this:

  • default actor + no task executor -> default executor
  • default actor + task executor -> task executor
  • custom actor executor + no task executor -> custom actor executor
  • custom actor executor + task executor -> custom actor executor

The only time that var currentExecutor: (any Executor)? { get } returns nil is if you are not inside a task right?

Yes I agree that we should do the former and try one executor after the other.

That's not how it works in the runtime so I'm attempting to clarify that.

When running "on a default actor" the actor is the executor. And we happen to submit to the global pool; Is is technically not true that the global concurrent executor is "the executor we're on". E.g. if comparing executors, the and you used the unownedExecutor of a default actor as the executor of another actor -- the runtime compares them, it is the same identify (the orignal actor) and therefore there's no suspension there and the "switch" succeeds, and things like that.

Not only is it the runtime's behavior, it is also how @al45tair is suggesting this property is implemented.

If we were to choose to return the global executor here... we have to really think very deeply about this, perhaps we could? :thinking: It would not be a SerialExecutor which may prevent all the issues I am worried about (wrong assume isolation calls).

I think we are talking past each other. I understand that in the runtime there is no real executor for default actors. However, we are talking about two things here:

  1. What should the proposed currentExecutor property return
  2. How should Task.yield()/sleep() behave

Now in the proposal currentExecutor is documented like this

/// Get the current executor; this is the executor that the currently
/// executing task is executing on.
public static var currentExecutor: (any Executor)? { get }

If we are inside a task there is always going to be a current executor because in the end we need to run the job somewhere. Regardless if we are isolated or not; or if the actor has a custom executor. Keep in mind this API is on Task and not Actor. So in my opinion the logic for currentExecutor should be like this:

  • If we are isolated and the actor has a custom serial executor return it
  • If there is a task executor preference return it
  • Return the default executor i.e. the value of the proposed var defaultExecutor: any TaskExecutor

Now for Task.sleep() I agree with @al45tair's previous proposal that we should go through the list in the same order and pick the first that supports scheduling.

Yeah I think we can agree to that -- it works out because we're not saying this is what we're "isolated to" but just "running on" and it's not a serial one, so I don't think people will have expectations of this being != with another actor's "running on" executor, since both may be on the default executor.

That's viable I think, unclear if that's the current implementation, it sounded like it would return nil today. But I think this would be ok to return the default one like this :+1:

1 Like

OK, I've updated the PR again following some of the comments above. I'm also removing EventableExecutor for now. I think it's the right direction, but I'm not entirely happy with the API and for scheduling reasons I think it's best to defer it to a separate proposal.

2 Likes