i have a problem with my WriteResource code that i use to have transactions which can pause in actors.
it seems that in "prepare" i cannot access the Refs anymore... e.g. i get this:
java.lang.IllegalStateException: txn.status is Preparing
at edu.stanford.ppl.ccstm.impl.StatusHolder.rollbackOrIllegalState(StatusHolder.scala:71)
at edu.stanford.ppl.ccstm.impl.StatusHolder.requireActive(StatusHolder.scala:64)
at edu.stanford.ppl.ccstm.impl.TxnImpl.get(TxnImpl.scala:444)
at edu.stanford.ppl.ccstm.impl.RefOps$class.get(RefOps.scala:16)
at edu.stanford.ppl.ccstm.impl.TBooleanRef.get(TBooleanRef.scala:19)
at edu.stanford.ppl.ccstm.Source$class.apply(Source.scala:142)
at edu.stanford.ppl.ccstm.impl.TBooleanRef.apply(TBooleanRef.scala:19)
at de.sciss.synth.proc.Ref$mcZ$sp.apply$mcZ$sp(Ref.scala:34)
at de.sciss.synth.proc.RichState.isSatisfied(RichObject.scala:165)
at de.sciss.synth.proc.ProcTxn$Impl$$anonfun$establishDependancies$1$$anonfun$apply$3$$anonfun$apply$...
where the code is
def prepare( t: Txn ) : Boolean = t.status.mightCommit && establishDependancies
private def establishDependancies( implicit tx: ProcTxn ) : Boolean = {
[...]
var topo = Topology.empty[ Entry, EntryEdge ]
entries.foreach( sourceEntry => {
topo = topo.addVertex( sourceEntry )
sourceEntry.dependancies.foreach( dep => {
[...]
}).getOrElse({
val (state, value) = dep
if( !state.isSatisfied( value )) { // !!!
error( "Unsatisfied dependancy " + dep )
}
})
})
})
true
}
although the stack print is abbreviated (-- you know by chance what causes this?) the line in charge is
if( !state.isSatisfied( value ))
where state is an instance of
class RichState( init: Boolean ) {
private val value = Ref( init )
def isSatisfied( value: Boolean )( implicit tx: ProcTxn ) : Boolean = this.value() == value
}
and Ref is my own wrapper class for ccstm's Ref, so it calls Ref:apply ...
Is this a fundamental limitation that i cannot read Ref values during prepare? If so, how can I work around it? I need to read the values to determine if prepare should return true or false (in fact throw an error because the Txn should not be retried in that case).
thanks, -sciss-
Before-commit handlers are run before the final validation, so they can
perform reads and writes at will. Before-commit handlers are also
allowed to add additional before-commit handlers or write resources, so
you might be able to replace a problematic write resource with a
before-commit handler that enqueues a write resource as its last action.
- Nathan
i am using this now for on-commit actions such as freeing resources. e.g. a shared-resource counter x:
val x = Ref( 3 )
val touched = Ref( false )
def dec( implicit t0: Txn ) {
if( !touched.swap( true )) t0.beforeCommit( t1 => {
println( "BEFORE COMMIT" )
touched.set( false ) // make sure it is reset for the next transaction
if( x()( t1 ) == 0 ) {
println( "IS ZERO" )
t1.addWriteResource( new Txn.WriteResource {
def prepare( t: Txn ) = true
def performCommit( t: Txn ) {
println( "COMMIT --> FREEING RESOURCE " )
// ...
}
def performRollback( t: Txn ) {
println( "ROLLBACK --> IGNORED" )
}
}, Int.MaxValue )
}
}, Int.MaxValue )
x -= 1
if( x() < 0 ) error( "RESOURCE WAS NOT ALLOCATED ANY MORE" )
}
STM.atomic { implicit t => dec; dec; dec } // --> free is called once
STM.atomic { implicit t => dec; dec; dec } // --> exception raised --> write resource not called
STM.atomic { implicit t => x.set( 3 )}
STM.atomic { implicit t => dec; dec; dec } // --> free is called once
seems to work fine. the only tricky bit is to ensure that beforeCommit is only invoked once per transaction, hence the need for another auxiliarly Ref "touched"... i don't know if there is a more elegant approach?
best, -sciss-
val registered = new TxnLocal[Boolean]
def dec(implicit t: Txn) {
if (!registered.get) {
registered.set(true)
t.beforeCommit( t0 => {
...
If your application logic allows it, it's better to release resources in
an after-commit handler than in a write resource. This will allow other
transactions to access the Ref-s updated by the transaction while the
resource release is in progress.
- Nathan
object Test {
val gens = Ref( Set.empty[ String ])
private val gensTouched = new TxnLocal[ Boolean ] {
override def initialValue( tx: Txn ) = false
}
def add( s: String )( implicit tx: Txn ) {
checkTouch
gens.transform( _ + s )
}
def remove( s: String )( implicit tx: Txn ) {
checkTouch
gens.transform( _ - s )
}
def dummy( implicit tx: Txn ) { checkTouch }
private def checkTouch( implicit tx: Txn ) {
// if( !gensTouched.swap( true ))
val wasTrue = gensTouched.get
if( !wasTrue ) {
gensTouched.set( true )
val oldSet = gens()
tx.beforeCommit( tx0 => {
val newSet = gens()( tx0 )
if( oldSet != newSet ) {
tx.afterCommit { tx1 =>
// observer notication goes here
println( "ADDED " + (newSet.diff( oldSet )) + "; REMOVED " + (oldSet.diff( newSet )) )
}
}
}, Int.MaxValue )
}
}
}
STM.atomic { implicit t => Test.add( "Gaga" )}
STM.atomic { implicit t => Test.remove( "Gaga" )}
STM.atomic { implicit t => Test.dummy }
seems to be fine.
i wonder why TxnLocal doesn't share its mehods with Ref, like through a common trait. Would be nice to have "swap", "transform" etc. Also the need to subclass when using an initialValue is in line with java's ThreadLocal, but against Scala's discouragement of null values and the object Ref's apply method. I would suggest to make it a trait with construction through the companion object ("apply" taking an initial value, and "make" creating the uninitialzed variant).
best, -sciss-
Am 24.06.2010 um 19:04 schrieb Nathan Bronson:
if (gensTouched.swap(true)) {
..
}
A trait with shared functionality between TxnLocal and Ref is possible,
but I'm not sure of a use case where code can be generalized over the
two implementations. Within a single transaction they behave similarly,
but if they are passed around outside atomic blocks their behavior is
quite different. Many of Ref's methods do make sense for TxnLocal,
though.
In this particular example I don't think you need to override
initialValue to false, since the null default initial value will be
unboxed to false. Hmm. Maybe the best way to initialize would be to
use a by-name parameter:
object TxnLocal {
def apply[A](initialValue: => A): TxnLocal[A]
}
Then you can write
val loc = TxnLocal(true)
or
val uuid = TxnLocal { generateNewUUID() }
This make it harder to get the current Txn, though, if it is needed
during the initialValue computation.
Thanks,
Nathan
1) they provide a compile-time check that code expecting to be in a
transaction is actually in a transaction
2) they improve performance
As to the relative importance of the positives and the negatives ...
- Nathan
On Sun, 2010-06-27 at 22:30 +0100, Sciss wrote: