apple / swift-distributed-actors

Peer-to-peer cluster implementation for Swift Distributed Actors
https://apple.github.io/swift-distributed-actors/
Apache License 2.0
587 stars 55 forks source link

+tracing Instrument Invocation handlers using swift-distributed-tracing #1085

Open ktoso opened 1 year ago

ktoso commented 1 year ago

Fully functional, basic, swift-distributed-tracing integration... in just 20 lines of actual changed code (!): 80dd5a5 (#1085)

Further, this includes a complete sample app that illustrates this.

Screenshot 2022-11-07 at 15 12 10

Resolves #847 Instrument system with Swift Distributed Tracing Resolves #476 Context propagation mechanism for actors: Baggage


The Samples project contains a full sample that showcases this end-to-end. I'll write some docs for it later, but also intend to include this sample in tracing documentation perhaps?

Short version:

We automatically get spans for distributed calls etc 🥳

distributed actor Cook { 
    // ... 
    distributed func makeDinner() async throws -> Meal {
        try await InstrumentationSystem.tracer.withSpan(#function) { _ in
            await noisySleep(for: .milliseconds(200))

            log.notice("Cooking dinner, but we need [2] vegetable choppers...! Suspend waiting for nodes to join.")
            let (first, second) = try await getChoppers()
            async let veggies = try chopVegetables(firstChopper: first, secondChopper: second)
            async let meat = marinateMeat()
            async let oven = preheatOven(temperature: 350)
            // ...
            return try await cook(veggies, meat, oven)
        }
    }

func chopVegetables(firstChopper: some Chopping,
                    secondChopper: some Chopping) async throws -> [Vegetable]
{
    try await InstrumentationSystem.tracer.withSpan("chopVegetables") { _ in
        // Chop the vegetables...!
        //
        // However, since chopping is a very difficult operation,
        // one chopping task can be performed at the same time on a single service!
        // (Imagine that... we cannot parallelize these two tasks, and need to involve another service).
        async let carrot = try firstChopper.chop(.carrot(chopped: false))
        async let potato = try secondChopper.chop(.potato(chopped: false))
        return try await [carrot, potato]
    }
}

and

protocol Chopping {
    func chop(_ vegetable: Vegetable) async throws -> Vegetable
}

distributed actor VegetableChopper: Chopping {
    @ActorID.Metadata(\.receptionID)
    var receptionID: String

    init(actorSystem: ActorSystem) async {
        self.actorSystem = actorSystem

        self.receptionID = "*" // default key for "all of this type"
        await actorSystem.receptionist.checkIn(self)
    }

    distributed func chop(_ vegetable: Vegetable) async throws -> Vegetable {
        await noisySleep(for: .seconds(5))

        return vegetable.asChopped
    }
}