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

Add websocket hot reloading #478

Merged
merged 4 commits into from
Oct 28, 2022
Merged

Add websocket hot reloading #478

merged 4 commits into from
Oct 28, 2022

Conversation

yissachar
Copy link
Collaborator

Adds websocket hot reloading for Android. Shaves 400-500ms off of hot reload times. For now leaving iOS as 500ms polling but will follow-up with another PR once that's working.

Note that right now there is no websocket on the server. This code will fallback to 500ms polling when we can't connect to a websocket, but also tries to make a websocket connection on each poll, in case the websocket is only down temporarily (e.g. in the case of a dev leaving emulator running, stopping hot reload server, then restarting, we want to make sure we always upgrade to websocket where possible to get the fastest speeds).

.url("ws://${uri.host}:${uri.port}/ws")
.build()

val wsFlow:Flow<String> = callbackFlow {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this code feels super wonky. I'm not a flows expert but surely there's a better way to do this?


val uri = URI(manifestUrl)
val request: Request = Request.Builder()
.url("ws://${uri.host}:${uri.port}/ws")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this approach of hardcoding the expected websocket URL, but I'm not sure where we would expose it as a config, and it doesn't seem like a huge problem since we're not likely to change the Zipline server often.

import okhttp3.WebSocketListener

@OptIn(ExperimentalCoroutinesApi::class)
internal actual fun hotReloadFlow(flow: Flow<String>): Flow<String> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would like to add some tests to validate this behaviour if I get the time.

ws?.close(1000, null)
}
}
wsFlow.onEach { emit(it) }.collect()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can delete this and switch to flattMapLatest from which you can return the inner flow and it will be automatically subscribed to.

Comment on lines 45 to 47
iosMain {
dependsOn(nativeMain)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this needed? The iosMain source set and its dependence on nativeMain should already have been configured globally for all multiplatform modules.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to add it to resolve some error, but doesn't look like that's the case anymore - possibly because I was actually working on a really old branch, and then rebased against latest.


var currentlyConnectedToWebSocket = false

val uri = URI(manifestUrl)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jesee would probably want you to use OkHttp's HttpUrl here!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plus you don't actually have to use the ws scheme with OkHttp so you can do HttpUrl.get(manifestUrl).resolve("/ws") and be done.

Comment on lines +41 to +44
val wsClient: OkHttpClient = OkHttpClient.Builder()
.readTimeout(0, TimeUnit.MILLISECONDS)
.connectTimeout(1, TimeUnit.SECONDS)
.build()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new client is being created for every upstream emission when what you probably want is to share this instance for the lifetime of the downstream subscription. You can do that with a level of nesting such as

return flow {
  val client = OkHttpClient.Builder()...build()

  emitAll(upstream.flatMapLatest { logic... })
}

@yissachar
Copy link
Collaborator Author

Looks like MockWebServer doesn't have proper support for testing websockets (or at least I couldn't figure it out), so I'll just merge and try to talk to Jesse when he's back to see if there's a different way to test this.

@yissachar yissachar merged commit 7716f9d into trunk Oct 28, 2022
@yissachar yissachar deleted the yissachar/add-websocket branch October 28, 2022 15:24
Copy link
Collaborator

@swankjesse swankjesse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE!

while (true) {
if (!currentlyConnectedToWebSocket) {
trySendBlocking(manifestUrl)
println("we are here")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this snuck in

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was removed in a follow-up

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

Successfully merging this pull request may close these issues.

3 participants