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

feat(network): commonize raw socket interface #3221

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion ktor-network/build.gradle.kts
Expand Up @@ -6,7 +6,7 @@ kotlin {
}

sourceSets {
jvmAndNixMain {
commonMain {
dependencies {
api(project(":ktor-utils"))
}
Expand Down
Expand Up @@ -2,10 +2,5 @@ package io.ktor.network.selector

import kotlinx.coroutines.*

/**
* A selectable entity with selectable NIO [channel], [interestedOps] subscriptions.
*/
public expect interface Selectable

@Suppress("KDocMissingDocumentation")
public class ClosedChannelCancellationException : CancellationException("Closed channel.")
@@ -0,0 +1,17 @@
package io.ktor.network.selector

import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlin.coroutines.*

/**
* Creates the selector manager for current platform.
*/
@Suppress("FunctionName")
public expect fun SelectorManager(
dispatcher: CoroutineContext = EmptyCoroutineContext
): SelectorManager

public expect interface SelectorManager : CoroutineScope, Closeable {
Copy link
Contributor

Choose a reason for hiding this comment

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

What it the reason at all to provide this empty SelectorManager in common API, when f.e. nodejs sockets, can be implemented without it, as there is only single global event loop there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to support the platforms without default event loop, so we need a "manager". (maybe not selector manager. Actually I'm just following the origin name here since I didn't define any member related to Select on common)

For those platforms that only single global event loop is avaliable, it makes no sence itself. But if we want to write codes on common, we need a placeholder or we'll be hard on commonizing the user code.

Copy link
Contributor

Choose a reason for hiding this comment

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

my question is more about: why at all expose an interface in common, which has no sense on JS?
We can create another aSocket functions, which will not use selector manager in multiplatform code f.e.

Overall, I don't understand, why to start with this?
Exposing Socket classes - ok
Exposing Select
classes - not ok, as for now, it's more of an internal detail, rather then something, that could be used outside of ktor-network independent from ktor-network itself.

IMO: better to start trying to implement nodejs/mingw sockets, and see, if it's feasible to use SelectorManager abstraction, or may be it's better to create something new
Like instead of aSocket(selectorManager) expose something like aSocket(socketEngine) or something fully different.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exposing Select classes - not ok, as for now, it's more of an internal detail, rather then something, that could be used outside of ktor-network independent from ktor-network itself.

Actually I did not treat it as a Selector (maybe I need to rename it), but a SocketDispatcher or NetworkEventLoopContext. (I believe such a context is needed for platforms that supports running several isolated event loops, and such interfaces need commonizing)

IMO: better to start trying to implement nodejs/mingw sockets, and see, if it's feasible to use SelectorManager abstraction, or may be it's better to create something new

I'll think twice about whether Interface Driven Development is suitable in this case.


}
32 changes: 32 additions & 0 deletions ktor-network/common/src/io/ktor/network/sockets/ConnectUtils.kt
@@ -0,0 +1,32 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.network.selector.*

internal expect suspend fun connect(
selector: SelectorManager,
remoteAddress: SocketAddress,
socketOptions: SocketOptions.TCPClientSocketOptions
): Socket

internal expect fun bind(
selector: SelectorManager,
localAddress: SocketAddress?,
socketOptions: SocketOptions.AcceptorOptions
): ServerSocket

internal expect fun UDPSocketBuilder.Companion.connectUDP(
selector: SelectorManager,
remoteAddress: SocketAddress,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): ConnectedDatagramSocket

internal expect fun UDPSocketBuilder.Companion.bindUDP(
selector: SelectorManager,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): BoundDatagramSocket
@@ -1,6 +1,6 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

Expand Down
@@ -1,3 +1,7 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.network.selector.*
Expand Down Expand Up @@ -28,16 +32,3 @@ public class UDPSocketBuilder(

public companion object
}

internal expect fun UDPSocketBuilder.Companion.connectUDP(
selector: SelectorManager,
remoteAddress: SocketAddress,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): ConnectedDatagramSocket

internal expect fun UDPSocketBuilder.Companion.bindUDP(
selector: SelectorManager,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): BoundDatagramSocket
21 changes: 21 additions & 0 deletions ktor-network/js/src/io/ktor/network/selector/SelectorManager.kt
@@ -0,0 +1,21 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.selector

import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlin.coroutines.*

/**
* Creates the selector manager for current platform.
*/
@Suppress("FunctionName")
public actual fun SelectorManager(dispatcher: kotlin.coroutines.CoroutineContext): SelectorManager {
TODO("SelectorManager is not yet implemented on Javascript platform")
}

public actual interface SelectorManager : CoroutineScope, Closeable {

}
32 changes: 32 additions & 0 deletions ktor-network/js/src/io/ktor/network/sockets/ConnectUtils.kt
@@ -0,0 +1,32 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.network.selector.*

internal actual suspend fun connect(
selector: SelectorManager,
remoteAddress: SocketAddress,
socketOptions: SocketOptions.TCPClientSocketOptions
): Socket = TODO("Not yet implemented")

internal actual fun bind(
selector: SelectorManager,
localAddress: SocketAddress?,
socketOptions: SocketOptions.AcceptorOptions
): ServerSocket = TODO("Not yet implemented")

internal actual fun UDPSocketBuilder.Companion.connectUDP(
selector: SelectorManager,
remoteAddress: SocketAddress,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): ConnectedDatagramSocket = TODO("Not yet implemented")

internal actual fun UDPSocketBuilder.Companion.bindUDP(
selector: SelectorManager,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): BoundDatagramSocket = TODO("Not yet implemented")
12 changes: 12 additions & 0 deletions ktor-network/js/src/io/ktor/network/sockets/SocketAddress.kt
@@ -0,0 +1,12 @@
package io.ktor.network.sockets

public actual sealed class SocketAddress

public actual data class InetSocketAddress actual constructor(
public actual val hostname: String,
public actual val port: Int
) : SocketAddress()

public actual data class UnixSocketAddress actual constructor(
public actual val path: String
) : SocketAddress()
@@ -0,0 +1,15 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.utils.io.errors.*

@Suppress("EXPECT_WITHOUT_ACTUAL")
public actual class SocketTimeoutException(
message: String,
cause: Throwable?
) : IOException(message, cause) {
public actual constructor(message: String) : this(message, null)
}
5 changes: 4 additions & 1 deletion ktor-network/jvm/src/io/ktor/network/selector/JvmSelector.kt
Expand Up @@ -5,7 +5,10 @@ import kotlinx.coroutines.*
import java.io.*
import java.nio.channels.*

public actual interface Selectable : Closeable, DisposableHandle {
/**
* A selectable entity with selectable NIO [channel], [interestedOps] subscriptions.
*/
public interface Selectable : Closeable, DisposableHandle {
/**
* Current selectable suspensions map
*/
Expand Down
12 changes: 5 additions & 7 deletions ktor-network/jvm/src/io/ktor/network/selector/SelectorManager.kt
Expand Up @@ -24,7 +24,7 @@ public actual interface SelectorManager : CoroutineScope, Closeable {
/**
* Notifies the selector that selectable has been closed.
*/
public actual fun notifyClosed(selectable: Selectable)
public fun notifyClosed(selectable: Selectable)

/**
* Suspends until [interest] is selected for [selectable]
Expand All @@ -35,9 +35,7 @@ public actual interface SelectorManager : CoroutineScope, Closeable {
* In other words you can select for read and write at the same time but should never
* try to read twice for the same selectable.
*/
public actual suspend fun select(selectable: Selectable, interest: SelectInterest)

public actual companion object
public suspend fun select(selectable: Selectable, interest: SelectInterest)
}

/**
Expand Down Expand Up @@ -65,14 +63,14 @@ public inline fun <C : Closeable, R> SelectorManager.buildOrClose(
* @property [flag] to be set in NIO selector
*/
@Suppress("KDocMissingDocumentation")
public actual enum class SelectInterest(public val flag: Int) {
public enum class SelectInterest(public val flag: Int) {
READ(SelectionKey.OP_READ),
WRITE(SelectionKey.OP_WRITE),
ACCEPT(SelectionKey.OP_ACCEPT),
CONNECT(SelectionKey.OP_CONNECT);

public actual companion object {
public actual val AllInterests: Array<SelectInterest> = values()
public companion object {
public val AllInterests: Array<SelectInterest> = values()

public val flags: IntArray = values().map { it.flag }.toIntArray()

Expand Down

This file was deleted.

19 changes: 0 additions & 19 deletions ktor-network/jvmAndNix/src/io/ktor/network/sockets/ConnectUtils.kt

This file was deleted.

@@ -0,0 +1,21 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.selector

import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlin.coroutines.*

/**
* Creates the selector manager for current platform.
*/
@Suppress("FunctionName")
public actual fun SelectorManager(dispatcher: kotlin.coroutines.CoroutineContext): SelectorManager {
TODO("SelectorManager is not yet implemented on Mingw x64 platform")
}

public actual interface SelectorManager : CoroutineScope, Closeable {

}
32 changes: 32 additions & 0 deletions ktor-network/mingwX64/src/io/ktor/network/sockets/ConnectUtils.kt
@@ -0,0 +1,32 @@
/*
* Copyright 2014-2022 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.network.selector.*

internal actual suspend fun connect(
selector: SelectorManager,
remoteAddress: SocketAddress,
socketOptions: SocketOptions.TCPClientSocketOptions
): Socket = TODO("Not yet implemented")

internal actual fun bind(
selector: SelectorManager,
localAddress: SocketAddress?,
socketOptions: SocketOptions.AcceptorOptions
): ServerSocket = TODO("Not yet implemented")

internal actual fun UDPSocketBuilder.Companion.connectUDP(
selector: SelectorManager,
remoteAddress: SocketAddress,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): ConnectedDatagramSocket = TODO("Not yet implemented")

internal actual fun UDPSocketBuilder.Companion.bindUDP(
selector: SelectorManager,
localAddress: SocketAddress?,
options: SocketOptions.UDPSocketOptions
): BoundDatagramSocket = TODO("Not yet implemented")
12 changes: 12 additions & 0 deletions ktor-network/mingwX64/src/io/ktor/network/sockets/SocketAddress.kt
@@ -0,0 +1,12 @@
package io.ktor.network.sockets

public actual sealed class SocketAddress

public actual data class InetSocketAddress actual constructor(
public actual val hostname: String,
public actual val port: Int
) : SocketAddress()

public actual data class UnixSocketAddress actual constructor(
public actual val path: String
) : SocketAddress()
@@ -0,0 +1,15 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package io.ktor.network.sockets

import io.ktor.utils.io.errors.*

@Suppress("EXPECT_WITHOUT_ACTUAL")
public actual class SocketTimeoutException(
message: String,
cause: Throwable?
) : IOException(message, cause) {
public actual constructor(message: String) : this(message, null)
}