Skip to content
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

Surprising actor deinitialization #77365

Open
ABridoux opened this issue Nov 4, 2024 · 1 comment
Open

Surprising actor deinitialization #77365

ABridoux opened this issue Nov 4, 2024 · 1 comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. concurrency Feature: umbrella label for concurrency language features

Comments

@ABridoux
Copy link

ABridoux commented Nov 4, 2024

Description

I have been using Xcode 16 for a few weeks now and noticed a behavior regarding actor deinitialization that surprises me.

In our app, we use a few actors that are stored as constant static properties and thus live as long as the app is not killed. Compiling with Xcode 16, we have noticed that capturing the actor as unowned lead to a crash in several cases, like a Task initialization (even though for a Task, capturing self as unowned is unneeded most of the time).

Reproduction

To exemplify this, here's a block of code for a command-line tool.

// MARK: - Static

enum Storage {
    static let shared = SharedActor()
}

// MARK: - SharedActor

actor SharedActor {

    // MARK: Properties

    var task: Task<Void, Error>?
    var value = 1

    // MARK: Init

    deinit {
        print ("DEINIT")
    }

    // MARK: Test

    func test() {
        task = Task { [unowned self] in
            print ("Task execution")
            print (value)
        }
    }
}

// MARK: - Run

await Storage.shared.test()
try await Task.sleep(for: .seconds(1))
print("Bye-Bye")

Here's what is printed in the console.

Task execution
1
DEINIT
Bye-Bye

Expected behavior

The deinit block should not be called, as the actor is stored as a static constant property and thus should live as long as the application or command-line tool lives.

Environment

swift-driver version: 1.115 Apple Swift version 6.0.2 (swiftlang-6.0.2.1.2 clang-1600.0.26.4)
Target: arm64-apple-macosx15.0

Note

I never had the issue before using Xcode 16.

Additional information

The behavior differs when a class is used instead of an actor, which lead me to think this might be a bug.
The issue is mentioned in the Swift Forums.

@ABridoux ABridoux added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Nov 4, 2024
@ktoso ktoso added concurrency Feature: umbrella label for concurrency language features and removed triage needed This issue needs more specific labels labels Nov 4, 2024
@jamieQ
Copy link
Contributor

jamieQ commented Nov 7, 2024

here's a slightly reduced reproduction that can be run as a command line tool:

@main
struct S {
  static func main() async {
    await SharedActor.shared.test()
  }

  actor SharedActor {
    static let shared = SharedActor()

    deinit {
      print("DEINIT")
    }

    func test() {
      Task { [unowned self] in
        _ = self
      }
    }
  }
}

facts of note:

  1. self must actually be used in the Task closure or the issue appears to go away
  2. the specific capture list format [unowned self] also appears needed. aliasing self in various ways and capturing the alias seems to prevent the issue from arising.
  3. i was wondering if the changes in [Concurrency] Retain the actor around the CAS in enqueue() when necessary. #77031 may have had some effect on this behavior, but the latest nightly compiler snapshots appear to now crash on this example (2024-10-28, 6.0.2 snapshot). reported here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. concurrency Feature: umbrella label for concurrency language features
Projects
None yet
Development

No branches or pull requests

3 participants