grpc / grpc-swift

The Swift language implementation of gRPC.
Apache License 2.0
2.02k stars 419 forks source link

unavailable: "channel isn't ready" #2080

Open piotrkowalczuk opened 6 days ago

piotrkowalczuk commented 6 days ago

Describe the bug

I'm new to Swift so I'm aware that it might be something on my end. Nevertheless, I will try my luck and report it as bug.

I have a very simple setup where I make an attempt to make a call stright from main struct of iOS app. Without luck, I'm getting unavailable: "channel isn't ready".

To reproduce

Steps to reproduce the bug you've found:

  1. Update system to macos:15
  2. Install latest Xcode:16.0
  3. Create multi platform project
  4. Add snippet from bellow to @main struct constructor.
  5. Run and hope your server will recognize request.

Expected behaviour

Ideally the request should succeed. In bare minimum I would like to see some logs from the transport layer on the server side.

Additional information

Server

The Go server that runs on my localhost has various debug modes enabled.

GRPC_TRACE=all
GRPC_VERBOSITY=DEBUG
GODEBUG=http2debug=1
GRPC_GO_LOG_VERBOSITY_LEVEL=99
GRPC_GO_LOG_SEVERITY_LEVEL=info

Code

Task {
    try await withThrowingDiscardingTaskGroup { group in
        let client = GRPCClient(
            transport: try .http2NIOPosix(
                target: .ipv4(host: "127.0.0.1", port: 8081),
                config: .defaults(transportSecurity: .plaintext)
            )
        )

        group.addTask {
            try await client.run()
        }

        defer {
            client.beginGracefulShutdown()
        }

        let auth =
            Authserv_V1_AuthService_Client(
                wrapping: client)

        do {
            let res = try await auth.obtainRefreshToken(
                .with {
                    $0.userID = UUID().uuidString
                })
            print(res)
        }catch {
            print("error: \(error)")
        }
    }
}

Changing to NIOTS do not help, print("2") is never reached:

let transport = try HTTP2ClientTransport.TransportServices(
    target: .ipv4(host: "127.0.0.1", port: 8081),
    config: .defaults(transportSecurity: .plaintext)
)
print("1")
try await transport.connect()
print("2")

Dependencies

image

Settings

image

protoc-gen-grpc-swift

protoc-gen-grpc-swift --version
protoc-gen-grpc-swift 1.0.0-development

installed from source from https://github.com/grpc/grpc-swift-protobuf that depends on grpc-swift:2.0.0-alpha.1.

glbrntt commented 5 days ago

Hi Piotr, thanks for filing this and trying out the alpha release!

We are certainly aware of some bugs but it's a bit surprising that this simple setup doesn't work...

Describe the bug

I'm new to Swift so I'm aware that it might be something on my end. Nevertheless, I will try my luck and report it as bug.

I have a very simple setup where I make an attempt to make a call stright from main struct of iOS app. Without luck, I'm getting unavailable: "channel isn't ready".

To clarify, is the server also running within your iOS app? I'd like to understand the setup better so I can reproduce it locally. As you noted there are no logs here: there are two logging systems in Swift (swift-log and OSLog), so we're currently evaluating how best to handle observability, which obviously makes things harder to debug at the moment.

GRPC_TRACE=all
GRPC_VERBOSITY=DEBUG
GODEBUG=http2debug=1
GRPC_GO_LOG_VERBOSITY_LEVEL=99
GRPC_GO_LOG_SEVERITY_LEVEL=info

For what it's worth: none of these env variables will be used.

Changing to NIOTS do not help, print("2") is never reached:

let transport = try HTTP2ClientTransport.TransportServices(
    target: .ipv4(host: "127.0.0.1", port: 8081),
    config: .defaults(transportSecurity: .plaintext)
)
print("1")
try await transport.connect()
print("2")

That's expected: connect() is long running, it won't return until the transport has been shutdown (or the task has been cancelled). You snippet above using the task group and client is the correct way to run a client and its transport.

piotrkowalczuk commented 5 days ago

To clarify, is the server also running within your iOS app? I'd like to understand the setup better so I can reproduce it locally. As you noted there are no logs here: there are two logging systems in Swift (swift-log and OSLog), so we're currently evaluating how best to handle observability, which obviously makes things harder to debug at the moment.

The server is running on my localhost. It's a Go app.

A minimal setup that allows me to reproduce the issue:

debug/Sources/main.swift

import NIOTransportServices
import GRPCNIOTransportHTTP2
import MY_PACKGE
import Foundation

try await withThrowingDiscardingTaskGroup { group in
    let client = GRPCClient(
        transport: try .http2NIOPosix(
            target: .ipv4(host: "127.0.0.1", port: 8081),
            config: .defaults(transportSecurity: .plaintext)
        )
    )

    group.addTask {
        try await client.run()
    }

    defer {
        client.beginGracefulShutdown()
    }

    let auth =
        MY_CLIENT(
            wrapping: client)

    do {
        let res = try await auth.MY_METHOD(
            .with {
                $0.userID = UUID().uuidString
            })
        print(res)
    } catch {
        print("error: \(error)")
    }
}

debug/Package.swift

// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "debug",
    platforms: [
        .macOS(.v15),
    ],
    products: [
        .executable(name: "debug", targets: ["debug"])
    ],
    dependencies: [
        .package(path: "../my_package"),
    ],
targets: [
        .executableTarget(
            name: "debug",
            dependencies: [
                .product(name: "MY_PACKAGE", package: "my_package"),
            ]
        ),
    ]
)

my_package/Package.swift

        .package(url: "https://github.com/grpc/grpc-swift-nio-transport.git", from: "1.0.0-alpha.1"),
        .package(url: "https://github.com/grpc/grpc-swift.git", from: "2.0.0-alpha.1"),
        .package(url: "https://github.com/grpc/grpc-swift-protobuf.git", from: "1.0.0-alpha.1"),
        .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.27.2"), 

and it gives

swift run debug
Building for debugging...
[1/1] Write swift-version--58304C5D6DBC2206.txt
Build of product 'debug' complete! (2.61s)
error: unavailable: "channel isn't ready"
glbrntt commented 5 days ago

Okay, I just want to confirm your current setup because I think it's changed, you now have a Swift client running on the same machine as a Go server?

piotrkowalczuk commented 5 days ago

Yes the same machine.

glbrntt commented 5 days ago

I setup a Go server and used a Swift client and wasn't able to reproduce this.

However, what I did notice was that the Go server in the gRPC Go examples bound to an IPv6 address ("::1") – is your Go server also bound to an IPv6 address?

jcalderita commented 3 days ago

I have the same problem in my iOS project with version 2.0.0-alpha.1

func gRPCEvents() async throws {
    let client = GRPCClient(
        transport: try .http2NIOPosix(
            target: .ipv4(host: "localhost", port: 6162),
            config: .defaults(transportSecurity: .plaintext)
        )
    )

    try await withThrowingDiscardingTaskGroup { group in
        group.addTask {
            try await client.run()
        }

        defer {
            client.beginGracefulShutdown()
        }

        let service = EventsService_Client(wrapping: client)

        let events = try await service
            .allEvents(Google_Protobuf_Empty())

        print(events.events.count)
    }
}

I have my own server on my machine "localhost" with vapor and 1.23.1 grpc version.

piotrkowalczuk commented 2 days ago

However, what I did notice was that the Go server in the gRPC Go examples bound to an IPv6 address ("::1") – is your Go server also bound to an IPv6 address?

Changing the listener to bind to ip4 explicitly did not help.

glbrntt commented 1 day ago

I have the same problem in my iOS project with version 2.0.0-alpha.1

func gRPCEvents() async throws {
    let client = GRPCClient(
        transport: try .http2NIOPosix(
            target: .ipv4(host: "localhost", port: 6162),
            config: .defaults(transportSecurity: .plaintext)
        )
    )

    try await withThrowingDiscardingTaskGroup { group in
        group.addTask {
            try await client.run()
        }

        defer {
            client.beginGracefulShutdown()
        }

        let service = EventsService_Client(wrapping: client)

        let events = try await service
            .allEvents(Google_Protobuf_Empty())

        print(events.events.count)
    }
}

I have my own server on my machine "localhost" with vapor and 1.23.1 grpc version.

@jcalderita "localhost" isn't an IPv4 address, so this should fail.

If you change .ipv4(host: "localhost", port: 6162) to .dns(host: "localhost", port: 6162) the DNS name resolver will be used and should resolve "localhost" to "127.0.0.1" and "::1" and try connecting to both.

glbrntt commented 1 day ago

However, what I did notice was that the Go server in the gRPC Go examples bound to an IPv6 address ("::1") – is your Go server also bound to an IPv6 address?

Changing the listener to bind to ip4 explicitly did not help.

Could you provide a complete minimal example with a Go server and a Swift client? Ideally zipped up so I can download it and just run the client and server.

jcalderita commented 1 day ago

I have the same problem in my iOS project with version 2.0.0-alpha.1

func gRPCEvents() async throws {
    let client = GRPCClient(
        transport: try .http2NIOPosix(
            target: .ipv4(host: "localhost", port: 6162),
            config: .defaults(transportSecurity: .plaintext)
        )
    )

    try await withThrowingDiscardingTaskGroup { group in
        group.addTask {
            try await client.run()
        }

        defer {
            client.beginGracefulShutdown()
        }

        let service = EventsService_Client(wrapping: client)

        let events = try await service
            .allEvents(Google_Protobuf_Empty())

        print(events.events.count)
    }
}

I have my own server on my machine "localhost" with vapor and 1.23.1 grpc version.

@jcalderita "localhost" isn't an IPv4 address, so this should fail.

If you change .ipv4(host: "localhost", port: 6162) to .dns(host: "localhost", port: 6162) the DNS name resolver will be used and should resolve "localhost" to "127.0.0.1" and "::1" and try connecting to both.

It works!!!! Thank you!!!