Question why my little test program shows these results

66 views
Skip to first unread message

Oliver Plow

unread,
Feb 20, 2012, 5:33:44 PM2/20/12
to Deuce-STM
Hello,

I wrote a little test program to play around with DeuceSTM, see
classes Account and AccountTest below. The idea is that when running
AccountTest the program would print

0
100

to the console. Because one of the 2 threads in AccountTest.main would
throw an exception (because of negative balance) and then both
transaction would be rolled back. I hope I made no silly mistake and
my test program is meaningful. Note, that a latch is passed on to
Account.transfer to make sure that both threads are in the atomic
block "at the same time".

Problem ist that the output on the console when running the program is

Exception in thread "Thread-2" java.lang.IllegalStateException:
negative balance
not allowed
at scratch.Account.setBalance(Account.java:26)
at scratch.Account.transfer(Account.java:36)
at scratch.Account.transfer(Account.java)
at scratch.AccountTest.run(AccountTest.java:65)
at java.lang.Thread.run(Unknown Source)
10
90

I first created an instrumented out.jar file:

java -jar deuceAgent-1.3.0.jar accounttest.jar out_accounttest.jar

On the console appeared the lines below:

20.02.2012 22:50:28 org.deuce.transform.asm.Agent transformJar
INFO: Start tranlating source:accounttest.jar
target:out_accounttest.jar
20.02.2012 22:50:28 org.deuce.transform.asm.Agent transformJar
INFO: Closing source:accounttest.jar target:out_accounttest.jar

This looked o.k. to me. Then I ran the program:

java -javaagent:./deuceAgent-1.3.0.jar -cp ./out_accounttest.jar
scratch.AccountTest

As mentioned above this printed

10
90

to the console. This confuses me a bit. Maybe I have a problem in
understanding the usage or some other misconception.

I would be very thankful if someone could have a short look into the
matter and give me a hint what is going one :-).

Thank you and kind regards,
Oliver Plow


Below the code of the aforementioned classes:


import org.deuce.Atomic;

public class Account
{
private int balance = 100;

public Account(int balance){
setBalance(balance);
}

public int getBalance(){
return balance;
}

public void setBalance(int newBalance)
{
if(newBalance < 0){
throw new IllegalStateException("negative balance not
allowed");
}
balance = newBalance;
}

@Atomic
public static void transfer(Account from, Account to, int amount,
CountDownLatch latch) throws InterruptedException{
int newBalance = from.getBalance()-amount;
latch.await();
from.setBalance(newBalance);
to.setBalance(to.getBalance()+amount);
}

}


-------------------------------------


import java.util.concurrent.CountDownLatch;

public class AccountTest implements Runnable {


public Account from;
public Account to;
public int amount = 0;
public CountDownLatch latch;


public AccountTest() {
super();
}


public AccountTest(Account from, Account to, int amount,
CountDownLatch latch) {
super();
this.from = from;
this.to = to;
this.amount = amount;
this.latch = latch;
}


public static void main(String args[])
{
try {
Account from = new Account(100);
Account to = new Account(0);
CountDownLatch latch = new CountDownLatch(1);


AccountTest test1 = new AccountTest(from, to, 90, latch);
AccountTest test2 = new AccountTest(from, to, 110, latch);


Thread th1 = new Thread(test1);
Thread th2 = new Thread(test2);


th1.start();
th2.start();

Thread.sleep(2000);

latch.countDown();

Thread.sleep(2000);

System.out.println(from.getBalance());
System.out.println(to.getBalance());
}
catch (Exception e) {
e.printStackTrace();
}
}


@Override
public void run() {
try {
Account.transfer(this.from, this.to, amount, latch);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Guy Korland

unread,
Feb 20, 2012, 6:09:20 PM2/20/12
to Deuce-STM
10, 90 is a correct answer.
Since you transfer 90 and then 100 from "from" to "to".
While only the first transfer succeed the second failed.

Oliver Plow

unread,
Feb 21, 2012, 5:24:29 PM2/21/12
to Deuce-STM
All right, I see. I thought that because both threads are in the
atomic block when the exception is thrown both threads (aka
transactions) would be rolled back. I was mistaken there, I guess.

But I have a better one now. There is first my test class ValueHolder
where things work as expected. I made use of Semaphores and
CountDownLatches to make sure that the critical situation to test the
behavior of DeuceSTM is created. Then I found out that this doesn't
work since the Semaphores and stuff are then within the atomic block.
So I changed to inserting breakpoints. Let the threads run to the
respective breakpoints, let one thread continue after in the debugger
to create the problem situation, etc. See the sysouts in
ValueHolder.changeValues(...) and ValueMap.changeValues(...) that
comment where to suspend threads and how to continue.

Problem is this, that things work well with the test class ValueHolder
where the variables that are changed are instance variables. I get the
output:

breakpoint 1: only 1 thread allowed to pass
breakpoint 2: both threads have to get here before they may continue
breakpoint 1: only 1 thread allowed to pass
changeValues foo: 6
changeValues bar: 11
foo: 6
bar: 11
breakpoint 1: only 1 thread allowed to pass
breakpoint 2: both threads have to get here before they may continue
changeValues foo: 9
changeValues bar: 12

But things don't work well with class ValueMap where the variables
changed are not instance variables but values in a map stored in an
instance variable. For the test class ValueMap I get some other
output:

breakpoint 1: only 1 thread allowed to pass
breakpoint 1: only 1 thread allowed to pass
breakpoint 2: both threads have to get here before they may continue
changeValues foo: 9
changeValues bar: 12
breakpoint 2: both threads have to get here before they may continue
changeValues foo: 9
changeValues bar: 14
foo: 9
bar: 14

When I step through the debugger through ValueHolder.changeValues(...)
I can see that when the second thread runs through this method a retry
is done, e.g. the given thread jumps back on the stack and runs
ValueHolder.changeValues(...) again. This behavior I can't see when
running the ValueMap test case.

Find the code for these classes below. It's an awful lot of code. My
apologies. I stripped things as much down as I could, though. For my
problem the number of instance variables of some class to be guarded
with an atomic block is not known at compile time. This is why I made
this ValueMap test class in addition.

Maybe you see the problem. I hope I did the instrumentation right also
for the ValueMap test class. I think I did. Find below the code.

Maybe you have some idea.
Thanks and cheers, Oliver


---------------- BEGIN ValueHolder ----------------

public class ValueHolder
{

int foo = 0;
int bar = 0;

public ValueHolder() {
super();
}

public ValueHolder(int foo, int bar) {
super();
this.foo = foo;
this.bar = bar;
}

@Atomic
public void changeValues(int value) throws InterruptedException
{
System.out.println("breakpoint 1: only 1 thread allowed to
pass" );
int tempFoo = foo;
foo = tempFoo + value;
System.out.println("breakpoint 2: both threads have to get
here before they may continue" );
bar = foo + value;

System.out.println("changeValues foo: " + foo);
System.out.println("changeValues bar: " + bar);
}

public static void main(String[] args) throws InterruptedException
{
changeValuesBreakpoints();
}

protected static void changeValuesBreakpoints() throws
InterruptedException
{
ValueHolder valueHolder = new ValueHolder(1, 2);

Thread th1 = new Thread(new
ValueChangerBreakpoints(valueHolder, 3));
Thread th2 = new Thread(new
ValueChangerBreakpoints(valueHolder, 5));

th1.start();
th2.start();

// wait very long till done with debugging or put breakpoint here
Thread.sleep(50000);

System.out.println("foo: " + valueHolder.foo);
System.out.println("bar: " + valueHolder.bar);
}


static class ValueChangerBreakpoints implements Runnable
{
ValueHolder valueHolder = null;
int value = 0;

public ValueChangerBreakpoints() { }

public ValueChangerBreakpoints(ValueHolder valueHolder, int
value) {
super();
this.valueHolder = valueHolder;
this.value = value;
}

public void run() {
try {
valueHolder.changeValues(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

}

---------------- END ValueHolder ----------------

---------------- BEGIN ValueMap ----------------

public class ValueMap
{
public Map<String, Integer> values = new HashMap<String,
Integer>();

public ValueMap()
{
super();
}

public ValueMap(int foo, int bar)
{
super();
values.put("foo", foo);
values.put("bar", bar);
}

@Atomic
public void changeValues(int value) throws InterruptedException
{
System.out.println("breakpoint 1: only 1 thread allowed to
pass");
int tempFoo = values.get("foo");
values.put("foo", tempFoo + value);
System.out.println("breakpoint 2: both threads have to get
here before they may continue");
values.put("bar", values.get("foo") + value);

System.out.println("changeValues foo: " + values.get("foo"));
System.out.println("changeValues bar: " + values.get("bar"));
}

public static void main(String[] args) throws InterruptedException
{
changeValuesBreakpoints();
}

protected static void changeValuesBreakpoints() throws
InterruptedException
{
ValueMap valueMap = new ValueMap(1, 2);

Thread th1 = new Thread(new ValueChangerBreakpoints(valueMap,
3));
Thread th2 = new Thread(new ValueChangerBreakpoints(valueMap,
5));

th1.start();
th2.start();

// wait very long till done with debugging or put breakpoint here
Thread.sleep(50000);

System.out.println("foo: " + valueMap.values.get("foo"));
System.out.println("bar: " + valueMap.values.get("bar"));
}

static class ValueChangerBreakpoints implements Runnable
{
ValueMap valueMap = null;
int value = 0;

public ValueChangerBreakpoints()
{
}

public ValueChangerBreakpoints(ValueMap valueMap, int value)
{
super();
this.valueMap = valueMap;
this.value = value;
}

public void run()
{
try
{
valueMap.changeValues(value);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

}
}

---------------- END ValueMap ----------------

Guy Korland

unread,
Feb 21, 2012, 5:34:17 PM2/21/12
to deuc...@googlegroups.com
The issue is that when using HashMap you're using a boot classloader class.
Such classed are not instrumented by the javaagent nor by your offline instrumentation.
To include those classes  in transaction you need to instrument rt.jar.

Oliver Plow

unread,
Feb 21, 2012, 5:42:16 PM2/21/12
to Deuce-STM
Yeah, I thought the issue would be some sort in this direction.
Instrumenting rt.jar could take a while ;-). I try with subclassing
HashMap and then only instrument that subclass.

Thanks for the answer,
Oliver

Guy Korland

unread,
Feb 21, 2012, 5:55:18 PM2/21/12
to deuc...@googlegroups.com
That won't work since you're probably going to call super methods...

Oliver Plow

unread,
Feb 23, 2012, 9:56:25 AM2/23/12
to Deuce-STM
Hi Guy,

I now did another try with an open source Map implementation that
directly subclasses Object. I used class FastMap from javolution.org.
I instrumented the javolution-5.5.1.jar and then rerun my ValueMap
test, but without success. One of the threads would have to block and
then do a retry as with the ValueHolder test class that changes bare
inst vars. But this does not happen and the values printed to the
console reveal that the mutual exclusion in the atomic block didn't
work.

Meanwhile I got a bit skilled in how to instrument jars and run the
sample programs. So, I think the problem is not caused by me
instrumenting some jar wrong or setting the VM args wrong or something
like that. This is really a pitty. Would be so handy if I could change
values in a map from within an atomic block.

Cheers, Oliver

Guy Korland

unread,
Feb 23, 2012, 2:05:50 PM2/23/12
to deuc...@googlegroups.com
Can you attach your sample?


Sent from Orange email services
--
You received this message because you are subscribed to the Google Groups "Deuce-STM" group.
To post to this group, send email to deuc...@googlegroups.com.
To unsubscribe from this group, send email to deuce-stm+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/deuce-stm?hl=en.

Oliver Plow

unread,
Feb 23, 2012, 3:23:38 PM2/23/12
to Deuce-STM
On 23 Feb., 20:05, Guy Korland <gkorl...@gmail.com> wrote:
> Can you attach your sample?

Hello,

please find below my test class ValueMap. Its inst var "values" is
filled with an instance of FastMap from javolution.org. I downloaded
javolution-5.5.1.jar from here: http://download.java.net/maven/2/javolution/javolution/5.5.1/
It was instrumented this way:

java -jar deuceAgent-1.3.0.jar javolution-5.5.1.jar
inst_javolution-5.5.1.jar

I then also instrumented my ValueMap class by putting it into a jar
and instrumenting that one the same way.

The output I get when running ValueMap.main is:

changeValues foo: 9
changeValues bar: 14
changeValues foo: 9
changeValues bar: 12
foo: 9
bar: 12

Which is not what I expected as already explained in the previous
post. The way I run the test is to stop jumping to the next line in
the debugger after

th1.start();
th2.start();

was executed. Then these 2 new threads were spawned which both run to
the first breakpoint in ValueMap.changeValues. Then, using the
debugger, I let both threads run to the 2nd breakpoint in the same
method. What I expect is that when letting the second thread in the
debugger continue to run to the 2nd breakpoint it would be blocked at
line "int tempFoo = values.get("foo");". This is what happens when I
do the same test steps in the debugger with the ValueHolder test
class, which I already posted in this thread. In this class the 2nd
thread would be blocked (apparently by the instrumented byte code) at
the corresponding line "int tempFoo = foo;". When the 1st thread has
finished executing ValueHolder.changeValues the 2nd thread would be
unblocked and then retries ValueHolder.changeValues. However, this
does not happen with the ValueMap class.

Thanks for any help.
Regards, Oliver



public class ValueMap
{
public Map<String, Integer> values = new FastMap<String,
Thread.sleep(1000);

Guy Korland

unread,
Mar 8, 2012, 11:53:37 PM3/8/12
to Deuce-STM
Hi,

I think I found two issues:
1. Replace:
public Map<String, Integer> values = new FastMap<String,
Integer>();
with
public FastMap<String, Integer> values = new FastMap<String,
Integer>();

As long as your reference is of type Map you need to instrument also
rt.jar.

2. To get consistent results replace:
values.put("bar", values.get("bar") + value);
with
values.put("bar", values.get("foo") + value);

Guy

Oliver Plow

unread,
Mar 11, 2012, 8:31:10 AM3/11/12
to Deuce-STM
Hi Guy,

changing the declaration from Map to FastMap did it :-). Am used too
much to program against an interface that I didn't think about it ...

Thanks for the help, Oliver

Oliver Plow

unread,
Mar 11, 2012, 4:42:32 PM3/11/12
to Deuce-STM
When I recall the behavior I saw when debugging through my little test
program (thread blocks till other thread has released that variable,
then retries) I get reminded of serialized transaction management. I
mean like isolation level serializable in SQL databases. Are there
some plans to support something like isolation level read_committed or
repeatable_read? Just curious.

Regards, Oliver

Guy Korland

unread,
Mar 11, 2012, 5:25:01 PM3/11/12
to deuc...@googlegroups.com
There are no such plans, I think such semantics are counter intuitive for most of the developers.
But, I might be wrong, you're welcome to write one.

Guy
> --
> You received this message because you are subscribed to the Google Groups "Deuce-STM" group.
> To post to this group, send email to deuc...@googlegroups.com.
> To unsubscribe from this group, send email to deuce-stm+...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/deuce-stm?hl=en.
>
>

--
Regards,
Guy Korland
Reply all
Reply to author
Forward
0 new messages