@@ -13,6 +13,7 @@ import org.springframework.test.context.ContextConfiguration
1313import org.springframework.test.context.jdbc.Sql
1414import org.springframework.test.context.junit.jupiter.SpringExtension
1515import st.orm.PersistenceException
16+ import st.orm.core.spi.TransactionContext
1617import st.orm.repository.countAll
1718import st.orm.template.impl.CoroutineAwareConnectionProviderImpl
1819import st.orm.template.model.City
@@ -31,8 +32,6 @@ open class ConnectionProviderTest(
3132 @Autowired val dataSource : DataSource ,
3233) {
3334
34- // Connection acquisition without transaction
35-
3635 @Test
3736 fun `getConnection without transaction should return new connection` () {
3837 val provider = CoroutineAwareConnectionProviderImpl ()
@@ -52,8 +51,6 @@ open class ConnectionProviderTest(
5251 connection.isClosed.shouldBeTrue()
5352 }
5453
55- // Connection within transaction
56-
5754 @Test
5855 fun `getConnection within transaction should reuse transaction connection` (): Unit = runBlocking {
5956 transactionBlocking {
@@ -72,48 +69,77 @@ open class ConnectionProviderTest(
7269 }
7370 }
7471
75- // ConcurrencyDetector
72+ private fun stubContext (): TransactionContext = object : TransactionContext {
73+ override fun entityCache (entityType : Class <out st.orm.Entity <* >>, retention : st.orm.core.spi.CacheRetention ) = throw UnsupportedOperationException ()
74+ override fun getEntityCache (entityType : Class <out st.orm.Entity <* >>) = throw UnsupportedOperationException ()
75+ override fun findEntityCache (entityType : Class <out st.orm.Entity <* >>) = null
76+ override fun clearAllEntityCaches () {}
77+ override fun <T : Any ?> getDecorator (resourceType : Class <T >): TransactionContext .Decorator <T > = TransactionContext .Decorator { it }
78+ }
7679
7780 @Test
78- fun `ConcurrencyDetector beforeAccess and afterAccess on same thread should succeed` () {
81+ fun `ConcurrencyDetector beforeAccess and afterAccess with same context should succeed` () {
7982 val connection = dataSource.connection
83+ val context = stubContext()
8084 try {
81- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection)
82- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection)
85+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context )
86+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context )
8387 } finally {
8488 connection.close()
8589 }
8690 }
8791
8892 @Test
89- fun `ConcurrencyDetector should allow nested access on same thread ` () {
93+ fun `ConcurrencyDetector should allow nested access with same context ` () {
9094 val connection = dataSource.connection
95+ val context = stubContext()
9196 try {
92- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection)
93- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection)
94- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection)
95- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection)
97+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context)
98+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context)
99+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context)
100+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context)
101+ } finally {
102+ connection.close()
103+ }
104+ }
105+
106+ @Test
107+ fun `ConcurrencyDetector should allow same context from different thread` () {
108+ val connection = dataSource.connection
109+ val context = stubContext()
110+ try {
111+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context)
112+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context)
113+ // Same context, different thread — simulates virtual thread migration.
114+ val thread = Thread {
115+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context)
116+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context)
117+ }
118+ thread.start()
119+ thread.join()
96120 } finally {
97121 connection.close()
98122 }
99123 }
100124
101125 @Test
102- fun `ConcurrencyDetector should detect concurrent access from different threads ` () {
126+ fun `ConcurrencyDetector should detect concurrent access from different contexts ` () {
103127 val connection = dataSource.connection
128+ val context1 = stubContext()
129+ val context2 = stubContext()
104130 try {
105- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection)
131+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context1 )
106132 var caughtException: Throwable ? = null
107133 val thread = Thread {
108- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection)
134+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .beforeAccess(connection, context2 )
109135 }
110136 thread.setUncaughtExceptionHandler { _, throwable -> caughtException = throwable }
111137 thread.start()
112138 thread.join()
113139 assertThrows<PersistenceException > {
114140 caughtException?.let { throw it }
115141 }
116- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection)
142+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context1 )
117143 } finally {
118144 connection.close()
119145 }
@@ -122,9 +148,10 @@ open class ConnectionProviderTest(
122148 @Test
123149 fun `ConcurrencyDetector afterAccess on unknown connection should be no-op` () {
124150 val connection = dataSource.connection
151+ val context = stubContext()
125152 try {
126153 // afterAccess on a connection never registered should not throw
127- CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection)
154+ CoroutineAwareConnectionProviderImpl .ConcurrencyDetector .afterAccess(connection, context )
128155 } finally {
129156 connection.close()
130157 }
0 commit comments