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

unavailable: "channel isn't ready" #2080

Open
piotrkowalczuk opened this issue Oct 2, 2024 · 11 comments
Open

unavailable: "channel isn't ready" #2080

piotrkowalczuk opened this issue Oct 2, 2024 · 11 comments
Labels

Comments

@piotrkowalczuk
Copy link

piotrkowalczuk commented Oct 2, 2024

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".

  • what works
    • naked http using URLRequest
    • grpc-swift:1.23.1
    • curl
    • grpcurl
  • what did not work
    • grpc-swift:2.0.0-alpha.1 (both posix and niots)

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
Copy link
Collaborator

glbrntt commented Oct 3, 2024

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
Copy link
Author

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
Copy link
Collaborator

glbrntt commented Oct 3, 2024

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
Copy link
Author

piotrkowalczuk commented Oct 3, 2024

Yes the same machine.

@glbrntt
Copy link
Collaborator

glbrntt commented Oct 3, 2024

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
Copy link

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
Copy link
Author

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
Copy link
Collaborator

glbrntt commented Oct 7, 2024

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
Copy link
Collaborator

glbrntt commented Oct 7, 2024

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
Copy link

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!!!

@sergiusromanof
Copy link

Issue

unavailable: "channel isn't ready"

Solution

I encountered the same problem today. If anyone has the same problem, you may have forgotten to add the keys to the Info.plist file for mscOS/iOS.

Solution → https://stackoverflow.com/questions/63597760/not-allowed-to-make-request-to-local-host-swift

MacOS

com.apple.security.network.server - Boolean True
com.apple.security.network.client - Boolean True

iOS

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/> 
</dict>

or

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    <key>NSExceptionDomains</key>
    <dict>
        <key>yourdomain.com</key>
        <dict>
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants