How to interrupt the awaiting in a `for-await` loop / Swift - 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?

Related

GoogleWebRTC hangs (freezes) the main thread in swift native app (OpenVidu)

We have hanging problem (the app freezes due of main thread lock) with our iOS (swift) native app with OpenVidu implementation (which uses GoogleWebRTC under the hood). The specific conditions required: need to join existing room with at least 8 participants already streaming. With 6 participants it happens less often and almost never with less than 6. It doesn't hang if participants join one by one, only if you join the room with all other participants already streaming. This indicates concurrent nature of the issue.
The GoogleWebRTC hangs on setRemoteDescription call:
func setRemoteDescription(sdpAnswer: String) {
let sessionDescription: RTCSessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: sdpAnswer)
self.peerConnection!.setRemoteDescription(sessionDescription, completionHandler: {(error) in
print("Local Peer Remote Description set: " + error.debugDescription)
})
}
As you can see on the screenshot above, the main thread hangs on __psynch_cvwait. No any other threads seems being locked. The lock never releases leaving the app completely frozen.
In the attempt to solve it I was trying the following:
I moved OpenVidu signaling server processing (RPC protocol) from the main thread into separate threads. This only caused the lock now occurs in the one of separate threads I created. It now doesn't block the UI, but blocks OV signaling. The problem persists.
I added the lock to process each signaling event (participant join event, publish video, etc) synchronously (one by one). This doesn't help either (it actually made the situation worse).
Instead of using GoogleWebRTC v. 1.1.31999 from Cocoapods, I downloaded the latest GoogleWebRTC sources, built them in release configuration and included into my project. This didn't help to solve the issue.
Any suggestions/comments would be appreciated.
Thanks!
EDIT 1:
The signaling_thread and worker_thread are both is waiting for something in the same kind of lock. Nothing of them execute any of my code at the moment of the lock.
I also tried to run in DEBUG build of GoogleWebRTC, in this case no locks happen, but everything works much slower (which is OK for debug, but we can't use this in Production).
EDIT 2:
I tried to wrap in additional DispatchQueue for offer and setLocalDescription callbacks, but this changes nothing. The problem still well reproducible (almost 100% of time, if I have 8 participants with streams):
self.peerConnection!.offer(for: constrains) { (sdp, error) in
DispatchQueue.global(qos: .background).async {
guard let sdp = sdp else {
return
}
self.peerConnection!.setLocalDescription(sdp, completionHandler: { (error) in
DispatchQueue.global(qos: .background).async {
completion(sdp)
}
})
}
}
The WebRTC Obj-C API can be called from any thread, but most method calls are passed to WebRTC's internal thread called signalling thread.
Also, callbacks/observers like SetLocalDescriptionObserverInterface or RTCSetSessionDescriptionCompletionHandler are called from WebRTC on the signaling thread.
Looking at the screenshots, it seems that the signaling thread is currently blocked and can no longer call WebRTC API calls.
So, to avoid deadlocks, it's a good idea to create your own thread / dispatch_queue and handle callbacks.
See
https://webrtc.googlesource.com/src/+/0a52ede821ba12ee6fff6260d69cddcca5b86a4e/api/g3doc/index.md and
https://webrtc.googlesource.com/src/+/0a52ede821ba12ee6fff6260d69cddcca5b86a4e/api/g3doc/threading_design.md
for details.
After the comment from OpenVidu team, the problem was solved by adding 100ms delay between adding participants who are already in the room. I would consider this more like a hack than a real solution, but I can confirm that it works both in test and in Production environment:
DispatchQueue.global(qos: .background).async {
for info in dict.values {
let remoteParticipant = self.newRemoteParticipant(info: info)
if let streamId = info.streamId {
remoteParticipant.createOffer(completion: {(sdp) in
self.receiveVideoFrom(sdp: sdp, remoteParticipant: remoteParticipant, streamId: streamId)
})
} else {
print("No streamId")
}
Thread.sleep(forTimeInterval: 0.1)
}
}

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

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.

Generic Grand Central Dispatch

I have code set up like below. It is my understanding that queue1 should finish all operations THEN move to queue2. However, as soon as my async operation starts, queue2 begins. This defeats the purpose of GCD.. what am I doing wrong? This outputs:
did this finish
queue2
then some time later, prints success from image download
..I want to make it clear that if I put in other code in queue1, such as print("test") or a loop 0..10 printing i, all those operations will complete before moving to queue2. It seems the async download is messing with it, how can I fix this? There is no documentation anywhere, I used This very hepful guide from AppCoda http://www.appcoda.com/grand-central-dispatch/
let queue1 = DispatchQueue(label: "com.matt.myqueue1")
let queue2 = DispatchQueue(label: "com.matt.myqueue2")
let group1 = DispatchGroup()
let group2 = DispatchGroup()
let item = DispatchWorkItem{
// async stuff happening like downloading an image
// print success if image downloads
}
queue1.sync(execute: item)
item.notify(queue1, execute: {
print("did this finish?")
})
queue2.sync {
print("queue2")
}
let item = DispatchWorkItem{
// async stuff happening like downloading an image
// print success if image downloads
}
OK, defines it, but nothing runs yet.
queue1.sync(execute: item)
Execute item and kick off its async events. Immediately return after that. Nothing here says "wait for those unrelated asynchronous events to complete." The system doesn't even have a way to know that there are additional async calls inside of functions you call. How would it know whether object.doit() includes async calls or not (and whether those are async calls you meant to wait for)? It just knows when item returns, continue.
This is what group1 is supposed to be used for (you don't seem to use it for anything). Somewhere down inside these "async stuff happening" you're supposed to tell the system that it finished by leaving the group. (I have no idea what group2 is for. It's never used either.)
item.notify(queue1, execute: {
print("did this finish?")
})
item already finished. We know it has to have finished already, because it was run with sync, and that doesn't return until its item has. So this block will be immediately scheduled on queue1.
queue2.sync {
print("queue2")
}
Completely unrelated and could run before or after the "did this finish" code, we schedule a block on queue2.
What you probably meant was:
let queue1 = DispatchQueue(label: "com.matt.myqueue1")
let group1 = DispatchGroup()
group1.enter()
// Kick off async stuff.
// These usually return quickly, so there's no need for your own queue.
// At some point, when you want to say this is "done", often in some
// completion handler, you call group1.leave(), for example:
... completionHandler: { group1.leave() }
// When all that finishes, print
group.notify(queue: queue1) { print("did this finish?") }
EVERYTHING is initially queued from the main queue, however at some point you switch from main queue to a background queue and you should NOT expect a synchronized queue would wait for what is has enqueued on another queue. They are irrelevant. If that was the case then always and always no matter what, everything is to wait for whatever it asked to run.*
so here's what I see is happening.
queue1 is happily finished, it has done everything it was suppose to by enqueuing item on another queue <-- that's all it was suppose to do. Since the 'async stuff' is async... it won't wait for it to finish. <-- actually if you use breakpoints inside the async you would see that the breakpoints would jump to } meaning they don't wait for a background queue they just jump to the end of the queue, since they are no longer on the main thread
then since it was a sync queue it wall wait till it's done. Once done it will go through its notify...now here's where it get's tricky: depending on how fast what you do in the async... 'print success' will either get called first or "queue2" though here obviously queue2 is returning/finishing sooner.
similar to what you see in this answer.
*: A mother (main queue) tells it's child1 to your room and bring book1 back, then tells child2 to your room and bring book2 back, then tells child3 to your room and bring book3 back. Each child is being ran from its own queue ( not the mother's queue).
The mother doesn't wait for child1 to come back...so it can tell child2 to go. The mother only tells child 1 go...then child2 go...then child 3 go.
However child2 is not told (to go) before child 1, nor child3 is told before child2 or child1 <-- this is because of the serial-ness of main queue. They get dispatched serially, but their completion order depends on how fast each child/queue finishes

boost::asio::io_service::run() is not exiting when i call boost::asio::io_serive::stop()

Hi I having written one simple application which uses the asynchronous socket functions. I am facing some problems while closing the socket.
I am using 5 second timer before calling the async_connect on the socket. In some cases the connection is not happening and timer expires. When timer is expired I am closing the socket tcp_socket.close(). But the thing is my connection callback handler is not at all called with the boost::asio::error::operation_aborted error when i tried to cancel instead of close. The same thing is happening for the next all the async connection invokes.
Eventhough I am closing the tcp socket and destroying the client_session object join() call on the created thread is not coming out means io_service::run() is still running not exiting...:-( I don't know why this is happening... tried lot of other ways still facing the same problem.
I am not getting what is the problem, all suggestions and solutions will be appreciated.
My real code some what look like this.
class client_session
{
public:
client_session(boost::asio::io_service& io_service_ )tcp_socekt_(io_service_),
timer_(io_service_)
{
}
~client_session()
{
tcp_socket_.close();
}
void OnTimerExpired(const boost::system::error_code& err)
{
if( err ) tcp_socket_.close();
}
//Its just for example this will be called from upper layer of my module. giving some information about the server.
void connect()
{
//Here am starting the timer
timer_.expires_from_now(boost::posix_time::seconds(2));
timer_.async_wait(boost::bind(&OutgoingSession::OnTimerExpiry, this,PLACEHLDRS::error));
.......
tcp_socket_.async_connect(iterator->endpoint(), boost::bind( &OutgoingSession::handle_connect, this, _1, iterator));
......
}
void OnConnect(const boost::system::error_code& err)
{
//Cancelling the timer
timer_.cancel();
.....
//Register for write to send the request to server
......
}
private:
tcp::socket tcp_socket_;
deadline_timer timer_;
}
void main()
{
boost::asio::io_service tcp_io_service;
boost::asio::io_service::work tcp_work(tcp_io_service);
boost::thread* worker = new boost::thread(&boost::asio::io_service::run,&tcp_io_service);
client_session* csession = new client_session(tcp_io_service);
csession->connect();
sleep(10);
tcp_io_service.stop();
delete csession;
worker.join(); //Here it not coming out of join because io_service::run() is not exited yet.
cout<<"Termination successfull"<<endl;
}
There seem to be a couple of different things wrong with the posted code. I would suggest starting with smaller steps i.e. along the lines of
start and stop asio worker thread cleanly ( see explanation below )
add code to start timer: handle OnTimerExpired correctly, check error code
add in code for async_connect: when connect handler is called, cancel timer and check error code.
add in other asynchronous operations, etc.
For one, when you cancel the timer in the connect handler, the OnTimerExpired handler will be invoked with boost::asio::operation_aborted and then you close the socket, which is probably not what you want to do.
Further, you give the io_service work, yet still call stop. Generally if you give the io_service work, you want to stop the execution thread by removing the work (e.g. This can be accomplished by means of storing work in a smart pointer and resetting it) and letting the currently started asynchronous operations finish cleanly.

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.