Processes & Concurrency

RSS for tag

Discover how the operating system manages multiple applications and processes simultaneously, ensuring smooth multitasking performance.

Concurrency Documentation

Posts under Processes & Concurrency subtopic

Post

Replies

Boosts

Views

Activity

i can not run "pgrep" or "ps" in sandbox?
Hi. I'm trying to learn macOS app development. i'm trying to run unix commands: func execute(_ command: String) throws -> String { let process = Process() let pipe = Pipe() process.executableURL = URL(fileURLWithPath: "/bin/bash") process.arguments = ["-c", command] process.standardOutput = pipe // process.standardError try process.run() process.waitUntilExit() guard let data = try pipe.fileHandleForReading.readToEnd() else { throw CommandError.readError } guard let output = String(data: data, encoding: .utf8) else { throw CommandError.invalidData } process.waitUntilExit() guard process.terminationStatus == 0 else { throw CommandError.commandFailed(output) } return output } when try to run "pgrep" in sandbox mode ON, i get: sysmon request failed with error: sysmond service not found error. if i turn it off it works. i don't know what to do. anyone can help me out?
2
0
231
Mar ’25
macOS 14 XPC vs Foundation XPC
I'm looking into a newer XPC API available starting with macOS 14. Although it's declared as a low-level API I can't figure it how to specify code signing requirement using XPCListener and XPCSession. How do I connect it with xpc_listener_set_peer_code_signing_requirement and xpc_connection_set_peer_code_signing_requirement which require xpc_listener_t and xpc_connection_t respectively? Foundation XPC is declared as a high-level API and provides easy ways to specify code signing requirements on both ends of xpc. I'm confused with all these XPC APIs and their future: Newer really high-level XPCListener and XPCSession API (in low-level framework???) Low-level xpc_listener_t & xpc_connection_t -like API. Is it being replaced by newer XPCListener and XPCSession? How is it related to High-level Foundation XPC? Are NSXPCListener and NSXPCConnection going to be deprecated and replaced by XPCListener and XPCSession??
2
0
728
Aug ’25
SMAppService Error 108 'Unable to read plist' on macOS 15 - Comprehensive Analysis & Test Case
SMAppService Error 108 "Unable to read plist" on macOS 15 Sequoia - Comprehensive Test Case Summary We have a fully notarized SMAppService implementation that consistently fails with Error 108 "Unable to read plist" on macOS 15 Sequoia, despite meeting all documented requirements. After systematic testing including AI-assisted analysis, we've eliminated all common causes and created a comprehensive test case. Error: SMAppServiceErrorDomain Code=108 "Unable to read plist: com.keypath.helperpoc.helper" 📋 Complete Repository: https://github.com/malpern/privileged_helper_help What We've Systematically Verified ✅ Perfect bundle structure: Helper at Contents/MacOS/, plist at Contents/Library/LaunchDaemons/ Correct SMAuthorizedClients: Embedded in helper binary via CREATE_INFOPLIST_SECTION_IN_BINARY=YES Aligned identifiers: Main app, helper, and plist all use consistent naming Production signing: Developer ID certificates with full Apple notarization and stapling BundleProgram paths: Tested both Contents/MacOS/helperpoc-helper and simplified helperpoc-helper Entitlements: Tested with and without com.apple.developer.service-management.managed-by-main-app What Makes This Different Systematic methodology: Not a "help me debug" post - we've done comprehensive testing Expert validation: AI analysis helped eliminate logical hypotheses Reproduction case: Minimal project that demonstrates the issue consistently Complete documentation: All testing steps, configurations, and results documented Use Case Context We're building a keyboard remapper that integrates with https://github.com/jtroo/kanata and needs privileged daemon registration for system-wide keyboard event interception. Key Questions Does anyone have a working SMAppService implementation on macOS 15 Sequoia? Are there undocumented macOS 15 requirements we're missing? Is Error 108 a known issue with specific workarounds? Our hypothesis: This appears to be a macOS 15 system-level issue rather than configuration error, since our implementation meets all documented Apple requirements but fails consistently. Has anyone encountered similar SMAppService issues on macOS 15, or can confirm a working implementation?
2
0
297
Jul ’25
ExtensionFoundation/ExtensionKit across app boundary
Hi there, I'm trying to work on an architecture where one app exposes an API (Extension Host) that other apps can plugin to. I've been reading all I can from the docs and whatever I can find online. It seemed like iOS26 added the ability to do such a thing (at least in early builds). Is that the case? Has the functionality been walked back such that extensions can only be loaded in iOS from within the single app bundle? My use case is the following: I'm working on an agent app that desires to have 3rd party developers add functionality (think how MCP servers add functionality to LLMs). The 3rd party plugins would be provided in their own app bundles vetted by the AppStore review team, of course, and would only provide hooks, basically, the main app can use to execute functions or get state. This is the best thread I found on the topic, and the subtext is that it needs to be in the same bundle. https://developer.apple.com/forums/thread/803896?answerId=865314022#865314022 Let's say for the moment that this isn't possible using ExtensionKit. What's the best way to achieve this? Our current best alternative idea is a hidded WebKit window that runs JS/WASM but that's so hackish. Please let me know, thanks!
3
0
129
1w
Background service on MacOS
Hi, I'm working on an application on MacOS. It contains a port-forward feature on TCP protocol. This application has no UI, but a local HTTP server where user can access to configure this application. I found that my application always exit for unknown purpose after running in backgruond for minutes. I think this is about MacOS's background process controlling. Source codes and PKG installers are here: https://github.com/burningtnt/Terracotta/actions/runs/16494390417
3
0
262
Jul ’25
SMAppService - How does this work?
I'm a developer using Lazarus Pascal, so converting ObjC and Swift comes with its challenges. I'm trying to figure how to properly use SMAppService to add my application as a login item for the App Store. I have learned that the old method (< macOS 13) uses a helper tool, included in the app bundle, which calls the now deprecated SMLoginItemSetEnabled. Now this is already quite a pain to deal with if you're not using XCode, not to mention converting the headers being rather complicated when you're not experienced with doing this. The "new" method (as of macOS 13) is using SMAppService. Can anyone explain how to use this? The documentation (for me anyway) is a not very clear about that and neither are examples that can be found all over the Internet. My main question is: Can I now use the SMAppService functions to add/remove a login item straight in my application, or is a helper tool still required?
3
0
145
Mar ’25
Launch daemon running but XPC is down
Hello, I have a question about a edge case scenario. Before that some info on my project- I have a launchdaemon that carries out some business logic, it also has XPC listener (built using C APIs). Question- Can there be a situation when the daemon is up and running but the XPC listener is down(due to some error or crash)? If yes then do I need to handle it in my code or launchd will handle it? when the daemon is stopped or shut down, how do I stop the XPC listener? After getting listener object from xpc_connection_create_mach_service should I just call xpc_connection_cancel followed by a call to xpc_release? Thanks! K
3
0
140
Apr ’25
Privileged helper without SMJobBless
To establish a privileged helper daemon from a command line app to handle actions requiring root privileges I still use the old way of SMJobBless. But this is deprecated since OSX 10.13 and I want to finally update it to the new way using SMAppService. As I'm concerned with securing it against malicious exploits, do you have a recommended up-to-date implementation in Objective-C establishing a privileged helper and verifying it is only used by my signed app? I've seen the suggestion in the documentation to use SMAppService, but couldn't find a good implementation covering security aspects. My old implementation in brief is as follows: bool runJobBless () { // check if already installed NSFileManager* filemgr = [NSFileManager defaultManager]; if ([filemgr fileExistsAtPath:@"/Library/PrivilegedHelperTools/com.company.Helper"] && [filemgr fileExistsAtPath:@"/Library/LaunchDaemons/com.company.Helper.plist"]) { // check helper version to match the client // ... return true; } // create authorization reference AuthorizationRef authRef; OSStatus status = AuthorizationCreate (NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef); if (status != errAuthorizationSuccess) return false; // obtain rights to install privileged helper AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 }; AuthorizationRights authRights = { 1, &authItem }; AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; status = AuthorizationCopyRights (authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); if (status != errAuthorizationSuccess) return false; // SMJobBless does it all: verify helper against app and vice-versa, place and load embedded launchd.plist in /Library/LaunchDaemons, place executable in /Library/PrivilegedHelperTools CFErrorRef cfError; if (!SMJobBless (kSMDomainSystemLaunchd, (CFStringRef)@"com.company.Helper", authRef, &cfError)) { // check helper version to match the client // ... return true; } else { CFBridgingRelease (cfError); return false; } } void connectToHelper () { // connect to helper via XPC NSXPCConnection* c = [[NSXPCConnection alloc] initWithMachServiceName:@"com.company.Helper.mach" options:NSXPCConnectionPrivileged]; c.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol (SilentInstallHelperProtocol)]; [c resume]; // call function on helper and wait for completion dispatch_semaphore_t semaphore = dispatch_semaphore_create (0); [[c remoteObjectProxy] callFunction:^() { dispatch_semaphore_signal (semaphore); }]; dispatch_semaphore_wait (semaphore, dispatch_time (DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)); dispatch_release (semaphore); [c invalidate]; [c release]; }
3
0
198
Oct ’25
Behavior of BGContinuedProcessingTask on Failure
Hi there, First thanks for all the work on BGContinuedProcessingTask! It looks really promising. I have a question / issue around the behavior when a BGContinuedProcessingTask expires. Here is my setup. I have an app who's responsible for uploading large files in the field (AKA wifi is not expected) For a given file, it can likely fail due to network conditions I'm using Multipart upload though so I can retry a file to pick up where it left off. I use one taskIdentifier per file, and when the file fails, I can retry the task and have it continue where it left off (I am reusing the taskIdentifier here for retries, let me know if I shouldn't be doing that) Here is the behavior I am seeing I start an upload, it seems to be uploading normally I turn on airplane mode to simulate expiration of the task the task fails as expected after ~30 seconds, and I see the failure in my home screen. I have callbacks in the task to put my app in the proper state on expiration / failure I turn back on airplane mode and I retry the task, the way I do this is I do NOT re-register, I simply re-submit the task with the same TaskIdentifier. What I would have expected is that the failure task is REPLACED with the new task and new progress. Instead what I see is TWO ContinuedBackgroundProcessingTasks, one in the failure state and one in progress. My question is How can I make retries reuse the same task notification item? OR if that's not possible, how do I programmatically clear the task failure? I've tried cancelTask but that doesn't seem to clear it.
3
1
200
2w
best practices for communication between system extension and daemon
Hello, My team has developed a DNS proxy for macOS. We have this set up with a system extension that interacts with the OS, and an always-running daemon that does all the heavy lifting. Communication between the two is DNS request and response packet traffic. With this architecture what are best practices for how the system extension communicates with a daemon? We tried making the daemon a socket server, but the system extension could not connect to it. We tried using XPC but it did not work and we could not understand the errors that were returned. So what is the best way to do this sort of thing?
3
0
739
Jan ’25
Crashes because main actor isolated closures are called on a background thread with `DispatchGroup.notify`, but no compiler warnings
Hello! We are in the progress of migrating a large Swift 5.10 legacy code base over to use Swift 6.0 with Strict Concurrency checking. We have already stumbled across a few weird edge cases where the "guaranteed" @MainActor isolation is violated (such as with @objc #selector methods used with NotificationCenter). However, we recently found a new scenario where our app crashes accessing main actor isolated state on a background thread, and it was surprising that the compiler couldn't warn us. Minimal reproducible example: class ViewController: UIViewController { var isolatedStateString = "Some main actor isolated state" override func viewDidLoad() { exampleMethod() } /// Note: A `@MainActor` isolated method in a `@MainActor` isolated class. func exampleMethod() { testAsyncMethod() { [weak self] in // !!! Crash !!! MainActor.assertIsolated() // This callback inherits @MainActor from the class definition, but it is called on a background thread. // It is an error to mutate main actor isolated state off the main thread... self?.isolatedStateString = "Let me mutate my isolated state" } } func testAsyncMethod(completionHandler: (@escaping () -> Void)) { let group = DispatchGroup() let queue = DispatchQueue.global() // The compiler is totally fine with calling this on a background thread. group.notify(queue: queue) { completionHandler() } // The below code at least gives us a compiler warning to add `@Sendable` to our closure argument, which is helpful. // DispatchQueue.global().async { // completionHandler() // } } } The problem: In the above code, the completionHandler implementation inherits main actor isolation from the UIViewController class. However, when we call exampleMethod(), we crash because the completionHandler is called on a background thread via the DispatchGroup.notify(queue:). If were to instead use DispatchQueue.global().async (snippet at the bottom of the sample), the compiler helpfully warns us that completionHandler must be Sendable. Unfortunately, DispatchGroup's notify gives us no such compiler warnings. Thus, we crash at runtime. So my questions are: Why can't the compiler warn us about a potential problem with DispatchGroup().notify(queue:) like it can with DispatchQueue.global().async? How can we address this problem in a holistic way in our app, as it's a very simple mistake to make (with very bad consequences) while we migrate off GCD? I'm sure the broader answer here is "don't mix GCD and Concurrency", but unfortunately that's a little unavoidable as we migrate our large legacy code base! 🙂
3
3
446
Nov ’25
application(_:didFinishLaunchingWithOptions:) not called on MDM iPads after overnight idle — app resumes without cold start
We are seeing a strange lifecycle issue on multiple MDM-managed iPads where application(_:didFinishLaunchingWithOptions:) is not called after the device is idle overnight. Even if we terminate the app manually via the app switcher, the next morning the system does not perform a cold launch. Instead, the app resumes directly in: applicationDidBecomeActive(_:) This causes all initialization logic that depends on didFinishLaunching to be completely skipped. This behavior is consistent across four different supervised MDM devices. Environment Devices: iPads enrolled in MDM (supervised) iOS version: 18.3 Xcode: 16.4 macOS: Sequoia 15.7.2 App type: Standard UIKit iOS app App: Salux Audiometer (App Store app) Expected Behavior If the app was terminated manually using the app switcher, the next launch should: Start a new process Trigger application(_:didFinishLaunchingWithOptions:) Follow the normal cold-start lifecycle Actual Behavior After leaving the iPad idle overnight (8–12 hours): The next launch skips didFinishLaunching The app resumes directly in applicationDidBecomeActive No new process is started App behaves as if it had been suspended, even though it was manually terminated Logs (Relevant Extracts) Day 1 — Normal cold launch [12:06:44.152 PM] PROCESS_STARTED [12:06:44.214 PM] DID_FINISH_LAUNCHING_START launchOptions=[] [12:06:44.448 PM] DID_FINISH_LAUNCHING_END We then used the app and terminated it via app switcher. Day 2 — Unexpected resume without cold start [12:57:49.328 PM] APP_DID_BECOME_ACTIVE No PROCESS_STARTED No didFinishLaunching No cold-start logs This means the OS resumed the app from a previous state that should not exist. Reproducible Steps Use an MDM-enrolled iPad. Launch the app normally. Terminate it manually via the multitasking app switcher. Leave the device idle overnight (8–12 hours). Launch the app the next morning. Observe that: didFinishLaunching does not fire applicationDidBecomeActive fires directly Questions for Apple Engineers / Community Is this expected behavior on MDM-supervised devices in iOS 18? Are there any known OS-level changes where terminated apps may be revived from disk/memory? Could MDM restrictions or background restoration policies override app termination? How can we ensure that our app always performs a clean initialization when launched after a long idle period? Additional Information We have full logs from four separate MDM iPads showing identical behavior. Happy to share a minimal reproducible sample if required.
3
0
72
1d
My system daemons are not getting launched in MacOS 15x post reboot
When I install my application, it installs fine and everything works alongwith all the system level daemons but when I reboot the system, none of my daemons are getting launched and this happens only on MacOS 15x, on older version it is working fine. In the system logs, I see that my daemons have been detected as legacy daemons by backgroundtaskmanagementd with Disposition [enabled, allowed, visible, notified] 2025-01-13 21:17:04.919128+0530 0x60e Default 0x0 205 0 backgroundtaskmanagementd: [com.apple.backgroundtaskmanagement:main] Type: legacy daemon (0x10010) 2025-01-13 21:17:04.919128+0530 0x60e Default 0x0 205 0 backgroundtaskmanagementd: [com.apple.backgroundtaskmanagement:main] Flags: [ legacy ] (0x1) 2025-01-13 21:17:04.919129+0530 0x60e Default 0x0 205 0 backgroundtaskmanagementd: [com.apple.backgroundtaskmanagement:main] Disposition: [enabled, allowed, visible, notified] (0xb) But later, it backgroundtaskmanagementd decides to disallow it. 2025-01-13 21:17:05.013202+0530 0x32d Default 0x4d6 89 0 smd: (BackgroundTaskManagement) [com.apple.backgroundtaskmanagement:main] getEffectiveDisposition: disposition=[enabled, disallowed, visible, notified], have LWCR=true 2025-01-13 21:17:05.013214+0530 0x32d Error 0x0 89 0 smd: [com.apple.xpc.smd:all] Legacy job is not allowed to launch: <private> status: 2 Is there anything changed in latest Mac OS which is causing this issue? Also what does this status 2 means. Can someone please help with this error? The plist has is true
3
0
339
Feb ’25
TCC Permission Inheritance Failure: Swift Parent -> Python Child
TCC Permission Inheritance for Python Process Launched by Swift App in Enterprise Deployment We are developing an enterprise monitoring application that requires a hybrid Swift + Python architecture due to strict JAMF deployment restrictions. We must deploy a macOS application via ABM/App Store Connect, but our core monitoring logic is in a Python daemon. We need to understand the feasibility and best practices for TCC permission inheritance in this specific setup. Architecture Component Bundle ID Role Deployment Swift Launcher com.athena.AthenaSentry Requests TCC permissions, launches Python child process. Deployed via ABM/ASC. Python Daemon com.athena.AthenaSentry.Helper Core monitoring logic using sensitive APIs. Nested in Contents/Helpers/. Both bundles are signed with the same Developer ID and share the same Team ID. Required Permissions The Python daemon needs to access the following sensitive TCC-controlled services: Screen Recording (kTCCServiceScreenCapture) - for capturing screenshots. Input Monitoring (kTCCServiceListenEvent) - for keystroke/mouse monitoring. Accessibility (kTCCServiceAccessibility) - a prerequisite for Input Monitoring. Attempts & Workarounds We have attempted to resolve this using: Entitlement Inheritance: Added com.apple.security.inherit to the Helper's entitlements. Permission Proxy: Swift app maintains active event taps to try and "hold" the permissions for the child. Foreground Flow: Keeping the Swift app in the foreground during permission requests. Questions Is this architecture supported? Can a Swift parent app successfully request TCC permissions that a child process can then use? TCC Inheritance: What are the specific rules for TCC permission inheritance between parent/child processes in enterprise environment? What's the correct approach for this enterprise use case? Should we: Switch to a Single Swift App? (i.e., abandon the Python daemon and rewrite the core logic natively in Swift). Use XPC Services? (instead of launching the child process directly).
3
0
167
Nov ’25
XPC Service Installed Outside App Doesn't Set Responsible
On macOS 15.7.1 I'm trying to install an XPC service outside the app (Developer ID). It mostly seems to go ok, but when I set Launch Constraints on Responsible, AMFI complains of a violation, saying the service is responsible for itself, and fails to launch. Removing that constraint (or adding the service itself to the constraint) works fine. The service is an optional download, and installed to /Users/Shared with a LaunchAgent specifying the MachService. The service is correctly launched and seems to pass all codesigning, notarization, and other checks, but the Responsible isn't set to the "calling" app. Is this broken, or working as intended?
3
0
202
Nov ’25
How to Handle Asynchronous Operations in BGContinuedProcessingTask
I would like to know whether BGContinuedProcessingTaskRequest supports executing asynchronous tasks internally, or if it can only execute synchronous tasks within BGContinuedProcessingTaskRequest? Our project is very complex, and we now need to use BGContinuedProcessingTaskRequest to perform some long-running operations when the app enters the background (such as video encoding/decoding & export). However, our export interface is an asynchronous function, for example video.export(callback: FinishCallback). This export call returns immediately, and when the export completes internally, it calls back through the passed-in callback. So when I call BGTaskScheduler.shared.register to register a BGContinuedProcessingTask, what should be the correct approach? Should I directly call video.export(nil) without any waiting, or should I wait for the export function to complete in the callback? For example: BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.xxx.xxx.xxx.xxx", using: nil) { task in guard let continuedTask = task as? BGContinuedProcessingTask else { task.setTaskCompleted(success: false) return } let scanner = SmartAssetsManager.shared let semaphore = DispatchSemaphore(value: 0) continuedTask.expirationHandler = { logError(items: "xwxdebug finished.") semaphore.signal() } logInfo(items: "xwxdebug start!") video.export { _ in semaphore.signal() } semaphore.wait() logError(items: "xwxdebug finished!") }
3
0
98
Nov ’25
Called endBackgroundTask but not working
When my app enter to background, I start a background task, and when Expiration happens, I end my background task. The code likes below: backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (backgroundTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:backgroundTask]; backgroundTask = UIBackgroundTaskInvalid; [self cancel]; } }); }]; When the breakpoint is triggered at the endBackgroundTask line, I also get the following log: [BackgroundTask] Background task still not ended after expiration handlers were called: <UIBackgroundTaskInfo: 0x282d7ab40>: taskID = 36, taskName = Called by MyApp, from MyMethod, creationTime = 892832 (elapsed = 26). This app will likely be terminated by the system. Call UIApplication.endBackgroundTask(:) to avoid this. The log don't appear every time, so why is that? Is there something wrong with my code?
3
0
3.0k
Jun ’25
Memory visibility issue regards to shared data with Dispatch Queue
I’m working with apple dispatch queue in C with the following design: multiple dispatch queues enqueue tasks into a shared context, and a dedicated dispatch queue (let’s call it dispatch queue A) processes these tasks. However, it seems this design has a memory visibility issue. Here’s a simplified version of my setup: I have a shared_context struct that holds: task_lis: a list that stores tasks to be prioritized and run — this list is only modified/processed by dispatch queue A (a serially dispatch queue), so I don't lock around it. cross_thread_tasks: a list that other queues push tasks into, protected by a lock. Other dispatch queues call a function schedule_task that locks and appends a new task to cross_thread_tasks call dispatch_after_f() to schedule a process_task() on dispatch queue A process_task() that processes the task_list and is repeatedly scheduled on dispatch queue A : Swaps cross_thread_tasks into a local list (with locking). Pushes the tasks into task_list. Runs tasks from task_list. Reschedules itself via dispatch_after_f(). Problem: Sometimes the tasks pushed from other threads don’t seem to show up in task_list when process_task() runs. The task_list appears to be missing them, as if the cross-thread tasks aren’t visible. However, if the process_task() is dispatched from the same thread the tasks originate, everything works fine. It seems to be a memory visibility or synchronization issue. Since I only lock around cross_thread_tasks, could it be that changes to task_list (even though modified on dispatch queue A only) are not being properly synchronized or visible across threads? My questions What’s the best practice to ensure shared context is consistently visible across threads when using dispatch queues? Is it mandatory to lock around all tasks? I would love to minimize/avoid lock if possible. Any guidance, debugging tips, or architectural suggestions would be appreciated! =============================== And here is pseudocode of my setup if it helps: struct shared_data { struct linked_list* task_list; } struct shared_context { struct shared_data *data; struct linked_list* cross_thread_tasks; struct thread_mutex* lock; // lock is used to protect cross_thread_tasks } static void s_process_task(void* shared_context){ struct linked_list* local_tasks; // lock and swap the cross_thread_tasks into a local linked list lock(shared_context->lock) swap(shared_context->cross_thread_tasks, local_tasks) unlock(shared_context->lock) // I didnt use lock to protect `shared_context->data` as they are only touched within dispatch queue A in this function. for (task : local_tasks) { linked_list_push(shared_context->data->task_list) } // If the `process_task()` block is dispatched from `schedule_task()` where the task is created, the `shared_context` will be able to access the task properly otherwise not. for (task : shared_context->data->task_list) { run_task_if_timestamp_is_now(task) } timestamp = get_next_timestamp(shared_context->data->task_list) dispatch_after_f(timestamp, dispatch_queueA, shared_context, process_task); } // On dispatch queue B static void schedule_task(struct task* task, void* shared_context) { lock(shared_context->lock) push(shared_context->cross_thread_tasks, task) unlock(shared_context->lock) timestamp = get_timestamp(task) // we only dispatch the task if the timestamp < 1 second. We did this to avoid the dispatch queue schedule the task too far ahead and prevent the shutdown process. Therefore, not all task will be dispatched from the thread it created. if(timestamp < 1 second) dispatch_after_f(timestamp, dispatch_queueA, shared_context, process_task); }
3
0
105
May ’25