Swift static sdk with async networking

Hi!

I'm moving a tool I made some time ago to swift6+swift static sdk. I am willing to move to async/await for my URLSession-based operations, and if I use the plain swift6 toolchain it works fine, but when I try with the swift-linux-musl sdk, I have found two issues already

  • print with no terminator doesn't show,
  • URLSession.shared.data(for:) throws an error Error Domain=NSURLErrorDomain Code=-1001 "(null)"

so I was wondering if the swift static linux sdk is supposed to support async operations or if I have to go back to completions for it.

Then again, it works fine without the static SDK

here's a simplified program that shows the same behavior:

import Foundation
@preconcurrency import FoundationNetworking
#if canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#endif

@main
struct Main {
    static func main() async {
        let url = URL(string:"https://forums.swift.org/c/general-announce/24")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        print(request)
        do {
            let (data, response) = try await URLSession.shared.data(for: request)
            print(data)
            print(response)
        } catch {
            print(error)
        }

    }
}
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "test123",
    platforms: [
        .macOS(.v10_15)
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .executableTarget(
            name: "test123"),
    ]
)

I'm building on ubuntu-22.04 and macOS 14.5 with the same results

I'm unable to reproduce your example, either with the latest 6.0 or 6.1 SDKs:

> ../swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a-ubi9/usr/bin/swift build --swift-sdk swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1 --triple x86_64-swift-linux-musl
warning: multiple Swift SDKs match ID `swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1` and host triple x86_64-unknown-linux-gnu, selected one at /home/finagolfin/.swiftpm/swift-sdks/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1.artifactbundle/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1/swift-linux-musl
Building for debugging...
clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
[8/8] Linking test123
Build complete! (6.35s)
> file .build/debug/test123
.build/debug/test123: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
> .build/debug/test123
https://forums.swift.org/c/general-announce/24
22208 bytes
<HTTPURLResponse 0x00007f6d340a41f0> { URL: https://forums.swift.org/c/general-announce/24 }{ status: 404, headers {
   "Cdck-Proxy-Id" = app-router-tiehunter05.sea2, app-balancer-tieinterceptor1c.sea2;
   "Content-Encoding" = gzip;
   "Content-Security-Policy" = upgrade-insecure-requests; base-uri 'self'; object-src 'none'; script-src 'nonce-00U9vMVIQyM3BGifupKDcEluR' 'strict-dynamic'; frame-ancestors 'self'; manifest-src 'self';
   "Content-Type" = "text/html; charset=utf-8";
   "Date" = Sun, 21 Jul 2024 11:47:17 GMT;
   "Server" = nginx;
   "Strict-Transport-Security" = max-age=31536000;
   "Transfer-Encoding" = chunked;
   "Vary" = Accept-Encoding, Accept;
   "x-request-id" = 4ac30317-6261-4ab3-9495-4123c70f0dba;
} }

Can you give more info on how your build commands differ?

Sure!
I was following the instructions at the static linux guide

so I was using "--swift-sdk x86_64-swift-linux-musl"

but switching to your model of command line (sdk full name as downloaded, tripe specified with --triple instead) it works as expected (got a static executable and my original case involving a REST operation worked this time.)

Thanks!

OK, the only reason I had to specify the full SDK name is because I had multiple SDKs installed, which most won't. After deleting them all except for the matching SDK version, I'm unable to reproduce with the commands you linked either:

> ../swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a-ubi9/usr/bin/swift sdk list
swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1
> ../swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a-ubi9/usr/bin/swift build --swift-sdk x86_64-swift-linux-musl
Building for debugging...
clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
[8/8] Linking test123
Build complete! (6.17s)
> file .build/debug/test123
.build/debug/test123: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
> .build/debug/test123
https://forums.swift.org/c/general-announce/24
22349 bytes
<HTTPURLResponse 0x00007f73599171f0> { URL: https://forums.swift.org/c/general-announce/24 }{ status: 404, headers {
   "Cdck-Proxy-Id" = app-router-tiehunter05.sea7, app-balancer-tieinterceptor1f.sea9;
   "Content-Encoding" = gzip;
   "Content-Security-Policy" = upgrade-insecure-requests; base-uri 'self'; object-src 'none'; script-src 'nonce-stmPDQ9SzOl38QdL6TtzDdxfj' 'strict-dynamic'; frame-ancestors 'self'; manifest-src 'self';
   "Content-Type" = "text/html; charset=utf-8";
   "Date" = Mon, 22 Jul 2024 07:19:15 GMT;
   "Server" = nginx;
   "Strict-Transport-Security" = max-age=31536000;
   "Transfer-Encoding" = chunked;
   "Vary" = Accept-Encoding, Accept;
   "x-request-id" = 15d1f8ad-f100-3004-bcf8-680f03346b61;
} }

Can you list the exact commands that caused your error? It would help the SDK author and maybe SwiftPM devs to fix any problems in the build process.

Hi!, here's my working environment

  • OS: Linux (Ubuntu 22.04 x86_64 running on top of WSL2)
  • swift-5.10 installed in /opt/swift
  • swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-16-a-ubuntu22.04 installed in /opt/swift6
  • PATH contains /opt/swift/usr/bin
  • ~/.swiftpm has swift-sdks/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1.artifactbundle, no other ones there.

now in my test project,

rm -rf .build
/opt/swift6/usr/bin/swift build --swift-sdk x86_64-swift-linux-musl
Building for debugging...
clang: warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]
[8/8] Linking test123
Build complete! (6.83s)

ldd .build/debug/test123

	not a dynamic executable

./.build/debug/test123
https://forums.swift.org/c/general-announce/24
Error Domain=NSURLErrorDomain Code=-1001 "(null)"

so it looks like in my case it required the full SDK name even if I had only one installed.

I don't know if this is related to the issue with x86_64-swift-linux-musl on ubuntu, The same program builds but never connects to the test URL on Ubuntu 22.04 aarch64 running on a raspberrypi4: both ways of specifying the swift sdk produce a static executable that will fail to connect to the test url.

There I installed only swift6. Also gcc, CMake and then the prereq list for ubuntu 20.04

apt-get install \
          binutils \
          git \
          gnupg2 \
          libc6-dev \
          libcurl4 \
          libedit2 \
          libgcc-9-dev \
          libpython2.7 \
          libsqlite3-0 \
          libstdc++-9-dev \
          libxml2 \
          libz3-dev \
          pkg-config \
          tzdata \
          uuid-dev \
          zlib1g-dev

(there was no list for 22.04)

I tried executing the (static) program again (raspberry pi), and again, like I was waiting for it to magically work, and at least in the raspberry pi that seems to be the case: sometimes it works, sometimes it doesn't.

Then again, if I don't use the static linux sdk, the executable always connects successfully.

If it is simply flaky, could it be a networking issue on your end?

I suppose Musl might have some flakier networking functions that are causing this.

@al45tair, wdyt?

The networking functions are largely provided by the kernel rather than Musl, and I think we'd know if Musl had flaky networking.

I'm honestly not sure why you're seeing it not work, or why you're seeing flaky behaviour. The one gotcha I do know about with it is that it does need to be able to locate the root certificates for TLS to work (this has to be done specially because libcurl doesn't know how to do it at runtime, but the statically linked program could find itself on any kind of Linux and there are a number of different locations where we might find them), but that should work for Ubuntu already.

We should check what

Error Domain=NSURLErrorDomain Code=-1001 "(null)"

means exactly.

-1001 is NSURLErrorTimedOut. If that's happening, that does suggest that you have some networking problem or other.

Hi, @al45tair

It'd be great if it was a weird networking issue on my side!... but what happens is I can't reproduce the issue if I don't involve the swift static sdk.

If I stick now to the raspberry pi with Ubuntu 22.04-aarch64,

  • when I build without the static sdk, the executable always produces the expected results (connects and displays some info on the request results)
  • when I build with swift build --swift-sdk $(arch)-swift-linux-musl, it builds OK always, but the results of running the executable are it fails/works as expected in what it looks like a random sequence
  • when I build with swift build --swift-sdk $(swift sdk list) --triple $(arch)-swift-linux-musl the behavior is the same as with the previous case

(after retrying the same (always removing .build first, before building) the x86_64 ubuntu 22.04 environment has the same behavior.)

Again, possibly unrelated, but it only seems to happen in the raspberry pi 4, swift build with the fully specified sdk missed the aarch64 _Concurrency module. It happens sometimes, too. Not always.

rm -rf .build; swift build --swift-sdk $(swift sdk list) --triple $(arch)-swift-linux-musl
warning: multiple Swift SDKs match ID `swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1` and host triple aarch64-unknown-linux-gnu, selected one at /home/marcelo/.swiftpm/swift-sdks/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1.artifactbundle/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1/swift-linux-musl
Building for debugging...
error: emit-module command failed with exit code 1 (use -v to see invocation)
<unknown>:0: error: could not find module '_Concurrency' for target 'aarch64-swift-linux-musl'; found: x86_64-swift-linux-musl, at: /home/marcelo/.swiftpm/swift-sdks/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1.artifactbundle/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1/swift-linux-musl/musl-1.2.5.sdk/x86_64/usr/lib/swift_static/linux-static/_Concurrency.swiftmodule
<unknown>:0: error: could not find module '_Concurrency' for target 'aarch64-swift-linux-musl'; found: x86_64-swift-linux-musl, at: /home/marcelo/.swiftpm/swift-sdks/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1.artifactbundle/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-07-02-a_static-linux-0.0.1/swift-linux-musl/musl-1.2.5.sdk/x86_64/usr/lib/swift_static/linux-static/_Concurrency.swiftmodule

I don't think --triple interacts well with --swift-sdk (@Max_Desiatov?), so I think I'd avoid that for now.

I wonder whether there is indeed some Musl-related issue here; I wonder if it's DNS related. I'd check your /etc/resolv.conf (Musl does implement the resolver; that isn't a kernel function, and there are behavioural differences from Glibc there — see https://wiki.musl-libc.org/functional-differences-from-glibc.html).

Here's resolv.conf from the raspberry pi 4, but again it works if I don't use the static sdk

# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8).
# Do not edit.
#
# This file might be symlinked as /etc/resolv.conf. If you're looking at
# /etc/resolv.conf and seeing this text, you have followed the symlink.
#
# This is a dynamic resolv.conf file for connecting local clients to the
# internal DNS stub resolver of systemd-resolved. This file lists all
# configured search domains.
#
# Run "resolvectl status" to see details about the uplink DNS servers
# currently in use.
#
# Third party programs should typically not access this file directly, but only
# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a
# different way, replace this symlink by a static file or a different symlink.
#
# See man:systemd-resolved.service(8) for details about the supported modes of
# operation for /etc/resolv.conf.

nameserver 127.0.0.53
options edns0 trust-ad
search .

note that resovectl status show it's using my internal network DNS server

and in the x86_64 (WSL2) environment resolv.conf contains

cat /etc/resolv.conf 
nameserver 1.1.1.1
nameserver 208.67.222.222
nameserver 208.67.220.220

which works w/o the static sdk

I might be inclined to try writing a small test program that just attempts to resolve the hostname you're trying to talk to, and see if that is somehow unreliable when using the static SDK. That'll at least narrow the problem down a bit. If that works reliably, then that isn't the problem.

You could also try adding

nameserver 8.8.8.8

to your /etc/resolv.conf and see if that makes things more reliable.

I'll try to work on that later today. Also, for if we've forgotten, the code uses async calls. I believe that may be in the root of the problem somehow. But you're right, URLSession.shared.data(for:) resolves the DNS entry first, so the timeout can be as early as that, depending on how it's implemented.

Ok, I'm not sure how to do this. I recall there's the C function gethostbyname(). Would it be ok just make the function that will call gethostbyname async?

The project with the original program is here (main)

I have updated the repository, added a new target w/ this only, and it doesn't fail when compiled w/ $(arch)-swift-linux-musl

import Foundation
#if os(Linux)
@preconcurrency import FoundationNetworking
#if canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#endif
#endif

@main
struct Main {
    static func main() async {
        await run()
    }
    
    static func run() async {
        let hostname = gethostbyname("swift.org")
        guard let hostname else {
            print("Can't resolve hostname")
            return
        }
        let hName = hostname.pointee.h_name
        guard let hName else {
            print("Can't get official name")
            return
        }
        let name = String(cString: hName)
        let hAddrList = hostname.pointee.h_addr_list
        
        print("found address for : \(name)!")

    }
}

so it's probable later in URLSession.shared.data()