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

Cleanup mpp utils #5980

Merged
merged 5 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion libraries/apollo-mpp-utils/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Module apollo-mpp-utils

apollo-mpp-utils contains a few utilities for working with multiplatform projects.
apollo-mpp-utils contains a few utilities for working with multiplatform projects.

As of June 2024, it only contains `currentTimeMillis`. In most cases, we should replace that with `kotlin.time.TimeMark` but it's still used in `HttpInfo` as absolute timestamps, and we can't remove it just yet.
14 changes: 0 additions & 14 deletions libraries/apollo-mpp-utils/api/apollo-mpp-utils.api
Original file line number Diff line number Diff line change
@@ -1,18 +1,4 @@
public final class com/apollographql/apollo3/mpp/Platform : java/lang/Enum {
public static final field Js Lcom/apollographql/apollo3/mpp/Platform;
public static final field Jvm Lcom/apollographql/apollo3/mpp/Platform;
public static final field Native Lcom/apollographql/apollo3/mpp/Platform;
public static final field WasmJs Lcom/apollographql/apollo3/mpp/Platform;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/apollographql/apollo3/mpp/Platform;
public static fun values ()[Lcom/apollographql/apollo3/mpp/Platform;
}

public final class com/apollographql/apollo3/mpp/UtilsKt {
public static final fun currentThreadId ()Ljava/lang/String;
public static final fun currentThreadName ()Ljava/lang/String;
public static final fun currentTimeFormatted ()Ljava/lang/String;
public static final fun currentTimeMillis ()J
public static final fun platform ()Lcom/apollographql/apollo3/mpp/Platform;
}

14 changes: 0 additions & 14 deletions libraries/apollo-mpp-utils/api/apollo-mpp-utils.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,4 @@
// - Show declarations: true

// Library unique name: <com.apollographql.apollo3:apollo-mpp-utils>
final enum class com.apollographql.apollo3.mpp/Platform : kotlin/Enum<com.apollographql.apollo3.mpp/Platform> { // com.apollographql.apollo3.mpp/Platform|null[0]
enum entry Js // com.apollographql.apollo3.mpp/Platform.Js|null[0]
enum entry Jvm // com.apollographql.apollo3.mpp/Platform.Jvm|null[0]
enum entry Native // com.apollographql.apollo3.mpp/Platform.Native|null[0]
enum entry WasmJs // com.apollographql.apollo3.mpp/Platform.WasmJs|null[0]
final fun valueOf(kotlin/String): com.apollographql.apollo3.mpp/Platform // com.apollographql.apollo3.mpp/Platform.valueOf|valueOf#static(kotlin.String){}[0]
final fun values(): kotlin/Array<com.apollographql.apollo3.mpp/Platform> // com.apollographql.apollo3.mpp/Platform.values|values#static(){}[0]
final val entries // com.apollographql.apollo3.mpp/Platform.entries|#static{}entries[0]
final fun <get-entries>(): kotlin.enums/EnumEntries<com.apollographql.apollo3.mpp/Platform> // com.apollographql.apollo3.mpp/Platform.entries.<get-entries>|<get-entries>#static(){}[0]
}
final fun com.apollographql.apollo3.mpp/currentThreadId(): kotlin/String // com.apollographql.apollo3.mpp/currentThreadId|currentThreadId(){}[0]
final fun com.apollographql.apollo3.mpp/currentThreadName(): kotlin/String // com.apollographql.apollo3.mpp/currentThreadName|currentThreadName(){}[0]
final fun com.apollographql.apollo3.mpp/currentTimeFormatted(): kotlin/String // com.apollographql.apollo3.mpp/currentTimeFormatted|currentTimeFormatted(){}[0]
final fun com.apollographql.apollo3.mpp/currentTimeMillis(): kotlin/Long // com.apollographql.apollo3.mpp/currentTimeMillis|currentTimeMillis(){}[0]
final fun com.apollographql.apollo3.mpp/platform(): com.apollographql.apollo3.mpp/Platform // com.apollographql.apollo3.mpp/platform|platform(){}[0]
Original file line number Diff line number Diff line change
@@ -1,31 +1,8 @@
package com.apollographql.apollo3.mpp

import platform.Foundation.NSDate
import platform.Foundation.NSDateFormatter
import platform.Foundation.NSThread
import platform.Foundation.timeIntervalSince1970
import platform.posix.pthread_self

actual fun currentTimeMillis(): Long {
return (NSDate().timeIntervalSince1970 * 1000).toLong()
}

private val nsDateFormatter by lazy { NSDateFormatter().apply { dateFormat = "HH:mm:ss.SSS" } }

actual fun currentTimeFormatted(): String {
return nsDateFormatter.stringFromDate(NSDate())
}

actual fun currentThreadId(): String {
return pthread_self()?.rawValue.toString()
}

actual fun currentThreadName(): String {
return if (NSThread.isMainThread) {
"main"
} else {
currentThreadId()
}
}

actual fun platform() = Platform.Native
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,7 @@

package com.apollographql.apollo3.mpp

import com.apollographql.apollo3.annotations.ApolloInternal
import kotlin.jvm.JvmName

expect fun currentTimeMillis(): Long

/**
* The current time as a human-readable String. Used for debugging.
*/
@ApolloInternal
expect fun currentTimeFormatted(): String

expect fun currentThreadId(): String

/**
* The current thread name ("main" for the main thread). Used for debugging.
*/
@ApolloInternal
expect fun currentThreadName(): String

enum class Platform {
Jvm,
Native,
Js,
WasmJs
}

/**
* The current platform. This is used from tests because Double.toString() doesn't behave the same on JS and other platforms.
* Prefer more specific functions like `assertMainThreadOnNative` when possible instead of checking the platform.
*/
expect fun platform(): Platform

// Helpful for debugging, but not wanted in the final library - uncomment as needed
//fun log(message: String) {
// println("${currentTimeFormatted()} [${currentThreadName()}] $message")
//}
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,3 @@ actual fun currentTimeMillis(): Long {
return Date().getTime().toLong()
}

actual fun currentTimeFormatted(): String {
return Date().toISOString()
}

actual fun currentThreadId(): String {
return "js"
}

actual fun currentThreadName(): String {
return currentThreadId()
}

actual fun platform() = Platform.Js

/**
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
* From https://github.com/ktorio/ktor/blob/6cd529b2dcedfcfc4ca2af0f62704764e160d7fd/ktor-utils/js/src/io/ktor/util/PlatformUtilsJs.kt#L16
*/
@ApolloInternal
val isNode: Boolean by lazy {
js(
"""
(typeof process !== 'undefined'
&& process.versions != null
&& process.versions.node != null) ||
(typeof window !== 'undefined'
&& typeof window.process !== 'undefined'
&& window.process.versions != null
&& window.process.versions.node != null)
"""
) as Boolean
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
package com.apollographql.apollo3.mpp

import java.text.SimpleDateFormat
import java.util.Locale

actual fun currentTimeMillis(): Long {
return System.currentTimeMillis()
}

private val simpleDateFormat by lazy { SimpleDateFormat("HH:mm:ss.SSS", Locale.ROOT) }

actual fun currentTimeFormatted(): String {
return simpleDateFormat.format(currentTimeMillis())
}

actual fun currentThreadId(): String {
return Thread.currentThread().id.toString()
}

actual fun currentThreadName(): String {
return Thread.currentThread().name
}

actual fun platform() = Platform.Jvm
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,3 @@ actual fun currentTimeMillis(): Long {
return currentTimeMillis2().toLong()
}

actual fun currentTimeFormatted(): String = js("(new Date()).toISOString()")

actual fun currentThreadId(): String {
return "wasm-js"
}

actual fun currentThreadName(): String {
return currentThreadId()
}

actual fun platform() = Platform.WasmJs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import com.apollographql.apollo3.exception.ApolloHttpException
import com.apollographql.apollo3.exception.DefaultApolloException
import com.apollographql.apollo3.exception.JsonDataException
import com.apollographql.apollo3.internal.CloseableSingleThreadDispatcher
import com.apollographql.apollo3.mpp.currentTimeMillis
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
Expand All @@ -35,6 +34,7 @@ import okio.BufferedSink
import okio.use
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic
import kotlin.time.TimeSource.Monotonic.markNow

/**
* An [HttpInterceptor] that batches HTTP queries to execute multiple at once.
Expand Down Expand Up @@ -68,7 +68,7 @@ class BatchingHttpInterceptor @JvmOverloads constructor(
private val maxBatchSize: Int = 10,
private val exposeErrorBody: Boolean = false,
) : HttpInterceptor {
private val creationTime = currentTimeMillis()
private val startMark = markNow()
private val dispatcher = CloseableSingleThreadDispatcher()
private val scope = CoroutineScope(dispatcher.coroutineDispatcher)
private val mutex = Mutex()
Expand Down Expand Up @@ -107,7 +107,7 @@ class BatchingHttpInterceptor @JvmOverloads constructor(
executePendingRequests()
} else {
scope.launch {
delay(batchIntervalMillis - ((currentTimeMillis() - creationTime) % batchIntervalMillis) - 1)
delay(batchIntervalMillis - (startMark.elapsedNow().inWholeMilliseconds % batchIntervalMillis) - 1)
executePendingRequests()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.apollographql.apollo3.exception.ApolloNetworkException
import com.apollographql.apollo3.exception.ApolloWebSocketClosedException
import com.apollographql.apollo3.exception.DefaultApolloException
import com.apollographql.apollo3.exception.SubscriptionConnectionException
import com.apollographql.apollo3.mpp.currentTimeMillis
import com.apollographql.apollo3.network.websocket.CLOSE_GOING_AWAY
import com.apollographql.apollo3.network.websocket.CLOSE_NORMAL
import com.apollographql.apollo3.network.websocket.ClientMessage
Expand All @@ -35,6 +34,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.time.TimeMark
import kotlin.time.TimeSource.Monotonic.markNow

/**
* A [SubscribableWebSocket] is the link between the lower level [WebSocket] and GraphQL.
Expand Down Expand Up @@ -63,7 +64,7 @@ internal class SubscribableWebSocket(
private var shutdownCause: ApolloException? = null
private var activeListeners = mutableMapOf<String, OperationListener>()
private var pending = mutableListOf<ApolloRequest<*>>()
private var _lastActiveMillis: Long = 0
private var _lastActiveMark: TimeMark? = null

private var webSocket: WebSocket
init {
Expand All @@ -75,9 +76,9 @@ internal class SubscribableWebSocket(
webSocket = webSocketEngine.newWebSocket(serverUrl, headers, this)
}

val lastActiveMillis: Long
val lastActiveMark: TimeMark?
get() = lock.withLock {
_lastActiveMillis
_lastActiveMark
}
val shutdown: Boolean
get() = lock.withLock {
Expand Down Expand Up @@ -248,7 +249,7 @@ internal class SubscribableWebSocket(
}

if (activeListeners.isEmpty()) {
_lastActiveMillis = currentTimeMillis()
_lastActiveMark = markNow()
}
ret
}
Expand All @@ -261,7 +262,7 @@ internal class SubscribableWebSocket(
}

fun markActive() = lock.withLock {
_lastActiveMillis = 0
_lastActiveMark = null
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.apollographql.apollo3.network.websocket.internal

import com.apollographql.apollo3.api.http.HttpHeader
import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.mpp.currentTimeMillis
import com.apollographql.apollo3.network.websocket.CLOSE_GOING_AWAY
import com.apollographql.apollo3.network.websocket.WebSocketEngine
import com.apollographql.apollo3.network.websocket.WsProtocol
Expand Down Expand Up @@ -75,18 +74,18 @@ internal class WebSocketHolder(
return idleTimeoutMillis
}

val lastActiveMillis = subscribableWebSocket!!.lastActiveMillis
if (lastActiveMillis != 0L) {
val elapsed = currentTimeMillis() - lastActiveMillis
val lastActiveMark = subscribableWebSocket!!.lastActiveMark
if (lastActiveMark != null) {
val elapsed = lastActiveMark.elapsedNow().inWholeMilliseconds
if (elapsed > idleTimeoutMillis) {
subscribableWebSocket!!.shutdown(null, CLOSE_GOING_AWAY, "Idle")
subscribableWebSocket = null
} else {
return idleTimeoutMillis - elapsed
}
}

}

return idleTimeoutMillis
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package test.network

import com.apollographql.apollo3.api.http.HttpRequest
import com.apollographql.apollo3.api.http.HttpResponse
import com.apollographql.apollo3.mpp.currentTimeMillis
import com.apollographql.apollo3.network.http.HttpInterceptor
import com.apollographql.apollo3.network.http.HttpInterceptorChain
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import test.network.AuthorizationInterceptor.TokenProvider
import kotlin.time.Duration.Companion.seconds
import kotlin.time.TimeMark

/**
* An [HttpInterceptor] that handles authentication
Expand All @@ -29,7 +31,10 @@ import kotlinx.coroutines.sync.withLock
class AuthorizationInterceptor(private val tokenProvider: TokenProvider, private val maxSize: Int = 1) : HttpInterceptor {
private val mutex = Mutex()

class Token(val value: String, val expiresEpochSecond: Long)
/**
* @param expirationMark the mark at which the token expires or [null] if it never expires
*/
class Token(val value: String, val expirationMark: TimeMark?)

interface TokenProvider {
/**
Expand Down Expand Up @@ -61,7 +66,7 @@ class AuthorizationInterceptor(private val tokenProvider: TokenProvider, private
class TokenLink(
val oldValue: String?,
val newValue: String,
val expiresEpochSecond: Long,
val expirationMark: TimeMark?,
var next: TokenLink?,
)

Expand All @@ -79,7 +84,7 @@ class AuthorizationInterceptor(private val tokenProvider: TokenProvider, private
tail = TokenLink(
oldValue = null,
newValue = token.value,
expiresEpochSecond = token.expiresEpochSecond,
expirationMark = token.expirationMark,
next = null
)
head = tail
Expand All @@ -90,16 +95,15 @@ class AuthorizationInterceptor(private val tokenProvider: TokenProvider, private

// Start refreshing tokens 2 seconds before they actually expire to account for
// network time
val margin = 2
if (currentTimeMillis() / 1000 + margin - link.expiresEpochSecond >= 0) {
if (link.expirationMark?.minus(2.seconds)?.hasPassedNow() == true) {
// This token will soon expire, get a new one
val token = tokenProvider.refreshToken(link.newValue)

insert(
TokenLink(
oldValue = link.newValue,
newValue = token.value,
expiresEpochSecond = token.expiresEpochSecond,
expirationMark = token.expirationMark,
next = null
)
)
Expand Down Expand Up @@ -131,7 +135,7 @@ class AuthorizationInterceptor(private val tokenProvider: TokenProvider, private
TokenLink(
oldValue = tokenValue,
newValue = token.value,
expiresEpochSecond = token.expiresEpochSecond,
expirationMark = token.expirationMark,
next = null
)
)
Expand Down
Loading
Loading