Thread safety of os_activity_t

Is it allowed to use an os_activity_t instance created with os_activity_create from multiple threads?

In particular, would it be allowed to use os_activity_apply/os_activity_scope concurrently from multiple threads to associate separate chunks of work with the same activity?

My use case is an activity where I'm using Task.detached to create a detached unstructured Task, which (as can be expected) prevents inheritance of the current activity. The bulk of the activity happens in the detached Task so I can just create the activity there but ideally I would like to also associate some of the setup work before spawning the Task with the same activity. So I'm wondering if it is safe to create the activity, apply it to the setup including spawning the detached Task and then capture and apply the same activity inside the Task as well where it might be applied concurrently with the first use on the thread spawning the Task.

Answered by DTS Engineer in 864067022

There are two parts to this:

  • What works?
  • What’s sensible?

AFAICT an OS activity is thread safe. You can happily create it in one thread and apply it in another.

However, there are reasons to be careful here. The doc comments in <os/activity.h> make it clear that the current activity — well stack of activities — is per-thread state. That raises a couple of concerns.

The first is just confusion on your part. If you start the same activity on multiple threads then you could easily get confused about what’s happening where. I think the OS will handle it just fine, so it’s really just a matter of whether it works for you.

The other concern is Swift concurrency. It uses a pool of worker threads and the thread running your task can change at any suspension point. That means you have to be careful with per-thread state. Specifically, you have to make sure you clean up any per-thread state that you apply before you suspend.

This comes naturally to os_activity_apply because the closure you supply is synchronous. OTOH, os_activity_scope_{enter,leave} open the door for problems.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

There are two parts to this:

  • What works?
  • What’s sensible?

AFAICT an OS activity is thread safe. You can happily create it in one thread and apply it in another.

However, there are reasons to be careful here. The doc comments in <os/activity.h> make it clear that the current activity — well stack of activities — is per-thread state. That raises a couple of concerns.

The first is just confusion on your part. If you start the same activity on multiple threads then you could easily get confused about what’s happening where. I think the OS will handle it just fine, so it’s really just a matter of whether it works for you.

The other concern is Swift concurrency. It uses a pool of worker threads and the thread running your task can change at any suspension point. That means you have to be careful with per-thread state. Specifically, you have to make sure you clean up any per-thread state that you apply before you suspend.

This comes naturally to os_activity_apply because the closure you supply is synchronous. OTOH, os_activity_scope_{enter,leave} open the door for problems.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you!

You raise a good point with regard to scoping an activity across suspension points. os_activity_scope_{enter,leave} likely requires being called on the same thread even if the activity is automatically propagated across threads inside the scope.

For non-detached Tasks this is simple as I can scope/apply the activity around the synchronous creation of the Task and the activity will be carried into the task automatically.

Inside an async context I would probably need to ensure that I'm entering and leaving the scope on the same thread and even then it would likely break if that thread is used to run other work in the meantime.

Hopefully we get Swift-native and Swift Concurrency compatible support for creating OS activities at some point, because they already work very well once created. They are even correctly propagated across custom non-GCD based TaskExecutors from what I can see.

Restricting my use to non-detached Tasks is fine for now though, they are much more frequent than detached ones anyway.


I guess the following would theoretically work to apply an activity inside an async-capable detached Task at the cost of an additional temporary Task:

// create a temporary detached task to create a detached context to apply the activity in
Task.detached(/* configure name, executor, priority as needed */) {
    // safely apply the activity
    os_activity_apply(activity) {
        // Spawn a non-detached task, inherit executor, priority and activity from the detached wrapper Task to run the actual operation
        // Use an immediate task to avoid a second scheduling delay
        Task.immediate(/* name appropriately */) {
            // run async work
        }
    }
}
Hopefully we get Swift-native and Swift Concurrency compatible support for creating OS activities at some point

I recommend that you file an enhancement request for that specifically. Please post your bug number, just for the record.

As to your comments about how to mix activities and Swift concurrency, I don’t know enough about those interactions to offer an informed opinion.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I recommend that you file an enhancement request for that specifically. Please post your bug number, just for the record.

Filed as FB20882147

Thread safety of os_activity_t
 
 
Q