Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creating channel blocks main thread #20

Open
danipralea opened this issue Dec 1, 2016 · 18 comments
Open

Creating channel blocks main thread #20

danipralea opened this issue Dec 1, 2016 · 18 comments

Comments

@danipralea
Copy link
Contributor

danipralea commented Dec 1, 2016

Hello again @danielrhodes
I found out that creating a Channel sometime blocks the main thread.
My code:

webSocketsRoomChannel = SocketRouter.shared.client.create(WebSocketEventsChannel.channel, identifier: channelIdPayload, autoSubscribe: true, bufferActions: true)

The stack:
screenshot 2016-12-01 18 39 38

I will come up with a fix if I find one
Update:
screenshot 2016-12-01 19 08 48

Is this an infinite cycle?

@danielrhodes
Copy link
Owner

Have you retained the channel anywhere?

@danipralea
Copy link
Contributor Author

danipralea commented Dec 1, 2016

it's a simple variable on the conversations view controller

var webSocketsRoomChannel: Channel?

@danielrhodes
Copy link
Owner

What version are you using? Something similar to the above used to happen, especially if the the channel had not been retained anywhere. But that should be fixed now.

@danipralea
Copy link
Contributor Author

danipralea commented Dec 1, 2016

- ActionCableClient (0.2.2):
    - Starscream (~> 2.0.1)

The only problem is that the issue is not consistent. It's very rare for me, but of course - very often for the testers

@danielrhodes
Copy link
Owner

Do you have some sample code for how you are setting up everything and in the view controller. Would be happy to take that and test it on my end.

@danipralea
Copy link
Contributor Author

danipralea commented Dec 2, 2016

class ConversationViewController: UIViewController {
    var webSocketsRoomChannel: Channel?
    func subscribeToWebsocketChannel() {
        guard SocketRouter.shared.isConnected, let channel = viewModel.channel, let identifier = channel.identifier else {
            print("socket is not connected or data is not sufficient")
            return
        }
        let channelIdPayload = ["channel_id" : identifier]
       webSocketsRoomChannel = SocketRouter.shared.client.create(WebSocketEventsChannel.channel, identifier: channelIdPayload, autoSubscribe: true, bufferActions: true)
        
        // Receive a message from the server. Typically a Dictionary.
        webSocketsRoomChannel?.onReceive = { (JSONResponse : Any?, error : Error?) in
            print("Received", JSONResponse ?? "N/A", error ?? "no error")
            
        }
        
        // A channel has successfully been subscribed to.
        webSocketsRoomChannel?.onSubscribed = {
            print("Yay! You're subscribed!")
        }
        
        // A channel was unsubscribed, either manually or from a client disconnect.
        webSocketsRoomChannel?.onUnsubscribed = {
            print("Unsubscribed")
        }
        
        // The attempt at subscribing to a channel was rejected by the server.
        webSocketsRoomChannel?.onRejected = {
            print("Rejected")
        }
    }
    
    func removeWebSocketsNotifications() {
        // Remove channel subscription
        webSocketsRoomChannel?.unsubscribe()
        // Stop listening notification
        NotificationCenter.default.removeObserver(self)
    }
    
    func addWebSocketsNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(webSocketConnected(_:)), name: NSNotification.Name(rawValue: WebsocketDidConnectNotification), object: nil)
        
        NotificationCenter.default.addObserver(self, selector: #selector(webSocketDisconnected(_:)), name: NSNotification.Name(rawValue: WebsocketDidDisconnectNotification), object: nil)
    }
    
    func webSocketConnected(_ notification: NSNotification) {
        print("\(self.className): web socket connected")
        if let roomChannel = webSocketsRoomChannel, roomChannel.isSubscribed == false {
            subscribeToWebsocketChannel()
        }
    }
    
    func webSocketDisconnected(_ notification: NSNotification) {
        print("\(self.className): web socket disconnected")
    }
}

@danielrhodes
Copy link
Owner

danielrhodes commented Dec 6, 2016

So from the looks of Instruments above, it seems like either you were running the application for a very long time or you might be calling subscribeToWebsocketChannel very very often. The code you highlighted is simply doing a Dictionary lookup, which isn't blocking, and is O(1), so I'm not sure how that would be causing a problem.

I did, however, find that there was some JSON encoding being done from the calling thread (which I assume is main in this case), and so for whatever reason, this might be the source of the time being taken above (sometimes Instruments only makes an educated guess about where in the code things happen).

Try out the json_encoding_on_calling_thread_fix branch and tell me how that worked for you.

https://github.com/danielrhodes/Swift-ActionCableClient/tree/json_encoding_on_calling_thread_fix

In your Podfile, you can do the following:

pod 'ActionCableClient', :git => 'https://github.com/danielrhodes/Swift-ActionCableClient.git', :branch => 'json_encoding_on_calling_thread_fix'

Note that I had to change the API slightly, but now there is a callback on the channel.action(:) method telling you if the action was successfully sent or an error occurred. There is an example on the main README and in the header docs.

@danipralea
Copy link
Contributor Author

danipralea commented Dec 6, 2016

I have it again:
screenshot 2016-12-06 19 06 08

I will try with the json_encoding_on_calling_thread_fix branch as well and let you know how it goes.
Thanks a lot for the support.

PS: subscribeToWebsocketChannel is done only once
PPS: The problem is not with sending an action, it's with creating the channel

@danipralea
Copy link
Contributor Author

danipralea commented Dec 7, 2016

@danielrhodes the new method of sending an action doesn't call the callback when an error occurs

The old way of doing it was working fine (throwing an error)

@danielrhodes
Copy link
Owner

Do you mean on the server side or client side? If the error is on the server side, there is no way for the client to know an error occurred.

@danipralea
Copy link
Contributor Author

danipralea commented Dec 7, 2016

on client side. all the problems I have are client-side.

@danielrhodes
Copy link
Owner

What was the specific error you were looking for?

@danipralea
Copy link
Contributor Author

creating a channel blocks the main thread

@danielrhodes
Copy link
Owner

danielrhodes commented Dec 9, 2016

How many times are you calling this over the course of runtime? And how many total channels do you have? I ran the example app and put client.create in a 5000 iteration loop (on the main thread) and profiled it to see if anything weird came up.

screen shot 2016-12-08 at 17 19 42

Nothing there indicates anything amiss given how many times it was called. In fact, most of that would only occur once because it checks to see if there's already a channel. So that reduces the performance bottleneck to doing a lookup in a dictionary. This seems consistent with your Instruments trace, but I can't see how that would come up if something strange weren't also occurring.

Perhaps a Dictionary lookup could cause a performance problem if there were an unusually large number of objects or were called in a very tight loop. Swift does not necessarily have the most optimized version of this, but it's certainly not a blocking operation.

@danipralea
Copy link
Contributor Author

I'm only doing it once

@danielrhodes
Copy link
Owner

danielrhodes commented Dec 9, 2016

So you only go into that ViewController once the entire time the app is running? And it completely blocks the main thread?

From looking at your code above, can you try calling subscribeToWebsocketChannel() in viewDidLoad and removing those NSNotification observers in addWebSocketsNotifications. You can subscribe to a channel before the client connects and it will automatically subscribe once a connection occurs, so most that logic is unnecessary.

@danipralea
Copy link
Contributor Author

I will definitely try that.
I have a list of channels, which I can select. And sometimes, very randomly - it completely freezes like that.

Thank you for your support. I will try and let you know how it goes

@danipralea
Copy link
Contributor Author

@danielrhodes there's a retain cycle in the create function:
screenshot 2017-01-19 16 35 43
screenshot 2017-01-19 16 36 53

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants