Error: txn.status is Preparing (reading Refs in a WriteResource)

4 views
Skip to first unread message

Sciss

unread,
Jun 21, 2010, 2:12:35 PM6/21/10
to scala...@googlegroups.com
hi,

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-


Sciss

unread,
Jun 21, 2010, 2:19:32 PM6/21/10
to scala...@googlegroups.com
well, this is just the start. in my design i would need also to successively write Refs during the prepare, so i guess that would certainly violate the system... i'm trying to work around this now.

Sciss

unread,
Jun 21, 2010, 5:48:02 PM6/21/10
to scala...@googlegroups.com
yes, i managed to work around this....

Nathan Bronson

unread,
Jun 23, 2010, 2:48:20 AM6/23/10
to scala...@googlegroups.com
Write resources are checked after the STM's reads and writes have
already been validated. This lets the last WriteResource (the one with
the highest priority value) have the final say over commit over abort,
because if it votes for commit there is nothing else to check.

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

Sciss

unread,
Jun 24, 2010, 7:48:19 AM6/24/10
to scala...@googlegroups.com
hi,

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-

Nathan Bronson

unread,
Jun 24, 2010, 2:04:40 PM6/24/10
to scala...@googlegroups.com
Your code is correct, but it will be cleaner if you use a TxnLocal
(sorry, it's not yet well documented). TxnLocal works like a
ThreadLocal: create a single TxnLocal instance for the duration of the
program (or use case), and then call get and set on it inside a
transaction. If you call get before set in any txn, then the binding
for that txn is initialized to the value returned by the overridable
initialValue method.

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

Sciss

unread,
Jun 24, 2010, 2:13:57 PM6/24/10
to scala...@googlegroups.com
ok, that looks better. i had been wondering what TxnLocal was about. thanks for your comments!

Sciss

unread,
Jun 27, 2010, 5:30:07 PM6/27/10
to scala...@googlegroups.com
here is me trying to implicit a kind of transactional-observer pattern:

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:

Sciss

unread,
Jun 27, 2010, 5:30:42 PM6/27/10
to scala...@googlegroups.com
trying to _implement_ of course. too much implicits in ccstm ;-)

Nathan Bronson

unread,
Jun 28, 2010, 3:45:53 AM6/28/10
to scala...@googlegroups.com
TxnLocal should definitely have a few more methods. Your use case would
be easier with swap, for example:

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

Nathan Bronson

unread,
Jun 28, 2010, 3:58:13 AM6/28/10
to scala...@googlegroups.com
Well, there's fewer implicits in my Scala code than finals in my Java
code :) They do provide two benefits though:

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:

Reply all
Reply to author
Forward
0 new messages