I used quic-go to implement a function to send messages to the server and receive data, which I turned into an iOS library with the gomobile bind command.

func Fetch(message string) ([]byte) {
    fmt.Printf("Client: Sending '%s'\n", message)

    stream, err := conn.OpenStreamSync(context.Background())  // conn is of type quic.Connection.
    if err != nil {
        fmt.Printf("go_Client: Error opening stream: %s\n", err)
    }

    fmt.Printf("go_client: Sending '%s'\n", message)
    _, err = stream.Write([]byte(message))
    if err != nil {
        fmt.Printf("go_client: Error writing to stream: %s\n", err)
    }

    bufferSize := 1024
    var receivedData []byte 

    for {
        buf := make([]byte, bufferSize)
        n, err := stream.Read(buf)
        if err != nil {
            if err == io.EOF {
                break 
            }
            fmt.Printf("Client: Error reading from stream: %s\n", err)
            break
        }
        receivedData = append(receivedData, buf[:n]...) 
    }

    stream.Close()

    return receivedData
}

This function is called in swift as follows.

func fetchObjectFromServer(_ message: String, completion: @escaping (Data?) -> Void) {
    Task.detached{
        let data = QuicFetch(message)
        if let unwrappedData = data {
            completion(unwrappedData)
        } else {
            completion(nil)
        }
    }
}

I am using "Task.detached" to make asynchronous calls, but when the QuicFetch function starts processing, the UI stops.

I experimentally modified the code of golang's Fetch function as follows and used it in the same way in swift.

func Fetch() {
    sleepTime := 10 * time.Second
    fmt.Printf("start sleep\n")
    time.Sleep(sleepTime)
    fmt.Printf("end sleep\n")
}

This function was properly processed in the background and the UI did not stop.

I would like to know why the UI stops when trying to process in the background only when communicating with quic-go.

Comment From: mknyszek

If I were to guess, the calls you're making in Fetch end up doing syscalls on iOS that the Go runtime needs to block the thread. If you end up on the UI thread (usually the main thread, which I suppose might be where you're calling into Go from Swift) then you'll see UI stalls. This doesn't fully explain why time.Sleep works though, since once you call into Go, the goroutine created for the thread calling into Go is bound to that thread. That being said, time.Sleep does not in general block a thread, and the goroutine just ends up in some timer data structures, to be executed later.

Do you have more details on how Go is being called from Swift? I'm not personally familiar with x/mobile, so I don't know what exactly the intermediate code is doing. I assume the call is going through cgo, but other than that, it would be good to have more details from someone familiar.

Comment From: mknyszek

CC @golang/runtime and maybe @hyangah for x/mobile knowledge?

Comment From: SuzukiTakuto

The XCFramework is created by executing the command "gomobile bind -target ios". This can be loaded in xcode to call Go from swift.

Comment From: SuzukiTakuto

@hyangah , @crawshaw , do you have any solutions? I would like you to check this issue if you don't mind.

Comment From: rohansingh

I was seeing something similar, but fixed it by moving the Go call out of the UI thread.

@SuzukiTakuto In your code above, it looks like you are using Task.detached. I'm far from a Swift expert, but my understanding is that a "task" is part of Swift's lightweight concurrency model. It does not actually spawn a new thread.

Go doesn't know anything about Swift tasks and cannot yield to other Swift tasks. I'm guessing that's why it blocks the thread entirely. If you spawn an actual new thread, that should solve the issue.

Comment From: pixelspark

@SuzukiTakuto did you ever find a solution to this? I think I am seeing something similar - hangs on iOS where the main thread is blocked. The stack trace is not very informative (the CGo stuff switches stacks it seems? No way to find out what is calling the pthread_cond_wait on the main thread):

Image

Image