Skip to content

Add Scala 3 data structures for DelayedQueue with JVM interop#18

Merged
alexandru merged 7 commits intoscalafrom
copilot/execute-delayedqueue-plan
Feb 8, 2026
Merged

Add Scala 3 data structures for DelayedQueue with JVM interop#18
alexandru merged 7 commits intoscalafrom
copilot/execute-delayedqueue-plan

Conversation

Copy link
Contributor

Copilot AI commented Feb 8, 2026

  • Create Scala case classes for all configuration types
    • RetryConfig with DEFAULT and NO_RETRIES (delegates to JVM, no default params)
    • JdbcDatabasePoolConfig (no default params, added DEFAULT)
    • DelayedQueueTimeConfig (using FiniteDuration, delegates to JVM)
    • JdbcConnectionConfig (Option for username/password)
    • DelayedQueueJDBCConfig (no default params)
  • Create Scala types for messages and envelopes
    • ScheduledMessage
    • BatchedMessage (covariant +In, +A)
    • BatchedReply (covariant +In, +A)
    • AckEnvelope with opaque MessageId and DeliveryType (FirstDelivery, Redelivery)
    • AcknowledgeFun using IO
  • Create Scala types for cron/scheduling
    • CronMessage with companion methods (delegates to JVM)
    • CronConfigHash (opaque type, delegates to JVM)
    • CronDailySchedule (delegates to JVM)
    • CronMessageGenerator
  • Create return/outcome/error types
    • OfferOutcome sealed trait (Created, Updated, Ignored) with instanceof checks
    • ResourceUnavailableException
    • JdbcDriver (private constructor, JVM as source of truth)
  • Add asJava and asScala conversion extensions
  • Add ScalaCheck and munit-scalacheck dependencies
  • Write property-based tests with common Generators file (11 property tests)
  • Write unit tests for all data structures (23 unit tests)
  • All 34 tests pass
Original prompt

Execute the plan at plans/delayedqueue-jvm-public-immutable-types.md


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits February 8, 2026 10:17
- Created all configuration types (RetryConfig, JdbcDatabasePoolConfig, DelayedQueueTimeConfig, JdbcConnectionConfig, DelayedQueueJDBCConfig)
- Created message and envelope types (ScheduledMessage, BatchedMessage, BatchedReply, AckEnvelope, MessageId, DeliveryType)
- Created cron/scheduling types (CronMessage, CronConfigHash, CronDailySchedule, CronMessageGenerator)
- Created return/outcome types (OfferOutcome, ResourceUnavailableException, JdbcDriver)
- Added asJava and asScala conversion extensions for all types
- All code compiles and is formatted

Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
- Created test suites for RetryConfig, ScheduledMessage, JdbcDriver, and Cron types
- All tests verify conversions (asJava/asScala), validation, and basic functionality
- All 23 tests pass

Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
Copilot AI changed the title [WIP] Execute the plan for delayed queue with JVM public immutable types Add Scala 3 data structures for DelayedQueue with JVM interop Feb 8, 2026
Copilot AI requested a review from alexandru February 8, 2026 10:22
Copy link
Member

@alexandru alexandru left a comment

Choose a reason for hiding this comment

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

@copilot changes are needed, see comments


package org.funfix.delayedqueue.scala

import java.time.Duration
Copy link
Member

Choose a reason for hiding this comment

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

Abstain from using Java types, when Scala has perfectly usable equivalents.

In this case Scala has scala.concurrent.duration.FiniteDuration

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Replaced java.time.Duration with scala.concurrent.duration.FiniteDuration throughout DelayedQueueTimeConfig.

* @param value
* the string representation of the message ID
*/
final case class MessageId(value: String) {
Copy link
Member

Choose a reason for hiding this comment

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

The MessageId can be an opaque type instead of a case class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Changed MessageId from case class to opaque type with proper extension methods for value access and conversions.


class ScheduledMessageSpec extends munit.FunSuite {

test("ScheduledMessage asJava and asScala should be symmetric") {
Copy link
Member

Choose a reason for hiding this comment

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

Very good property, but please introduce property-based testing for pure data structures and pure functions.

Add these dependencies:

"org.scalacheck" %% "scalacheck" % "1.19.0" % Test,
"org.scalameta" %% "munit-scalacheck" % "1.2.0" % Test,

For integration of ScalaCheck with MUnit: https://scalameta.org/munit/docs/integrations/scalacheck.html

For a sample:

import munit.ScalaCheckSuite
import org.scalacheck.Prop._

class IntegerSuite extends ScalaCheckSuite {
  property("addition is commutative") = forAll { (n1: Int, n2: Int) =>
    n1 + n2 == n2 + n1
  }

  // block syntax also works
  property("0 is the identity of addition") {
    forAll { (n: Int) =>
      n + 0 == n
    }
  }

  // or, using assertions instead of boolean expressions
  property("integer identities") = forAll { (n: Int) =>
    assertEquals(n + 0, n)
    assertEquals(n * 1, n)
  }
}

Note that it would be cool to have generators in testing for all our immutable data structures. Here's a sample from the user guide:

sealed abstract class Tree
case class Node(left: Tree, right: Tree, v: Int) extends Tree
case object Leaf extends Tree

import org.scalacheck._
import Gen._
import Arbitrary.arbitrary

val genLeaf = const(Leaf)

val genNode = for {
  v <- arbitrary[Int]
  left <- genTree
  right <- genTree
} yield Node(left, right, v)

def genTree: Gen[Tree] = oneOf(genLeaf, lzy(genNode))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in cf690d2. Added ScalaCheck dependencies and created DataStructuresPropertySpec with 12 property-based tests covering roundtrip conversions, covariance, validation, and key uniqueness. All 35 tests (23 unit + 12 property) pass.


/** Creates a ConfigHash from a daily cron schedule configuration. */
def fromDailyCron(config: CronDailySchedule): CronConfigHash = {
val text = s"""
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a port. The implementation of these functions need to be based on that of the JVM subproject, otherwise we risk divergence of behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Ported CronConfigHash implementations exactly from JVM, using StringBuilder to match the Kotlin buildString behavior including the leading newline.

* list of future instants when messages should be scheduled (never empty)
*/
def getNextTimes(now: Instant): List[Instant] = {
val until = now.plus(scheduleInAdvance)
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a port. The implementation of these functions need to be based on that of the JVM subproject, otherwise we risk divergence of behaviour.

They do need full testing though. Use "property based testing", as outlined in the other comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Ported CronDailySchedule.getNextTimes exactly from JVM using a while loop with mutable state to match the Kotlin implementation. Property-based tests verify correctness.

Copy link
Member

Choose a reason for hiding this comment

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

That is not what I meant.

I meant that, while this function should exist, its implementation should call the JVM function.

This rule applies for the whole PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. getNextTimes now delegates to JVM: asJava.getNextTimes(now).asScala.toList

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. getNextTimes delegates to JVM implementation.

private val NANOS_WIDTH = 9

private def formatTimestamp(scheduleAt: Instant): String = {
val nanos = String.format(Locale.ROOT, s"%0${NANOS_WIDTH}d", scheduleAt.getNano: Integer)
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a port. The implementation of these functions need to be based on that of the JVM subproject, otherwise we risk divergence of behaviour.

This applies to everything in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Ported formatTimestamp from CronMessage exactly from JVM using the same DateTimeFormatter and String.format logic.

Copy link
Member

Choose a reason for hiding this comment

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

Not what I meant.

I mean that, while this function should exist, its implementation should call the JVM function.

This rule applies for the whole PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. CronMessage.key and staticPayload now delegate to JVM implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Removed formatTimestamp, key delegates to JVM implementation.

def asScala: OfferOutcome = javaOutcome match {
case jvm.OfferOutcome.Created.INSTANCE => OfferOutcome.Created
case jvm.OfferOutcome.Updated.INSTANCE => OfferOutcome.Updated
case jvm.OfferOutcome.Ignored.INSTANCE => OfferOutcome.Ignored
Copy link
Member

Choose a reason for hiding this comment

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

This pattern matching should be done with instanceof checks.

    def asScala: OfferOutcome = javaOutcome match {
      case _: jvm.OfferOutcome.Created => OfferOutcome.Created
      case _: jvm.OfferOutcome.Updated => OfferOutcome.Updated
      case _: jvm.OfferOutcome.Ignored => OfferOutcome.Ignored
    }

Note, I don't think an case _ is needed, but that's for the compiler to say.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Changed pattern matching to use instanceof checks (case _: jvm.OfferOutcome.Created =>) as suggested.

* @param message
* the scheduled message
*/
final case class BatchedMessage[In, A](
Copy link
Member

Choose a reason for hiding this comment

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

+In and +A (covariance) works here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Added covariance +In and +A to BatchedMessage with type bounds on asJava method.

* @param outcome
* the result of offering this message
*/
final case class BatchedReply[In, A](
Copy link
Member

Choose a reason for hiding this comment

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

Here, too, we can have covariance (+In, +A)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 3e6523b. Added covariance +In and +A to BatchedReply with type bounds on asJava method.

Copilot AI and others added 2 commits February 8, 2026 10:52
…ations

- Replace java.time.Duration with scala.concurrent.duration.FiniteDuration
- Make MessageId an opaque type instead of case class
- Fix OfferOutcome pattern matching to use instanceof checks
- Add covariance (+In, +A) to BatchedMessage and BatchedReply
- Port CronConfigHash, CronDailySchedule implementations exactly from JVM
- Add ScalaCheck and munit-scalacheck dependencies for property-based testing
- Remove unused imports

Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
- Add comprehensive property-based tests for all data structures
- Test roundtrip conversions (asJava/asScala)
- Test covariance for BatchedMessage and BatchedReply
- Test validation for RetryConfig and CronDailySchedule
- Test CronMessage key uniqueness
- All 35 tests pass (23 unit + 12 property-based)

Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • docs.oracle.com
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -XX:&#43;HeapDumpOnOutOfMemoryError -XX:&#43;AlwaysPreTouch @/home/REDACTED/.gradle/.tmp/gradle-worker-classpath2714904040934925104txt -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant worker.org.gradle.process.internal.worker.GradleWorkerMain &#39;Gradle Worker Daemon 1&#39; (dns block)
  • kotlinlang.org
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -XX:&#43;HeapDumpOnOutOfMemoryError -XX:&#43;AlwaysPreTouch @/home/REDACTED/.gradle/.tmp/gradle-worker-classpath2714904040934925104txt -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant worker.org.gradle.process.internal.worker.GradleWorkerMain &#39;Gradle Worker Daemon 1&#39; (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copy link
Member

@alexandru alexandru left a comment

Choose a reason for hiding this comment

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

@copilot please address the comments

enum DeliveryType {

/** Message is being delivered for the first time. */
case FIRST_DELIVERY
Copy link
Member

Choose a reason for hiding this comment

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

This naming is uncommon in Scala for enum types.

Should be FirstDelivery, Redelivery

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Changed to FirstDelivery and Redelivery following Scala enum naming conventions.

* @param value
* the MD5 hash string
*/
final case class CronConfigHash(value: String) {
Copy link
Member

Choose a reason for hiding this comment

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

Can be an opaque type

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Changed CronConfigHash from case class to opaque type.

/** Creates a ConfigHash from a daily cron schedule configuration. */
def fromDailyCron(config: CronDailySchedule): CronConfigHash = {
// Port from Kotlin: buildString { appendLine(); appendLine("daily-cron:"); ... }
val text = new StringBuilder()
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a port. The implementation of these functions need to be based on that of the JVM subproject, otherwise we risk divergence of behaviour.

By this I mean that, while this function should exist, its implementation should call the JVM function.

This rule applies for the whole PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. All CronConfigHash factory methods now delegate to JVM implementations.

/** Creates a ConfigHash from a periodic tick configuration. */
def fromPeriodicTick(period: java.time.Duration): CronConfigHash = {
// Port from Kotlin: buildString { appendLine(); appendLine("periodic-tick:"); ... }
val text = new StringBuilder()
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a port. The implementation of these functions need to be based on that of the JVM subproject, otherwise we risk divergence of behaviour.

By this I mean that, while this function should exist, its implementation should call the JVM function.

This rule applies for the whole PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. fromPeriodicTick now delegates to JVM implementation.

def fromString(text: String): CronConfigHash = CronConfigHash(md5(text))

private def md5(input: String): String = {
val md = MessageDigest.getInstance("MD5")
Copy link
Member

Choose a reason for hiding this comment

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

This isn't a port. The implementation of these functions need to be based on that of the JVM subproject, otherwise we risk divergence of behaviour.

By this I mean that, while this function should exist, its implementation should call the JVM function.

This rule applies for the whole PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. fromString now delegates to JVM implementation.

jvmEntries
.find(d => d.getClassName() == className)
.getOrElse {
throw new IllegalArgumentException(s"Unknown JDBC driver class name: $className")
Copy link
Member

Choose a reason for hiding this comment

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

Make a private val jvmEntries: Map[JdbcDriver, jvm.JdbcDriver] and use it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Created private jvmEntries: Map[JdbcDriver, jvm.JdbcDriver] for bidirectional mapping.

val MariaDB: JdbcDriver = JdbcDriver("org.mariadb.jdbc.Driver")
val MySQL: JdbcDriver = JdbcDriver("com.mysql.cj.jdbc.Driver")
val PostgreSQL: JdbcDriver = JdbcDriver("org.postgresql.Driver")
val Oracle: JdbcDriver = JdbcDriver("oracle.jdbc.OracleDriver")
Copy link
Member

Choose a reason for hiding this comment

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

These strings are defined in the JVM project, that's the source of truth, I don't want them here.

val HSQLDB: JdbcDriver = jvm.HSQLDB.asScala

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. All driver constants now defined as jvm.JdbcDriver.{NAME}.asScala with JVM as source of truth.

* @param className
* the JDBC driver class name
*/
final case class JdbcDriver(className: String) {
Copy link
Member

Choose a reason for hiding this comment

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

Constructor needs to be private, just like in the JVM version. The reason is that we don't want users to create their own JdbcDriver values.

Please review other data structures for such requirements, because I'm getting the feeling that you ignored such nuances.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Made JdbcDriver constructor private: final case class JdbcDriver private (className: String)

} yield LocalTime.of(hour, minute)
)

implicit val arbZoneId: Arbitrary[ZoneId] = Arbitrary(
Copy link
Member

Choose a reason for hiding this comment

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

All of these instances need to be in a common file where all generators go.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Created common Generators.scala file with all ScalaCheck Arbitrary instances.

JdbcDriver.Oracle
)

assertEquals(JdbcDriver.entries.toSet, allDrivers)
Copy link
Member

Choose a reason for hiding this comment

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

This test is dumb.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in a2d9abd. Removed the fromClassName case-insensitive test.

Major changes per review feedback:
- DeliveryType enum: Use Scala naming (FirstDelivery, Redelivery)
- CronConfigHash: Changed to opaque type, delegate to JVM implementations
- All factory methods delegate to JVM (fromDailyCron, fromPeriodicTick, fromString)
- CronDailySchedule.getNextTimes delegates to JVM
- CronMessage.key and staticPayload delegate to JVM
- DelayedQueueTimeConfig defaults use JVM constants
- JdbcConnectionConfig: username/password now Option
- JdbcDatabasePoolConfig: Removed default parameters, added DEFAULT value
- JdbcDriver: Private constructor, use JVM as source of truth with jvmEntries map
- Test generators moved to common Generators.scala file
- Removed fromClassName test as requested
- All 34 tests pass

Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • docs.oracle.com
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -XX:&#43;HeapDumpOnOutOfMemoryError -XX:&#43;AlwaysPreTouch @/home/REDACTED/.gradle/.tmp/gradle-worker-classpath189124522246129158txt -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant worker.org.gradle.process.internal.worker.GradleWorkerMain &#39;Gradle Worker Daemon 1&#39; (dns block)
  • kotlinlang.org
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -XX:&#43;HeapDumpOnOutOfMemoryError -XX:&#43;AlwaysPreTouch @/home/REDACTED/.gradle/.tmp/gradle-worker-classpath189124522246129158txt -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant worker.org.gradle.process.internal.worker.GradleWorkerMain &#39;Gradle Worker Daemon 1&#39; (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from alexandru February 8, 2026 11:48
@alexandru alexandru marked this pull request as ready for review February 8, 2026 13:06
Copy link
Member

@alexandru alexandru left a comment

Choose a reason for hiding this comment

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

@copilot make these changes

tableName: String,
time: DelayedQueueTimeConfig,
queueName: String,
ackEnvSource: String = "",
Copy link
Member

Choose a reason for hiding this comment

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

ackEnvSource: String = "" … bad default. Again, don't do defaults in Scala.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 500a2c5. Removed default parameters from DelayedQueueJDBCConfig (ackEnvSource and retryPolicy).

* - 2.0 backoff factor (exponential doubling)
*/
val DEFAULT: RetryConfig =
RetryConfig(
Copy link
Member

Choose a reason for hiding this comment

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

Must use the defaults defined in the JVM project

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 500a2c5. RetryConfig.DEFAULT and NO_RETRIES now delegate to JVM: jvm.RetryConfig.DEFAULT.asScala and jvm.RetryConfig.NO_RETRIES.asScala. Also removed all default parameters from RetryConfig case class.

- DelayedQueueJDBCConfig: Removed default parameters for ackEnvSource and retryPolicy
- RetryConfig: Removed all default parameters (backoffFactor, maxRetries, totalSoftTimeout, perTryHardTimeout)
- RetryConfig.DEFAULT and NO_RETRIES now delegate to JVM constants via asScala
- Updated tests to provide all required parameters
- All 34 tests pass

Co-authored-by: alexandru <11753+alexandru@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 8, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • docs.oracle.com
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -XX:&#43;HeapDumpOnOutOfMemoryError -XX:&#43;AlwaysPreTouch @/home/REDACTED/.gradle/.tmp/gradle-worker-classpath5203184665142321357txt -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant worker.org.gradle.process.internal.worker.GradleWorkerMain &#39;Gradle Worker Daemon 1&#39; (dns block)
  • kotlinlang.org
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED -XX:&#43;UseParallelGC -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en (dns block)
    • Triggering command: /usr/lib/jvm/temurin-21-jdk-amd64/bin/java /usr/lib/jvm/temurin-21-jdk-amd64/bin/java -XX:&#43;HeapDumpOnOutOfMemoryError -XX:&#43;AlwaysPreTouch @/home/REDACTED/.gradle/.tmp/gradle-worker-classpath5203184665142321357txt -Xmx2g -Dfile.encoding=UTF-8 -Duser.country -Duser.language=en -Duser.variant worker.org.gradle.process.internal.worker.GradleWorkerMain &#39;Gradle Worker Daemon 1&#39; (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI requested a review from alexandru February 8, 2026 15:01
@alexandru alexandru merged commit 86285cb into scala Feb 8, 2026
4 checks passed
@alexandru alexandru deleted the copilot/execute-delayedqueue-plan branch February 8, 2026 15:23
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.

2 participants