How can I get DispatchQueue() code to cleanup on application exit - swift

I have a process running in a DispatchQueue which creates a temporary file. The file is deleted in a defer block so the clean up occurs regardless of whether an error is thrown or I just return from process() normally. See code below
func process() throws {
let file = createTemporaryFile()
defer {
deleteTemporaryFile(file)
}
try callCodeThatMightThrowErrors()
}
dispatchQueue.async {
do {
try process()
} catch {
dealWithError()
}
}
Now this all works fine until I quit my application. If I have a DispatchQueue currently in the middle of the process() function the defer block is not run and the file is not deleted and I leave a temporary file in the system. Is there any way I can get this defer block to be called? I would rather not have to store a global array of temporary files that need to be deleted at application exit.

You need to either:
a) prevent your app from terminating while your process is running, OR
b) know when termination is happening and cancel your process
Either way, NSApplicationDelegate has a method (applicationShouldTerminate) to ask you if it can terminate. While your process is running, you should return NSTerminateLater, and then when the process is done, call replyToApplicationShouldTerminate.
You should also make sure that sudden termination is disabled while your process is running so that you actually get the termination delgation. See ProcessInfo disableSuddenTermination`

Do you really have to clean up on app exit though? If you are sure that no temporary file can exist on app start then add your cleanup code there. That way no matter how a user or the OS terminated the app, your cleanup code would run. Of course, if the process is not terminated you can clean the temp files where you do it now.

Related

How to interrupt the awaiting in a `for-await` loop / Swift

I'm wondering how to stop a for-await loop from "awaiting".
Here's the loop. I use it to listen to new Transactions with storekit2:
transactionListener = Task(priority: .background) { [self] in
// wait for transactions and process them as they arrive
for await verificationResult in Transaction.updates {
if Task.isCancelled { print("canceled"); break }
// --- do some funny stuff with the transaction here ---
await transaction.finish()
}
print("done")
}
As you can see, Transaction.updates is awaited and returns a new transaction whenever one is created. When the App finishes, I cancel the loop with transactionListener.cancel() - but the cancel is ignored as the Transaction.updates is waiting for the next transaction to deliver and there's no direct way in the API to stop it (like, e.g. Task.sleep() does)
The issues starts, when I run unit-tests. The listener from a previous test is still listening while the next test is already running. This produces very unreliable test results and crashes our CI/CD pipeline. I nailed it down to the shown piece of code and the described issue.
So, question: Is it possible to interrupt a for-await loop from awaiting? I have something like the Unix/Linux command kill -1 in mind. Any ideas?

Background Process as NSOperation or Thread to monitor and update File

I want to check if a pdf file is changed or not, and if is changed i want to update the corresponding view. I don't know if it's more suitable to use a background process as a Thread or as an NSOperation to do this task. The Apple Documentation says: "Examples of tasks that lend themselves well to NSOperation include network requests, image resizing, text processing, or any other repeatable, structured, long-running task that produces associated state or data.But simply wrapping computation into an object doesn’t do much without a little oversight".
Also, if I understood correctly from the documentation, a Thread once started can't be stopped during his execution while an NSOperation could be paused or stopped and also they could rely on dependency to wait the completion of another task.
The workflow of this task should be more or less this diagram:
Task workflow
I managed to get the handler working after the notification of type .write has been sent. If i monitor for example a *.txt file everything works as expected and i receive only one notification. But i am monitoring a pdf file which is generated from terminal by pdflatex and thus i receive with '.write' nearly 15 notification. If i change to '.attrib' i get 3 notification. I need the handler to be called only once, not 15 or 3 times. Do you have any idea how can i do it or is not possible with a Dispatch Source? Maybe there is a way to execute a dispatchWorkItem only once?
I have tried to implement it like this(This is inside a FileMonitor class):
func startMonitoring()
{
....
let fileSystemRepresentation = fileManager.fileSystemRepresentation(withPath: fileStringURL)
let fileDescriptor = open(fileSystemRepresentation, O_EVTONLY)
let newfileMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: fileDescriptor,
eventMask: .attrib,
queue: queue)
newfileMonitorSource.setEventHandler(handler:
{
self.queue.async
{
print(" \n received first write event, removing handler..." )
self.newfileMonitorSource.setEventHandler(handler: nil)
self.test()
}
})
self.fileMonitorSource = newfileMonitorSource
fileMonitorSource!.resume()
}
func test()
{
fileMonitorSource?.cancel()
print(" restart monitoring ")
startMonitoring()
}
I have tried to reassign the handler in test(), but it's not working(if a regenerate the pdf file, what is inside the new handler it's not executed) and to me, doing in this way, it seems a bit boilerplate code. I have also tried the following things:
suspend the DispatchSource in the setEventHandler of startMonitoring() (passing nil), but then when i am resuming it, i get the remaining .write events.
cancel the DispatchSource object and recall the startMonitoring() as you can see in the code above, but in this way i create and destroy the DispatchSource object everytime i receive an event, which i don't like because the cancel() function shoul be called in my case only when the user decide to disable this feauture i am implementing.
I will try to write better how the workflow of the app should be so you can have an more clear idea of what i am doing:
When the app starts, a functions sets the default value of some checkboxes of the window preference. The user can modify this checkboxes. So when the user open a pdf file, the idea is to launch in a background thread the following task:
I create a new queue call it A and launch asynch an infinite while where i check the value of the UserDefault checkboxe (that i use to reload and update the pdf file) and two things could happen
if the user set the value to off and the pdf document has been loaded there could be two situations:
if there is no current monitoring of the file (when the app starts): continue to check the checkboxe value
if there is currently a monitoring of the file: stop it
if the user set value to on and the pdf document has been loaded in this background thread (the same queue A) i will create a class Monitor (that could be a subclass of NSThread or a class that uses DispatchSourceFileSystemObject like above), then i will call startMonitoring() that will check the date or .write events and when there is a change it will call the handler. Basically this handler should recall the main thread (the main queue) and check if the file can be loaded or is corrupted and if so update the view.
Note: The infinite while loop(that should be running in the background), that check the UserDefault related to the feature i am implementing it's launched when the user open the pdf file.
Because of the problem above (multiple handlers calls), i should use the cancel() function when the user set checkboxe to off, and not create/destroy the DispatchSource object everytime i receive a .write event.

Repeat Process() n times in Swift 3

I am making a macOS app. I have a task (a script) that I am running with Process() in Swift 3. When I press a button (button.State == NSOnState), I would like the task to repeating n times, and terminate earlier until the button is pressed again (button.State == NSOffState).
I looked up how to repeat a task, and it looks possible with a simple for loop – for i in {1..n}.
Now, the problem I am having is that it doesn't seem to be possible to call a task multiple times. When I try to call the task the second time, I get an error in the console:
[General] task already launched
Here is my code:
#IBAction func buttonPressed(_ sender: Any) {
let script = "for i in {1..5}; do echo \"hi\"; done; sleep 1"
let task = Process()
task.terminationHandler = self.commandTerminationHandler
task.launchPath = "/bin/bash"
task.arguments = ["-c", script]
if button.state == NSOnState {
task.launch() // launches task
task.waitUntilExit() // waits until task has been completed (about 1 second)
task.terminate() // (should) terminate the task. (The console error occurs with or without this line)
task.launch() // tries launching the task again, but this results in the console error.
print("The task was launched twice")
} else {
// task.terminate()
}
}
I googled this error, and found [this][https://forums.macrumors.com/threads/nstask-not-terminating.1617855/#post-17677541]:
The error isn't that the task is still running. It's that the task has already run and completed, and can't be started again. You'll need to create a new NSTask object to run the task again.
So I need to make a new NSTask (or Process as of Swift 3) and keep making new ones to repeat the code forever. This sound very complicated (as if I'm using a workaround) and is probably inefficient.
Is there a better way to repeat a Process in Swift 3?
For the sake of completeness, I'd also like to mention that I considered using for i in {1..n} do ... done directly in script. This has one problem:
It doesn't look like it's possible to stop the task when the button is pressed again. This is because if I run task.terminate(), I get the error "task not launched." The only way I can stop it is by running killall bash in my Terminal, which doesn't seem like a nice solution. To do this in Xcode, I'd need make a Process to kill bash with bash... which is strange.
I'm having the same issue. A potential fix that I've come across online is to set the task to nil after terminating it, then reassigning parameters such as path, arguments, etc before running it again. However this can only be done in Objective C; Swift has very strict rules when it comes to nil (and probably for good reasons).
You can run 2 different script at the same time. One of them you want to run should be running and the other always check the another script at every turn and this should be like recursive. The another script will kill the another script until the button is pressed.
If you set your task item to a var instead of a let you can repeatedly set task to a new process object inside your loop.
var task = Process()
task.launchPath = ....
task.arguments = [...]
task = Process()
task.launchPath = ....
task.arguements = [...]

Confusion about CFNetwork, CFReadStreamOpen, and CFRunLoopRun

That sinking feeling when you realize you have no idea what's going on...
I've been using this code in my network code for almost two years without problems.
if (!CFReadStreamOpen(myReadStream)) {
CFStreamError myErr = CFReadStreamGetError(myReadStream);
if (myErr.error != 0) {
// An error has occurred.
if (myErr.domain == kCFStreamErrorDomainPOSIX) {
// Interpret myErr.error as a UNIX errno.
strerror(myErr.error);
} else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
OSStatus macError = (OSStatus)myErr.error;
}
// Check other domains.
}
}
I believe it was originally based on the code samples given here:
http://developer.apple.com/library/mac/#documentation/Networking/Conceptual/CFNetwork/CFStreamTasks/CFStreamTasks.html
I recently noticed, however, that some connections are failing, because CFReadStreamOpen returns false but the error code is 0. After staring at the above link some more, I noticed the CFRunLoopRun() statement, and added it:
if (!CFReadStreamOpen(myReadStream)) {
CFStreamError myErr = CFReadStreamGetError(myReadStream);
if (myErr.error != 0) {
// An error has occurred.
if (myErr.domain == kCFStreamErrorDomainPOSIX) {
// Interpret myErr.error as a UNIX errno.
strerror(myErr.error);
} else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
OSStatus macError = (OSStatus)myErr.error;
}
// Check other domains.
} else
// start the run loop
CFRunLoopRun();
}
This fixed the connection problem. However, my app started showing random problems - interface sometimes not responsive, or not drawing, text fields not editable, that kind of stuff.
I've read up on CFReadStreamOpen and on run loops (specifically, that the main run loop runs by itself and I shouldn't run a run loop unless I'm setting it up myself in a secondary thread - which I'm not, as far as I know). But I'm still confused about what's actually happening above. Specifically:
1) Why does CFReadStreamOpen sometimes return FALSE and error code 0? What does that actually mean?
2) What does the CFRunLoopRun call actually do in the above code? Why does the sample code make that call - if this code is running in the main thread I shouldn't have to run the run loop?
I guess I'll answer my own question, as much as I can.
1) In my code, at least, CFReadStreamOpen always seems to return false. The documentation is a bit confusing, but I read it to mean the stream wasn't opened yet, but will be open later in the run loop.
2) Most of the calls I was making were happening in the main thread, where the run loop was already running, so calling CFRunLoopRun was unnecessary. The call that was giving me problems was happening inside a block, which apparently spawned a new thread. This new thread didn't start a new run loop - so the stream would never open unless I explicitly ran the new thread's run loop.
I'm still not 100% clear on what happens if I call CFRunLoopRun() on a thread with an already running run loop, but it's obviously not good.
I ended up ditching my home-brewed networking code and switching to ASIHTTPRequest, which I was considering to do anyway.

Code with a potential deadlock

// down = acquire the resource
// up = release the resource
typedef int semaphore;
semaphore resource_1;
semaphore resource_2;
void process_A(void) {
down(&resource_1);
down(&resource_2);
use_both_resources();
up(&resource_2);
up(&resource_1);
}
void process_B(void) {
down(&resource_2);
down(&resource_1);
use_both_resources();
up(&resource_1);
up(&resource_2);
}
Why does this code causes deadlock?
If we change the code of process_B where the both processes ask for the resources in the same order as:
void process_B(void) {
down(&resource_1);
down(&resource_2);
use_both_resources();
up(&resource_2);
up(&resource_1);
}
Then there is no deadlock.
Why so?
Imagine that process A is running and try to get the resource_1 and gets it.
Now, process B takes control and try to get resource_2. And gets it. Now, process B tries to get resource_1 and does not get it, because it belongs to resource A. Then, process B goes to sleep.
Process A gets control again and try to get resource_2, but it belongs to process B. Now he goes to sleep too.
At this point, process A is waiting for resource_2 and process B is waiting for resource_1.
If you change the order, process B will never lock resource_2 unless it gets resource_1 first, the same for process A.
They will never be dead locked.
A necessary condition for a deadlock is a cycle of resource acquisitions. The first example constructs this a cycle 1->2->1. The second example acquires the resources in a fixed order which makes a cycle and henceforth a deadlock impossible.