Skip to content

Synchronization between threads #5613

@p-lenart

Description

@p-lenart

I wanted to create a very small PoC that showed case offloading CPU-intensive calls to the background thread. I cannot confirm it, but I have a suspicion that Swift's stdlib implementation for WASI is not actually thread-safe, and I cannot synchronize code in an expected way. I attached the code at the bottom. I can see various crashes around accessing invalid memory, locking, etc. Here are some examples:

Thread 1Thread 2
Thread 1Thread 2
Error: error while executing at wasm backtrace:
    0: 0xef40ca - MT.wasm!__wasi_fd_write
    1: 0xf00db4 - MT.wasm!writev
    2: 0xf00eeb - MT.wasm!__stdio_write
    ...
   12:  0x3da41 - MT.wasm!$s2MT13spawnBlockingyxxyYbcYaKs8SendableRzlFyyYbcfU_TA
   13:  0x400ca - MT.wasm!$s2MT18_wasi_thread_startyys5Int32V_SvSgtF
   14:  0x4000b - MT.wasm!wasi_thread_start
Thread 1
Error: error while executing at wasm backtrace:
    0:  0x414a6 - MT.wasm!$sypWOc
    1: 0x1dde66 - MT.wasm!$ss6_print_9separator10terminator2toySayypG_S2Sxzts16TextOutputStreamRzlFs7_StdoutV_Tg5
    2: 0x1deb20 - MT.wasm!$ss5print_9separator10terminatoryypd_S2StFTm
    ...
    7:  0x400ca - MT.wasm!$s2MT18_wasi_thread_startyys5Int32V_SvSgtF
    8:  0x4000b - MT.wasm!wasi_thread_start

Here is my code:

import WASILibc
import Foundation

// Simple runtime for spawning blocking operation on separate thread.

enum ThreadError: Error {
    case threadSpawnFailed
}

class BoxedValue<T: Sendable>: @unchecked Sendable {
    private let lock = NSLock()
    private var value: T?

    func put(_ newValue: T) {
        lock.lock()
        value = newValue
        lock.unlock()
    }

    func take() -> T? {
        lock.lock()
        let currentValue = value
        value = nil
        lock.unlock()
        return currentValue
    }

    deinit {
        print("BoxedValue deinitialized")
    }
}

class ThreadContext: @unchecked Sendable {
    let operation: @Sendable () -> Void
    init(operation: @Sendable @escaping ()-> Void ) {
        self.operation = operation
    }

    deinit {
        print("ThreadContext deinitialized")
    }
}

public func spawnBlocking<T: Sendable>(_ operation: @Sendable @escaping () -> T) async throws -> T {
    let returnValue = BoxedValue<T>()
    let context = ThreadContext(operation: {
        let result = operation()
        returnValue.put(result)
    })
    let unmanagedContext = Unmanaged<ThreadContext>.passRetained(context)
    let result = WASILibc.__wasi_thread_spawn(unmanagedContext.toOpaque())
    guard result >= 0 else {
        throw ThreadError.threadSpawnFailed
    }
    // Tried implementation based on unchecked continuation and async stream, but we
    // cannot call theses APIs from the WASM thread, unfortunately.
    while true {
        if let value = returnValue.take() {
            return value
        }
        try await Task.sleep(nanoseconds: 10_000_000)
    }
}

@_expose(wasm, "wasi_thread_start")
@_cdecl("wasi_thread_start")
public func _wasi_thread_start(_ threadId: Int32, _ arg: UnsafeMutableRawPointer?) {
    guard let arg = arg else {
        fatalError("Thread started with null argument")
    }
    let unmanagedContext = Unmanaged<ThreadContext>.fromOpaque(arg)
    unmanagedContext.takeRetainedValue().operation()
}


// Example

print("Hello from main thread")

let task1 = Task {
    try await spawnBlocking {
        print("Starting counter 1 thread")
        while true {
            print("Thread 1")
            usleep(100_000)
        }
        print("Finished counter 1 thread")
    }
}

let task2 = Task {
    try await spawnBlocking {
        print("Starting counter 2 thread")
        while true {
            print("Thread 2")
            usleep(100_000)
        }
        print("Finished counter 2 thread")

    }
}

try await task1.value
try await task2.value

Swift SDK version that I use: 6.1-RELEASE-wasm32-unknown-wasip1-threads
Compiled with: swift build --swift-sdk wasm32-unknown-wasip1-threads
Run with: wasmtime --wasi threads .build/debug/MT.wasm

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions