Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

package grails.gorm.tests

import java.util.concurrent.atomic.AtomicReference

import grails.gorm.annotation.Entity
import org.grails.datastore.gorm.neo4j.Neo4jTransaction
import org.grails.datastore.mapping.core.OptimisticLockingException
Expand All @@ -27,6 +29,7 @@ import org.grails.datastore.mapping.transactions.SessionHolder
import org.neo4j.graphdb.GraphDatabaseService
import org.neo4j.graphdb.Transaction
import org.springframework.transaction.support.TransactionSynchronizationManager
import spock.util.concurrent.PollingConditions

/**
* @author Burt Beckwith
Expand Down Expand Up @@ -96,11 +99,18 @@ class OptimisticLockingSpec extends GormDatastoreSpec {
}
}
}.join()
// The background thread's save is already synchronized via join() above; this sleep is
// headroom for the embedded Neo4j harness's own write durability, not thread completion.
// A noisy/loaded CI runner can push that past a couple of seconds - give it more room
// rather than risk a spurious failure (heisenbug).
sleep 5000
// The background thread's save is already synchronized via join() above; poll (rather
// than sleep a fixed duration) until an independent session observes it, since the
// embedded Neo4j harness's own write-durability/visibility lag can outlast any fixed
// guess (heisenbug) - a noisy/loaded CI runner has been seen pushing past 2s, and this
// adapts instead of gambling on a bigger number.
new PollingConditions(timeout: 10, initialDelay: 0.1, delay: 0.2).eventually {
def observedName
OptLockVersioned.withNewSession { s ->
observedName = OptLockVersioned.get(o.id).name
}
assert observedName == 'locked in new session'
}

o.name += ' in main session'
def ex
Expand All @@ -125,24 +135,41 @@ class OptimisticLockingSpec extends GormDatastoreSpec {

given:
def o = new OptLockNotVersioned(name: 'locked').save(flush: true)
session.transaction.commit()
session.transaction.nativeTransaction.close()
session.clear()

when:
o = OptLockNotVersioned.get(o.id)

try {
Thread.start {
def failure = new AtomicReference<Throwable>()
def backgroundUpdate = Thread.start {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Merge hazard: feat/neo4j-gorm-registry-migration (branched from the same base commit) deletes this module and relocates this spec to grails-data-neo4j/core/src/test/groovy/grails/gorm/tests/OptimisticLockingSpec.groovy - still containing the old join(2000) block, plus a stray session. -> manager.session. substitution inside the string literals (' in new manager.session'). A git merge-tree of the two heads reports a content conflict on this exact block, and the likely resolution (taking the migrated file wholesale) silently drops this fix. Worth coordinating with that branch, and/or applying the change to the TCK copy (grails-datamapping-tck/src/main/groovy/org/apache/grails/data/testing/tck/tests/OptimisticLockingSpec.groovy, which still has .join() + sleep(2000)), which survives the migration.

try {
OptLockNotVersioned.withNewSession { s ->
def reloaded = OptLockNotVersioned.get(o.id)
reloaded.name += ' in new session'
reloaded.save(flush: true)
OptLockNotVersioned.withTransaction {
def reloaded = OptLockNotVersioned.get(o.id)
assert reloaded
reloaded.name += ' in new session'
reloaded.save(flush: true)
}
}
}.join(2000)
} catch (InterruptedException e) {
// ignore
} catch (Throwable t) {
failure.set(t)
}
}
backgroundUpdate.join()
// A thread that dies from an uncaught exception is also no longer alive, so join()
// alone can't distinguish a completed write from a crashed one; assert the captured
// outcome explicitly.
assert failure.get() == null
// Same cross-session visibility-lag rationale as "Test optimistic locking" above.
def nameAfterBackgroundUpdate
new PollingConditions(timeout: 10, initialDelay: 0.1, delay: 0.2).eventually {
OptLockNotVersioned.withNewSession { s ->
nameAfterBackgroundUpdate = OptLockNotVersioned.get(o.id).name
}
assert nameAfterBackgroundUpdate == 'locked in new session'
}
// Same headroom rationale as "Test optimistic locking" above.
sleep 5000

o.name += ' in main session'
def ex
Expand All @@ -158,6 +185,10 @@ class OptimisticLockingSpec extends GormDatastoreSpec {
o = OptLockNotVersioned.get(o.id)

then:
// Proves the background write actually landed before the main session's blind
// overwrite below; without it, these assertions would pass even if the background
// thread never ran.
nameAfterBackgroundUpdate == 'locked in new session'
ex == null
o.name == 'locked in main session'
}
Expand Down
Loading