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

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
How to debug or ensure single resume calls for continuations at compile time or with tools like Instruments?
When using the continuation API, we're required to call resume exactly once. While withCheckedContinuation helps catch runtime issues during debugging, I'm looking for ways to catch such errors at compile time or through tools like Instruments. Is there any tool or technique that can help enforce or detect this requirement more strictly than runtime checks? Or would creating custom abstractions around Continuation be the only option to ensure safety? Any suggestions or best practices are appreciated.
2
0
419
Jan ’25
Cleanup LaunchAgents after development
I have been playing with application bundled LaunchAgents: I downloaded Apple sample code, Run the sample code as is, Tweaked the sample code a lot and changed the LaunchAgents IDs and Mach ports IDs, Created new projects with the learnings, etc. After deleting all the Xcode projects and related project products and rebooting my machine several times, I noticed the LaunchAgent are still hanging around in launchctl. If I write launchctl print-disabled gui/$UID (or user/$UID) I can see all my testing service-ids: disabled services = { "com.xpc.example.agent" => disabled "io.dehesa.apple.app.agent" => disabled "io.dehesa.sample.app.agent" => disabled "io.dehesa.example.agent" => disabled "io.dehesa.swift.xpc.updater" => disabled "io.dehesa.swift.agent" => disabled } (there are more service-ids in that list, but I removed them for brevity purposes). I can enable or disable them with launchctl enable/disable service-target, but I cannot really do anything else because their app bundle and therefore PLIST definition are not there anymore. How can I completely remove them from my system? More worryingly, I noticed that if I try to create new projects with bundled LaunchAgents and try to reuse one of those service-ids, then the LaunchAgent will refuse to run (when it was running ok previously). The calls to SMAppService APIs such .agent(plistName:) and register() would work, though.
3
0
122
May ’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
727
Aug ’25
How to safely terminate hanging threads in launched agent context
Hello, In a launched agent, I need to call into a third‑party library that may occasionally hang. At present, these calls are made from a separate thread, but if the thread hangs it cannot be terminated (pthread_cancel/pthread_kill are ineffective). Would Apple recommend isolating this functionality in a separate process that can be force‑terminated if it becomes unresponsive, or is there a preferred approach for handling such cases in launched agents? Can I use the system call fork() in launched agent? Thank you in advance!
2
0
92
Oct ’25
Background Download Not Working in release builds- flutter_downloader
Issue: Background downloads using the flutter_downloader package work perfectly in debug mode and release mode when run directly from Xcode (plugged in). However, when I create an archive build and install the app separately (via TestFlight or direct IPA install), the background download stops working as soon as the app is minimized. ✅ What I’ve already done Info.plist <key>UIBackgroundModes</key> <array> <string>remote-notification</string> <string>fetch</string> <string>processing</string> <string>audio</string> <string>push-to-talk</string> </array> AppDelegate.swift import UIKit import Flutter import Firebase import flutter_downloader import BackgroundTasks @main @objc class AppDelegate: FlutterAppDelegate { static let backgroundChannel = "com.example.app/background_service" private var backgroundCompletionHandler: (() -> Void)? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { FirebaseApp.configure() GeneratedPluginRegistrant.register(with: self) FlutterDownloaderPlugin.setPluginRegistrantCallback(registerPlugins) if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self } if #available(iOS 13.0, *) { registerBackgroundTask() } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @available(iOS 13.0, *) private func registerBackgroundTask() { BGTaskScheduler.shared.register( forTaskWithIdentifier: "com.example.app.process_download_queue", using: nil ) { [weak self] task in guard let self = self else { return } self.handleDownloadQueueTask(task: task as! BGProcessingTask) } } @available(iOS 13.0, *) private func handleDownloadQueueTask(task: BGProcessingTask) { scheduleNextDownloadTask() let headlessEngine = FlutterEngine(name: "BackgroundTaskEngine", project: nil, allowHeadlessExecution: true) headlessEngine.run() let channel = FlutterMethodChannel( name: AppDelegate.backgroundChannel, binaryMessenger: headlessEngine.binaryMessenger ) task.expirationHandler = { channel.invokeMethod("backgroundTaskExpired", arguments: nil) } channel.invokeMethod("processNextInBackground", arguments: nil) { result in task.setTaskCompleted(success: (result as? Bool) ?? false) } } override func application( _ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void ) { self.backgroundCompletionHandler = completionHandler super.application(application, handleEventsForBackgroundURLSession: identifier, completionHandler: completionHandler) } override func applicationDidEnterBackground(_ application: UIApplication) { if #available(iOS 13.0, *) { scheduleNextDownloadTask() } } @available(iOS 10.0, *) override func userNotificationCenter( _ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void ) { if #available(iOS 14.0, *) { completionHandler([.list, .banner, .badge, .sound]) } else { completionHandler([.alert, .badge, .sound]) } } @available(iOS 10.0, *) override func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void ) { completionHandler() } } // MARK: - Helper @available(iOS 13.0, *) func scheduleNextDownloadTask() { let request = BGProcessingTaskRequest(identifier: "com.example.app.process_download_queue") request.requiresNetworkConnectivity = true request.requiresExternalPower = false request.earliestBeginDate = Date(timeIntervalSinceNow: 60) do { try BGTaskScheduler.shared.submit(request) print("BGTask: Download queue processing task scheduled successfully.") } catch { print("BGTask: Could not schedule download queue task: \(error)") } } private func registerPlugins(registry: FlutterPluginRegistry) { if !registry.hasPlugin("FlutterDownloaderPlugin") { FlutterDownloaderPlugin.register(with: registry.registrar(forPlugin: "FlutterDownloaderPlugin")!) } } 🧩 Observations Background download works correctly when: The app is plugged in and run via Xcode (release/debug) It stops working when: The app is installed from an archived build (IPA/TestFlight) and minimized All entitlements and background modes are properly added. Provisioning profile includes required background modes. ❓Question Is there any known limitation or signing difference between Xcode run and archived release builds that could cause URLSession background tasks not to trigger? Has anyone faced a similar issue when using flutter_downloader on iOS 13+ with BGTaskScheduler or URLSession background configuration? Any help or working setup example for production/TestFlight would be appreciated.
1
0
69
Nov ’25
C program posix_spawn diskutil fails with error -69877
Hello, I am programming a CLI tool to partition USB disks. I am calling diskutil to do the work, but I am hitting issues with permissions, it seems. Here is a trial run of the same command running diskutil directly on the terminal vs running from my code: Calling diskutil directly (works as expected) % /usr/sbin/diskutil partitionDisk /dev/disk2 MBR Free\ Space gap 2048S fat32 f-fix 100353S Free\ Space tail 0 Started partitioning on disk2 Unmounting disk Creating the partition map Waiting for partitions to activate Formatting disk2s1 as MS-DOS (FAT32) with name f-fix 512 bytes per physical sector /dev/rdisk2s1: 98784 sectors in 98784 FAT32 clusters (512 bytes/cluster) bps=512 spc=1 res=32 nft=2 mid=0xf8 spt=32 hds=16 hid=2079 drv=0x80 bsec=100360 bspf=772 rdcl=2 infs=1 bkbs=6 Mounting disk Finished partitioning on disk2 /dev/disk2 (disk image): #: TYPE NAME SIZE IDENTIFIER 0: FDisk_partition_scheme +104.9 MB disk2 1: DOS_FAT_32 F-FIX 51.4 MB disk2s1 Calling diskutil programmatically (error -69877) % sudo ./f-fix DEBUG: /usr/sbin/diskutil partitionDisk /dev/disk2 MBR Free Space gap 2048S fat32 f-fix 100353S Free Space tail 0 Started partitioning on disk2 Unmounting disk Error: -69877: Couldn't open device (Is a disk in use by a storage system such as AppleRAID, CoreStorage, or APFS?) Failed to fix drive `/dev/disk2' Source Code The relevant code from my program is this: char *args[16]; int n = 0; args[n++] = "/usr/sbin/diskutil"; args[n++] = "partitionDisk"; args[n++] = (char *)disk; args[n++] = (char *)scheme; (...) args[n++] = NULL; char **parent_env = *_NSGetEnviron(); if (posix_spawnp(&pid, args[0], NULL, NULL, args, parent_env) != 0) return 1; if (waitpid(pid, &status, 0) < 0) return 1; return 0; Question Are there any system protections against running it like so? What could I be missing? Is this a Disk Arbitration issue?
1
0
113
May ’25
Do i need to free memory for C strings obtained from xpc_dictionary_get_string?
I am using C APIs for XPC communication. When my XPC server gets a xpc_dictionary as a message, I use xpc_dictionary_get_string to get the string which is of type const char*. Afterwards, when I try to free up the memory for the string, I get an error. I could not find any details on why this happens. Does XPC handle the lifecycle of these C strings ? I did some tests to see the behaviour. The following code snippet prints a string temp before and after releasing the dictionary memory. char* string = "dummy-string"; xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(dict, "str", string); const char* temp = xpc_dictionary_get_string(reply, "str"); printf("temp before release: %s\n", temp); xpc_release(reply); printf("temp after release: %s\n", temp); output: # temp before release: dummy-string # temp after release: I tried to free the variable temp before and after releasing dict . char* string = "dummy-string"; xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(dict, "str", string); const char* temp = xpc_dictionary_get_string(dict, "str"); printf("temp before release: %s\n", temp); free((void *)temp); // case 1 xpc_release(dict); // free((void *)temp); // case 2 printf("temp after release: %s\n", temp); in both the cases i got the output: # temp before release: dummy-string # app(18502,0x1f02fc840) malloc: Double free of object 0x145004a20 # app(18502,0x1f02fc840) malloc: *** set a breakpoint in malloc_error_break to debug # SIGABRT: abort # PC=0x186953720 m=0 sigcode=0 # signal arrived during cgo execution # ... # ...
2
0
416
Feb ’25
BGAppRefreshTask Canceled Immediately by dasd in Network Extension
Dear Apple Support Team, My app, io.cylonix.sase, has a BGAppRefreshTask (io.cylonix.sase.ios.refresh) that is canceled by dasd ~9ms after submission from a Network Extension. Please help identify the cause and suggest a solution. App Details: App ID: io.cylonix.sase iOS Version: 17.1.1 (iPhone Xs Max) Network Extension: saseWgNetworkExtension with packet-tunnel-provider entitlement Use Case: VPN app; Network Extension records file receipts in shared group UserDefaults and schedules BGAppRefreshTask to wake the main app. App Usage: High (frequently used) System State: Sufficient resources (not low on battery or memory) Issue: The task is submitted but canceled immediately with priority 10. It has never run, so rate-limiting is not an issue. ` debug 22:09:37.952749-0700 dasd Best binding found for evaluator 0x16d541720: &lt;private&gt; debug 22:09:37.954483-0700 dasd Invoking selector backgroundTaskSchedulerPermittedIdentifiersWithContext:tableID:unitID:unitBytes: on &lt;LSApplicationRecord 0x724844650&gt; default 22:09:37.955563-0700 dasd CANCELED: bgRefresh-io.cylonix.sase.ios.refresh:ABDAFA at priority 10 &lt;private&gt;!
6
0
162
May ’25
iOS BGProcessingTask + Background Upload Not Executing Reliably on TestFlight (Works in Debug)
iOS BGProcessingTask + Background Upload Not Executing Reliably on TestFlight (Works in Debug) Description: We are facing an issue with BGTaskScheduler and BGProcessingTask when trying to perform a background audio-upload flow on iOS. The behavior is inconsistent between Debug builds and TestFlight (Release) builds. Summary of the Problem Our application records long audio files (up to 1 hour) and triggers a background upload using: BGTaskScheduler BGProcessingTaskRequest Background URLSession (background with identifier) URLSession background upload task + AppDelegate.handleEventsForBackgroundURLSession In Debug mode (Xcode → Run on device), everything works as expected: BGProcessingTask executes handleEventsForBackgroundURLSession fires Background URLSession continues uploads reliably Long audio files successfully upload even when the app is in background or terminated However, in TestFlight / Release mode, the system does not reliably launch the BGProcessingTask or Background URLSession events. Technical Details We explicitly register BGTaskScheduler: BGTaskScheduler.shared.register( forTaskWithIdentifier: "example.background.process", using: nil ) { task in self.handleBackgroundProcessing(task: task as! BGProcessingTask) } We schedule it using: let request = BGProcessingTaskRequest(identifier: "example.background.process") request.requiresNetworkConnectivity = true request.requiresExternalPower = false try BGTaskScheduler.shared.submit(request) We also use Background URLSession: let config = URLSessionConfiguration.background(withIdentifier: sessionId) config.sessionSendsLaunchEvents = true config.isDiscretionary = false AppDelegate.handleEventsForBackgroundURLSession is implemented correctly and works in Debug. Issue Observed (TestFlight Only) In TestFlight builds: BGProcessingTask rarely triggers, or the system marks it as NO LONGER RUNNING. Background upload tasks sometimes never start or complete. No logs appear from our BGProcessingTask handler. system logs show messages like: NO LONGER RUNNING bgProcessing-example.background.process Tasks running in group [com.apple.dasd.defaultNetwork] are 1! This occurs most frequently for large audio uploads (30–60 minutes), while small files behave normally. What We Have Verified Proper Info.plist values: Permitted background modes: processing, audio, fetch BGTaskSchedulerPermittedIdentifiers contains our identifier BGProcessingTask is being submitted successfully (no errors) App has microphone permission + background audio works Device plugged/unplugged doesn’t change outcome Key Question for Apple We need clarification on: Why BGProcessingTask behave differently between Debug and TestFlight builds? Are there additional restrictions or heuristics (related to file size, CPU usage, runtime, network load, or power constraints) that cause BGProcessingTask to be throttled or skipped in Release/TestFlight? How can we guarantee a background upload continues reliably for large files (100MB–500MB) on TestFlight and App Store builds? Is there an Apple-recommended pattern to combine BGProcessingTask + Background URLSession for long-running uploads? Expected Result Background uploads should continue reliably for long audio files (>30 minutes) when the app goes to background or is terminated, in the same way they currently function in Debug builds.
1
0
84
3w
LaunchAgent can't connect to CloudKit daemon
For this code: let status = try await container.accountStatus() Seeing this error: 2025-05-08 15:32:00.945731-0500 localhost myAgent[2661]: (myDaemon.debug.dylib) [com.myDaemon.cli:networking] Error Domain=CKErrorDomain Code=6 "Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error." UserInfo={NSLocalizedDescription=Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error., CKRetryAfter=5, CKErrorDescription=Error connecting to CloudKit daemon. This could happen for many reasons, for example a daemon exit, a device reboot, a race with the connection inactivity monitor, invalid entitlements, and more. Check the logs around this time to investigate the cause of this error., NSUnderlyingError=0x600001bfc270 {Error Domain=NSCocoaErrorDomain Code=4099 UserInfo={NSDebugDescription= I initially started the this process as System Daemon to see what would happen (which obviously does not have CloudKit features). Then moved it back to /Library/LaunchAgents/ and can't get rid of that error. I see also following message from CloudKit daemon: Ignoring failed attempt to get container proxy for &lt;private&gt;: Error Domain=NSCocoaErrorDomain Code=4099 UserInfo={NSDebugDescription=&lt;private&gt;} Automatically retrying getting container proxy due to error for &lt;private&gt;: Error Domain=NSCocoaErrorDomain Code=4099 UserInfo={NSDebugDescription=&lt;private&gt;} XPC connection interrupted for &lt;private&gt; And this error for xpc service: [0x130e074b0] failed to do a bootstrap look-up: xpc_error=[3: No such process] If I start the same cli process directly from XCode, then it works just fine.
3
0
147
May ’25
[MacOS] Accessing underlying pthread or mach thread of NSThread
Is the title possible ? I tried [[thread valueForKey:@"_private"] valueForKey:@"tid"] but the tid was not kvc compliant. private apis are alright because this is just for testing remote process thread creation. I already have a working method but it has hardcoded assembly so you can't do anything else. this question is mainly for Quinn (figured he may know something about this)
2
0
320
Feb ’25
Some questions about how to use the Background Assets capability on iOS
Regarding the Background Assets capability on iOS: In the install scenario, resources defined as the "install" type are incorporated into the App Store download progress. Do resources of the "update" type in the update scenario also get incorporated into the App Store download progress in the same way? If an exception occurs during the download of install-type resources and the download cannot proceed further, will the system no longer actively block users from launching the app and instead enable the launch button? Currently, if a user has enabled automatic updates on their device, after the app is updated and released on the App Store, will the Background Assets download start immediately once the automatic update completes? Or does Background Assets have its own built-in scheduling logic that prevents it from running concurrently with the automatic update?
1
0
52
1w
SMAppService getting notified when status changes externally (from System Settings)
Say I want to sync a toggle in my app with SMAppService's .status property. If the status changes from my app I can track it. But if user toggles it from System Settings, I don't see a notification so then the UI in my app is out of date. The status property is not key value observable and there doesn't appear to be a SMAppServiceStatusDidChangeNotification ? I can re-read it every time my app will become active but feels kind of wrong to do it this way.
2
0
79
May ’25
Re-enrolling a LaunchDaemon, does it require user auth?
I am building an app that uses the SMAppService to register a LaunchDaemon that is bundled with my .app. I've got a priming flow created which walks the user through approving the service so that it will start on login. However, I need to also be able to upgrade this background service if the user updates the app. To do this, I think I need to call unregisterAndReturnError and then registerAndReturnError. From my testing, this seems to work correctly, but I have a concern. Will the user ever be prompted to re-authorize the LaunchDaemon that I am registering? If so, under what circumstances will that happen, and what does it look like (so that I can guide the user through it)?
5
0
294
May ’25
Getting Progress from long running process
I have been working on updating an old app that makes extensive use of Objective-C's NSTask. Now using Process in Swift, I'm trying to gather updates as the process runs, using readabilityHandler and availableData. However, my process tends to exit before all data has been read. I found this post entitled "Running a Child Process with Standard Input and Output" but it doesn't seem to address gathering output from long-running tasks. Is there a straightforward way to gather ongoing output from a long running task without it prematurely exiting?
10
0
166
May ’25
What DispatchQueues should i use for my app's communication subsystem?
We would be creating N NWListener objects and M NWConnection objects in our process' communication subsystem to create server sockets, accepted client sockets on server and client sockets on clients. Both NWConnection and NWListener rely on DispatchQueue to deliver state changes, incoming connections, send/recv completions etc. What DispatchQueues should I use and why? Global Concurrent Dispatch Queue (and which QoS?) for all NWConnection and NWListener One custom concurrent queue (which QoS?) for all NWConnection and NWListener? (Does that anyways get targetted to one of the global queues?) One custom concurrent queue per NWConnection and NWListener though all targetted to Global Concurrent Dispatch Queue (and which QoS?)? One custom concurrent queue per NWConnection and NWListener though all targetted to single target custom concurrent queue? For every option above, how am I impacted in terms of parallelism, concurrency, throughput &amp; latency and how is overall system impacted (with other processes also running)? Seperate questions (sorry for the digression): Are global concurrent queues specific to a process or shared across all processes on a device? Can I safely use setSpecific on global dispatch queues in our app?
13
0
967
Jan ’25
XPC activity doesn’t fire while main app is closed
Hi, I have a sandboxed app with a bundled sandboxed XPC service. When it’s launched, the XPC service registers a repeating XPC activity with the system. The activity’s handler block does get called regularly like I’d expect, but it stops being called once the main app terminates. What’s the recommended way to fix this issue? Could I have a bundled XPC service double as a launch agent, or would that cause other problems?
4
0
150
May ’25