Skip to content
This repository was archived by the owner on Sep 2, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") {
exclude(group = "com.vaadin.external.google")
}
implementation("it.ozimov:embedded-redis:$embeddedRedisVersion") {
implementation("org.signal:embedded-redis:$embeddedRedisVersion") {
exclude(group = "org.slf4j", module = "slf4j-simple")
}
testImplementation("org.springframework.boot:spring-boot-starter:$springBootVersion")
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ org.gradle.warning.mode=all
org.gradle.jvmargs=-XX:MaxMetaspaceSize=2G

springBootVersion=2.5.2
embeddedRedisVersion=0.7.3
embeddedRedisVersion=0.8.3
lettuceVersion=6.1.4.RELEASE
jUnitVersion=5.7.0
kotlin.version=1.5.20
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import org.springframework.context.ApplicationListener
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.core.env.MapPropertySource
import org.springframework.util.SocketUtils

@Order(Ordered.LOWEST_PRECEDENCE)
open class EmbeddedRedisApplicationListener : ApplicationListener<ApplicationPreparedEvent> {
Expand Down
153 changes: 153 additions & 0 deletions src/main/kotlin/com/asarkar/spring/test/redis/SocketUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asarkar.spring.test.redis

import org.springframework.util.Assert
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.ServerSocket
import java.util.Random
import javax.net.ServerSocketFactory

/**
* Simple utility methods for working with network sockets for example,
* for finding available ports on `localhost`.
*
* Within this class, a TCP port refers to a port for a [ServerSocket];
* whereas, a UDP port refers to a port for a [DatagramSocket].
*
* org.springframework.util.SocketUtils was removed in Spring 6.
* From Springboot 3.2.3 version, SocketUtils cannot be used.
* org.springframework.test.util.TestSocketUtils can be used as an alternative, but embedded-redis-spring has to support older versions, so org.springframework.util.SocketUtils was copied.
*
* @author Sam Brannen
* @author Ben Hale
* @author Arjen Poutsma
* @author Gunnar Hillert
* @author Gary Russell
* @author fennec-fox
* @since 4.0
*/
object SocketUtils {
/**
* The default minimum value for port ranges used when finding an available
* socket port.
*/
private const val PORT_RANGE_MIN: Int = 1024

/**
* The default maximum value for port ranges used when finding an available
* socket port.
*/
const val PORT_RANGE_MAX: Int = 65535

private val random = Random(System.nanoTime())

/**
* Find an available TCP port randomly selected from the range
* [`minPort`, `maxPort`].
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
/**
* Find an available TCP port randomly selected from the range
* [`minPort`, {@value #PORT_RANGE_MAX}].
* @param minPort the minimum port number
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
/**
* Find an available TCP port randomly selected from the range
* [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}].
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
@JvmOverloads
fun findAvailableTcpPort(minPort: Int = PORT_RANGE_MIN, maxPort: Int = PORT_RANGE_MAX): Int {
return SocketType.TCP.findAvailablePort(minPort, maxPort)
}

private enum class SocketType {
TCP {
override fun isPortAvailable(port: Int): Boolean {
try {
val serverSocket = ServerSocketFactory.getDefault().createServerSocket(
port,
1,
InetAddress.getByName("localhost")
)
serverSocket.close()
return true
} catch (ex: Exception) {
return false
}
}
};

/**
* Determine if the specified port for this `SocketType` is
* currently available on `localhost`.
*/
protected abstract fun isPortAvailable(port: Int): Boolean

/**
* Find a pseudo-random port number within the range
* [`minPort`, `maxPort`].
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return a random port number within the specified range
*/
private fun findRandomPort(minPort: Int, maxPort: Int): Int {
val portRange = maxPort - minPort
return minPort + random.nextInt(portRange + 1)
}

/**
* Find an available port for this `SocketType`, randomly selected
* from the range [`minPort`, `maxPort`].
* @param minPort the minimum port number
* @param maxPort the maximum port number
* @return an available port number for this socket type
* @throws IllegalStateException if no available port could be found
*/
fun findAvailablePort(minPort: Int, maxPort: Int): Int {
Assert.isTrue(minPort > 0, "'minPort' must be greater than 0")
Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'")
Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to $PORT_RANGE_MAX")

val portRange = maxPort - minPort
var candidatePort: Int
Copy link
Owner

Choose a reason for hiding this comment

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

I need to review this more closely, but is it possible for multiple threads to enter the do-while loop?

Copy link
Author

Choose a reason for hiding this comment

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

This code is a conversion of the existing
org/springframework/util/SocketUtils.java code to Kotlin.
I have not been able to check whether it is thread safe in multithreading.

Copy link
Author

Choose a reason for hiding this comment

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

@asarkar Could you please check the PR again?

Copy link
Owner

Choose a reason for hiding this comment

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

Thanks, but I would like to see some tests for SocketUtils. If Spring has some, feel free to use those, but otherwise, we need new tests.

Copy link
Author

Choose a reason for hiding this comment

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

@asarkar The current PR was created because SocketUtils in Spring is not available.
Starting with Spring 6, SocketUtils.java used by asarkar has been removed from the code.

I will add the test code for SocketUtils mentioned above soon.

var searchCounter = 0
do {
check(searchCounter <= portRange) {
String.format(
"Could not find an available %s port in the range [%d, %d] after %d attempts",
name,
minPort,
maxPort,
searchCounter
)
}
candidatePort = findRandomPort(minPort, maxPort)
searchCounter++
} while (!isPortAvailable(candidatePort))

return candidatePort
}
}
}