forked from swiftlang/swift
    
        
        - 
                Notifications
    
You must be signed in to change notification settings  - Fork 29
 
Closed
Description
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_startThread 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.valueSwift 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
Labels
No labels