Search code examples
cswifttaskcancellation

Cancel signal from Swift not reaching C task


I have a Swift initializer kicking off a C task with the standard argc/argv arguments, which works fine but I wanted to pass a pointer to a kill signal (isCancelled) so it can do some cleanup when I close the parent Swift program. In my test here, where I just wait 10 seconds before cancelling the task, I'm able to pass the pointer but the pointed value doesn't change when I cancel the task. Here is the code - the print shows the isCancelled is true after cancelling but my C Task (myClient) always sees 0 for the value of *isCancelled:

    init() {
        let myClientTask: Task = Task {
            let argc: CInt = 2
            var argv: [UnsafePointer<CChar>?] = [("IndigoPolarAlignAid" as NSString).utf8String,("2" as NSString).utf8String,nil]
            var isCancelled: Bool = Task.isCancelled
            _ = myClient(argc, &argv, &isCancelled)
        }
        let _: Task = Task {
            sleep(10) // Seconds
            print("Timesup!")
            myClientTask.cancel()
            print("SWClient: isCancelled is \(myClientTask.isCancelled)")
        }
    }

Solution

  • In your example, isCancelled is just a local variable and it is assigned exactly once. It has no connection to the Task's isCancelled property other than being initialized with its value at some point. What you want here is a cancellation handler that will run when the Task is cancelled.

    If you want to pass a C pointer you can read, it would be along these lines:

    let myClientTask: Task = Task {
        let argc: CInt = 2
        var argv: [UnsafePointer<CChar>?] = [("IndigoPolarAlignAid" as NSString).utf8String,("2" as NSString).utf8String,nil]
    
        let isCancelled = UnsafeMutablePointer<Bool>.allocate(capacity: 1)
        isCancelled.initialize(to: Task.isCancelled)
        defer { isCancelled.deallocate() }
    
        await withTaskCancellationHandler {
            _ = myClient(argc, &argv, isCancelled)
        } onCancel: {
            isCancelled.pointee = true
        }
    }
    

    That said, this isn't thread-safe (reading and writing through a pointer on different threads without a lock isn't defined behavior). It would be better to pass an atomic type rather than a BOOL*, but what type to use depends a bit on your C code, though I would check out Swift Atomics, which should interoperate with C well without having to go to pthreads. (I haven't tried to pass atomics across languages this way; someone else may have a more complete answer for that.)

    I would generally recommend not doing it this way, and instead making another C function you can call in onCancel that will take care of shutting things down.

    Also, as Sweeper points out, your sleep should be:

    try await Task.sleep(for: .seconds(10))