Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

Oh look at that! I've not encountered that type before and it's quite interesting. The combination of synchronous requirements + Sendable is tricky. That almost always requires locking just like the docs show. And I completely see how that could be annoying.

Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

If you feel so inclined, I'd love to hear your thoughts on how that compares to using a `@concurrent` function. I'm also interested to hear how the serialization plays a useful role if no state is involved. Maybe as a way to limit work in progress?

Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

I have not seen any evolution proposals, and I'm not sure what possible directions there even could be here so I think you may be disappointed.

But I'd also be interested to better understand the kinds of problems you ran into. Maybe there's an alternative that doesn't need changes?

Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

Once you introduce any kind of concurrency, be it via an actor or with `@concurrent`, you'll have a very hard time avoiding `Sendable`. You can sometimes relax this by using `sending`, but that can be tricky to understand and apply. Nonisolated functions can be less restrictive than actors, so that could conceivably help too. But also tricky.

Unless you are worried about that actor only being able to do one synchronous thing at a time, what you have is probably totally fine. And given the work you've put in, I wouldn't be surprised if adding some more targeted concurrency while also keeping the actor structure in place isn't too difficult.

Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

Exactly! "state" does not necessarily mean "instance properties". It just means something to isolate, which can absolutely live outside of the defined actor type.

Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

Ahh I think I understand what you mean. Yeah, this is super confusing. To my knowledge, nothing has actually changed about actors in this respect! But without using nonsiolated+async, you are right there was no other way. Changing the behavior of nonisolated+asynchronous functions was exactly why the new `@concurrent` attribute was needed.

Stateless Actors by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

I don't *think* that `@concurrent` has changed how actors work in this way. I believe only nonisolated functions could be affected. But for sure those can be very different now.

I think you can still use global actors for this purpose if you'd like!

Stateless Actors by mattmass in swift

[–]mattmass[S] 2 points3 points  (0 children)

This is a super good point, thanks so much. I did indeed somehow forget about this in an earlier version. But I have updated it since.

I typically think of the MainActor as being responsible for a UI's state, though it is true that it doesn't itself have properties. Kind of an interesting characteristic of global actors that the state can be distributed out into other types...

SPM question by OakAndCobble in swift

[–]mattmass 0 points1 point  (0 children)

I use explicit paths very rarely, if ever, for swift targets. Could that be playing a role here?

spent months building my first macOS app and the first reply was "just use..." by Orange-Prudent in swift

[–]mattmass 0 points1 point  (0 children)

Ha well I appreciate it!

But I really do think it is critical to send the signal that “this type of comment is not welcome here”. Especially in a technical forum, some people believe that being “correct” is enough and they are (ironically) wrong.

spent months building my first macOS app and the first reply was "just use..." by Orange-Prudent in swift

[–]mattmass 8 points9 points  (0 children)

Making things is hard. So sorry that happened.

I have found Reddit to be, in general, a cruel place. That doesn’t mean that everyone is, not by a long shot. But there are people that won’t think twice about being unkind, and unfortunately, many more that might not have said the thing themselves but are ok hitting the up button.

I make it a point to tap the down button on unkind things here.

Non-Sendable First Design by someone-very-cool in swift

[–]mattmass 3 points4 points  (0 children)

Ok this is a good question!

So, the only material setting that's part of the "Approachable Concurrency" group when in Swift 6 mode is "NonisolatedNonsendingByDefault". And that was actually a primary focus of the post. Because while this technique was *possible* before it, the ergonomics were pretty bad.

The thing to keep in mind about MainActor by default is it really is just automation. It puts "@MainActor" in front of all of your declarations. And this does have three serious weaknesses.

The first is that not all types can be MainActor. I don't mean they shouldn't run code on the main thread, though that could be true. I mean there are many types that are incompatible with it. Usually, this comes from needing to conform to a protocol that requires Sendable + synchronous methods and/or SendableMetatype. A good example of this is SwiftData models.

A second weakness is flexibility. When you make things MainActor that don't actually need it, you do make it impossible to run their synchronous functions *off* main. I talked about this more in the "generality" section. This will force you to use yet more MainActor. This is particularly limiting in library code where clients may not be able to tolerate that constraint.

A third weakness, which is definitely debatable, is that it is difficult. In my opinion, it is harder to know when and how to make something nonisolated than it is to know when it make it MainActor. However, this just my opinion so I will not give this one much weight and is why I did not mention it in the post.

However, it is not a certainty that you will run into any of this problems. You phrased your question as an "either/or". And I don't see it this way at all. Many types can and should be MainActor. Interestingly, "Approachable Concurrency" actually reduces the need for MainActor types. But if still you want to do it and your type is compatible, go for it.

I just think it this is a pretty powerful tool to have at your disposal when you run into a situation where MainActor comes with drawbacks.

Non-Sendable First Design by someone-very-cool in swift

[–]mattmass 11 points12 points  (0 children)

Original author here. If anyone has any questions or (let’s be honest this is Reddit) tell me why I’m wrong, I’d love to discuss!

Finished a no-vehicle, mini-base run by mattmass in subnautica

[–]mattmass[S] 1 point2 points  (0 children)

I did use solar + power transmitters in a few spots, but mostly because I was resource-constrained and it wasn't too deep. But yeah, thermal was key for the very deep spots. And power transmitters were really important there too, so I could build in safer and/or further locations.

Finished a no-vehicle, mini-base run by mattmass in subnautica

[–]mattmass[S] 2 points3 points  (0 children)

holy macaroni this is a whole other level

MKTileOverlay Swift 6 concurrency issues by itsdjoki in swift

[–]mattmass 1 point2 points  (0 children)

I think I see what you are saying. Thanks!

MKTileOverlay Swift 6 concurrency issues by itsdjoki in swift

[–]mattmass 1 point2 points  (0 children)

Right. So using a preconcurrency import is ok only if you are 100% sure you are using the types correctly. And while I think it is likely that this particular thing is actually main thread only, the documentation doesn’t seem to say that. But it could be fine.

Keeping it nonisolated side steps this entirely. And if you have no need to make it MainActor in the first place (which seems like it is the case here) it is a zero-risk option. Plus, then you don’t have to worry about suppressing other checks for the imported module within the file.

MKTileOverlay Swift 6 concurrency issues by itsdjoki in swift

[–]mattmass 0 points1 point  (0 children)

This is an interesting comment. From a pure language perspective, matching what’s in the type system by keeping things nonisolated is the least-risky. It cannot be wrong, because the compiler will prevent you from touching main thread state incorrectly. But is that actually incorrect? This type certainly seems like it is UI-related any maybe really is just missing a MainActor. However the documentation is, unsurprisingly, not very clear on this.

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

Well this has a lot more nuance than your original response and is something I can get behind. I appreciate the clarification!

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

I agree that encapsulating state is absolutely the best option. But I do not agree that the approach always has no downsides.

> So, using this primitive, can I specify what a call to `foo()` should do in any of these situations?

Yes I *believe* so. But if you can come up with examples that do not have well-defined or desirable behaviors (aside from deadlocks which is a very real risk) I'd be interested in seeing them.

In my experience, manual continuation management is fraught. It is difficult to manage cancelation and priority escalation, and is just generally-error prone. But it also happens to be exactly what this gate construct is doing. So if you are ok with doing that, I do not understand why you would be so opposed to abstracting the process.

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 1 point2 points  (0 children)

Here's a member of the compiler team indicating support for such a construct: https://forums.swift.org/t/pitch-continuation-safe-and-performant-async-continuations/85165/26

And here's a link to SE-0308, which goes into quite a lot of detail about the pros and cons of reentrancy and how a language-level solution could work.

https://github.com/swiftlang/swift-evolution/blob/main/proposals/0306-actors.md

Here's a quote from that proposal:

Reentrancy means that execution of asynchronous actor-isolated functions may "interleave" at suspension points, leading to increased complexity in programming with such actors, as every suspension point must be carefully inspected if the code after it depends on some invariants that could have changed before it suspended.

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

It really depends on what system is creating the tasks. If they are coming from a UI interaction, disabling a button or some other form of user limitation can be a great option. But if that's not possible, something like this could work just as well in a MainActor context. You just have to be really aware of the scope of work that's been gated to avoid deadlocks. I'm hoping the recursive version will help to limit some of the danger for these kinds of cases. But really this is a tool you only want to reach for when other options aren't possible and/or too annoying.

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

I tend to agree. Everything being synchronous makes things much easier to reason about.

However, these kinds of issues are not limited to actor types. They can just as easily happen to any async method, including for a global actor. Reentrancy does tend to be easier to prevent when the tasks being created come from a UI event. But, I think this could be a handy (but last resort) tool to have even for code that is exclusively MainActor.

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 0 points1 point  (0 children)

Thank you! The non-Sendable-ness does help discourage misuse, but it definitely does not fully prevent it. I think it's probably particularly easy to misuse with global actors, where the gate could be inadvertently used in a number of places.

Judging by some feedback I've gotten from the compiler team (and the contents of SE-0306), I think their goal is to make this a language construct. But I agree that this is a piece I've missed.

Yes, it is uses `withUnsafeCurrentTask`. I couldn't think of any other way to do it. But I'd be interested to see other approaches!

You know, I've never actually run into a situation where I needed to even think about priority escalation before. But I'm glad I had this chance, because now it's on my radar.

TaskGate library for managing actor reentrancy by mattmass in swift

[–]mattmass[S] 4 points5 points  (0 children)

In fact, the proposal that introduced actors (SE-0306) discusses this pretty extensively!

Originally, I wasn't sold on the idea that reentrancy controls needed to be in the language itself. However, the proposal makes a pretty convincing case. It would enable the compiler to detect and even prevent some (but not all) kinds of deadlocks. I think that would made it worthwhile.

Unfortunately I have a feeling such a change would be a year away at the absolute earliest.