skip to Main Content

I have a function that returns the memory use of a device. It uses mach_task_self_ which gives a warning that it is not concurrency-safe.

task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), intPtr, &count)

According to this answer you can import and mark as @preconcurrency however when I try this it doesn’t work.

@preconcurrency import Darwin.Mach.mach_init

It says '@preconcurrency' attribute on module 'Darwin' has no effect

When I jump to the definition of mach_task_self_ it shows me it is in Darwin > Mach > mach_init

2

Answers


  1. It seems like a @preconcurrency import won’t work if the thing you are trying to access is also imported by another non-preconcurrency import. Presumably, you are also importing things like Foundation.

    If you put the import in a separate file,

    @preconcurrency import Darwin
    
    var machTaskSelf: mach_port_t {
        mach_task_self_
    }
    

    there is no warning.

    And now you can use this computed property elsewhere.

    task_info(machTaskSelf, task_flavor_t(TASK_VM_INFO), intPtr, &count)
    
    Login or Signup to reply.
  2. I ran into the same issue earlier today and ended up coming up with a workaround to get rid of the warnings.

    I had to dabble a bit with Objective-C, but that allowed me to write a wrapper to mach_task_self_ that borrows Swift’s actor isolation such that we can safely access the data.

    I created a class with a static property that is initialized as soon as the framework is loaded and all subsequent accesses to the attribute are synchronized internally. It worked beautifully for my use case, I hope it helps you too!

    MyMachTaskWrapper.h:

    @import Foundation;
    @import Darwin;
    
    __attribute__((swift_attr("@MainActor")))
    @interface MyMachTaskWrapper: NSObject
    
    @property(class) mach_port_t isolatedMachTaskSelf;
    
    @end
    

    MyMachTaskWrapper.m:

    #import "MyMachTaskWrapper.h"
    
    @implementation MyMachTaskWrapper
    
    static mach_port_t isolated_mach_task_self_;
    
    // Invoked when this class is added to the Objective-C runtime
    + (void)load {
      [self setIsolatedMachTaskSelf:mach_task_self_];
    }
    
    // Just to be sure I decided to synchronize reads with a mutex lock,
    // but I'm not completely sure if this is needed
    + (mach_port_t)isolatedMachTaskSelf {
      @synchronized(self) {
        return isolated_mach_task_self_;
      }
    }
    
    // This should only ever run on `load`
    + (void)setIsolatedMachTaskSelf:(mach_port_t)task {
        isolated_mach_task_self_ = task;
    }
    
    @end
    

    Of course, you will need to expose this class to Swift. After that, the usage is as simple as:

    Task { @MainActor in
      MyMachTaskWrapper.isolatedMachTaskSelf // no warnings here!
    }
    

    Note that Swift sees MyMachTaskWrapper as a @MainActor, so we must ensure we’re working on the right context.

    I got the idea of writing this wrapper from SE-0412, which states:

    There remain tools for enforcing safety for imported global variables from other languages, such as isolating to a global actor using for example attribute((swift_attr("@MainActor"))) in C or Obj-C, or wrapping access within a safer API that declares the correct isolation or locks appropriately.

    I’m still not sure if this is the best approach, but it works for me 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search