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 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Starts a Redis server and makes the port available as Spring Boot environment property. Stops the server when the
Spring context is destroyed.

Requires Java 8 or later. Uses [embedded-redis](https://github.com/ozimov/embedded-redis) and [Spring Boot](https://spring.io/projects/spring-boot).
Requires Java 8 or later. Uses [embedded-redis](https://github.com/signalapp/embedded-redis) and [Spring Boot](https://spring.io/projects/spring-boot).

## Installation

Expand Down
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
127 changes: 127 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,127 @@
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 fennec-fox
* @since 1.1.2
*/
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) {
"Could not find an available $name port in the range [$minPort, $maxPort] after $searchCounter attempts"
}
candidatePort = findRandomPort(minPort, maxPort)
searchCounter++
} while (!isPortAvailable(candidatePort))

return candidatePort
}
}
}