Skip navigation

Release: 2.5.4 Previous Releases
Publish Date: March, 2008

Article Rating?


Configuring Terracotta


This guide contains instructions on how to setup your tc-config.xml file so that your application will be clustered using Terracotta.

Overview of the Configuration Process

Typically, getting Terracotta integrated is a simple process. It is iterative, requiring you to tell Terracotta which classes need to be instrumented and what locking should be provided to replicate your shared state across many JVMs.

The basic steps of this process are:

  1. Start a Terracotta Server
  2. (optional) Identify a root
  3. Start your Application (a Terracotta Client)
  4. (iteratively) resolve UnlockedSharedObject and TCNonPortableErrors

The process looks approximately like this (click on the picture to see full size):

A basic configuration file

To start you will need a basic configuration file. Here is the most basic configuration file (if you already have a configuration file, use that one instead):

tc-config.xml:

<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <servers>
       <update-check>
         <enabled>true</enabled>
       </update-check>
  </servers>
</tc:tc-config>

Save this file to a file named tc-config.xml. You can now start a Terracotta server:

$ $TC_HOME/bin/start-tc-server.sh

By default, the server will use the filename tc-config.xml to start. If you your TC server started successfully, you should see the following output:

2008-03-15 10:18:50,654 INFO - Configuration loaded from the file at
 '/Users/tgautier/src/forge/cookbook/helloworld/tc-config.xml'.
2008-02-02 11:52:14,719 INFO - Terracotta 2.5.1, as of 20080128-160143 
(Revision 6850 by cruise@rh4mo0 from 2.5)
...
2008-02-02 11:52:17,491 INFO - Terracotta Server has started up as ACTIVE node 
on 0.0.0.0:9510 successfully, and is now ready for work.

In particular, note the line that states what config file was used to start the server. This config file should correspond with the config file you just created.

Start your application

Now that you have a running server, and working config file, we have to identify a root in your application. If you have a TIM such as EHCache, you may not need a root as one will already have been specified for you. In that case, you can skip to the Instrumentation and Locking section.

Configure a root

Configuring a root is simple. Simply identify a point in your application that represents shared state. Usually this shared state is held by a HashMap, an Array, or some other java.util Collection.

For example, in the HashMap recipe the HashMap held by the field map is configured to be a root.

The section in the tc-config.xml file that controls roots is called roots and should reside inside the application/dso xml element.

The HashMap config is repeated here for reference. Notice the roots section, which identifies the map field to be a root in the Main class:

<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <application>
    <dso>  
      <locks>
        <autolock>
           <method-expression>void Main.put(..) </method-expression>
        </autolock>
        <autolock>
           <method-expression>void Main.list(..) </method-expression>
           <lock-level>read</lock-level>
        </autolock>
      </locks>
      <roots>
        <root>
          <field-name>Main.instance</field-name>
        </root>
      </roots>
    </dso>
  </application>
</tc:tc-config>

Instrumentation and Locking

After you have identified a root, your application is ready to run. When you run it, however, you will most likely run into two kinds of Exceptions:

  • TCNonPortableObjectError exception
  • UnlockedSharedObjectException exception

Don't panic. These are normal!

Next, we will describe how to resolve them.

Resolving TCNonPortableObjectError - Configuring Instrumentation

This Error indicates that a class that has not been instrumented by Terracotta must be instrumented.

All classes that will be instrumented by Terracotta must be instrumented at class load time. This restriction is due to the way that Terracotta adds clustering behavior to your classes at runtime.

When you receive this error, it will inform you precisely what configuration you should add to your config file. For example, let's look at the Instrumentation Recipe. Note that the Instrumentation Recipe shows us how to add Instrumentation for our classes. It already has instrumentation configured. But for this example, let's suppose there is only the root section of the config file, which would look like this:

<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <application>
    <dso>  
      <roots>
        <root>
          <field-name>Main.instance</field-name>
        </root>
      </roots>
    </dso>
  </application>
</tc:tc-config>

Now when we run this application:

$ dso-java.sh Main

We are rewarded with a NonPortableObjectError, which looks like this:

Exception in thread "main" com.tc.exception.TCNonPortableObjectError: 
*******************************************************************************
Attempt to share an instance of a non-portable class referenced by a portable class. This
unshareable class has not been included for sharing in the configuration.

For more information on this issue, please visit our Troubleshooting Guide at:
http://terracotta.org/kit/troubleshooting

Referring class       : Main
Referring field       : Main.counter
Thread                : main
JVM ID                : VM(1)
Non-portable root name: Main.instance
Non-included class    : Counter

Action to take:

1) Reconfigure to include the unshareable classes
   * edit your tc-config.xml file
   * locate the <dso> element
   * add this snippet inside the <dso> element

       <instrumented-classes>
         <include>
           <class-expression>Counter</class-expression>
         </include>
       </instrumented-classes>

   * if there is already an <instrumented-classes> element present, simply add
     the new includes inside it

It is possible that some or all of the classes above are truly non-portable, the solution
is then to mark the referring field as transient.

Notice at the end of the Exception, it tells us how to fix our config file. We need to add the Counter class to the instrumentation list. This is done by adding the snippet of configuration provided in the Exception to the application/dso xml element. Our resulting tc-config.xml file now looks like this:

<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>Counter</class-expression>
        </include>
      </instrumented-classes>
      <roots>
        <root>
          <field-name>Main.instance</field-name>
        </root>
      </roots>
    </dso>
  </application>
</tc:tc-config>

In your application, you may have to repeat this process several times, until all of the classes that are being shared are accounted for.

Note that it is also possible to use wildcards to identify classes.

The Wildcard Recipe shows an example of how to use pattern matching. Wildcards can be used, for example, to identify an entire package of classes. Our sharededitor sample, which is shipped with Terracotta, makes use of this short-cut.

Instead of listing each class, you can use a wildcard to match all of the classes. For example, an entire package can be identified using a single pattern such as demo.sharededitor.models.*.

Once you have updated your configuration fiile, you can start your application again, and resolve any further Exceptions.

Resolving UnlockedSharedObjectException - Configuring Locking

An UnlockedSharedException simply means that Terracotta has detected that our application has tried to update a value in a shared object without a Terracotta lock present.

Terracotta locks are very much the same as standard Java synchronization. The big difference between Terracotta locking and Java synchronization is that Terracotta requires that you have a lock to update a shared object, while Java will let this happen without complaining.

This is actually a good thing, because with Terracotta, your application is now running in a clustered environment, and a clustered environment is equivalent to having multiple threads. When multiple threads have write access to shared data, just as in normal Java, you must always protect access to that data. Terracotta saves you from finding out race conditions or data corruption at run-time by detecting and reporting attempts to update shared data outside of a lock.

For more details on the similarities, and differences, between Terracotta locking and Java locking, read howto:Terracotta Locking Overview.

Let's use the Instrumentation Recipe again. So far, we have added the proper instrumentation configuration to our config file. However, when we run this example again, it tries to update a counter in a shared object, so we now get an UnlockedSharedException, which looks like this:

com.tc.object.tx.UnlockedSharedObjectException: 
*******************************************************************************
Attempt to access a shared object outside the scope of a shared lock.  
All access to shared objects must be within the scope of one or more shared locks
defined in your Terracotta configuration.  

Please alter the locks section of your Terracotta configuration so that this access
is auto-locked or protected by a named lock.

For more information on this issue, please visit our Troubleshooting Guide at:
 http://terracotta.org/kit/troubleshooting


    Caused by Thread: main  in  VM(2)
    Shared Object Type: Counter
*******************************************************************************

       <elided>
	at com.tc.object.TCObjectImpl.objectFieldChanged(TCObjectImpl.java:320)
	at com.tc.object.TCObjectImpl.intFieldChanged(TCObjectImpl.java:360)
	at Counter.__tc_setcount(Counter.java)
	at Counter.count(Counter.java:13)
	at Main.count(Main.java:14)
	at Main.main(Main.java:20)

This tells us several things. First of all, we know that our application tried to update a field on a shared object without a Terracotta lock present.

This can be due to two things:

  1. We did not configure Terracotta locking for this code
  2. The code itself does not have a synchronization that Terracotta can use as a boundary

Here's how we can resolve these issues. First, identify the method upon which the lock should be applied.

Identify the method for configuration

We can identify what method was in question by analyzing the stack trace. Let's look at the stack-trace:

<elided>
	at com.tc.object.TCObjectImpl.objectFieldChanged(TCObjectImpl.java:320)
	at com.tc.object.TCObjectImpl.intFieldChanged(TCObjectImpl.java:360)
	at Counter.__tc_setcount(Counter.java)
	at Counter.count(Counter.java:13)
	at Main.count(Main.java:14)
	at Main.main(Main.java:20)

In this stack trace, the method that is causing the problem is Counter.count(). We can figure this out by looking for the line directly below the first line of Terracotta injected code. Terracotta injected code always begins with _tc so the first line in this stack trace of Terracotta injected code is at Counter.__tc_setcount(Counter.java)}}.

The line directly below this code is Counter.count(Counter.java:13) meaning the Counter.count() method is to blame for this particular exception. Furthermore, we can actually make a pretty good guess as to what went wrong - first of all, we can see that the exact spot in the code that tripped us up was line 13. If you have the source code, pull up the Counter class and you can see what the code is that triggered this exception.

Here it is, from the Counter.java class in the Instrumentation recipe:

line 13:       count++;

Even if we don't have access to the source, we can still make a pretty good guess what happened. This is because the Terracotta injected code gives us a clue. The injected code for a field setter will always take the form of "__tc_setfieldname" where fieldname is the name of the field, so we know from this particular code that the Counter class was trying to update the count field when the exception was thrown.

So, now we know what method is causing our problem. Depending on the method, there may or may not be synchronization appropriate for auto-locking. If there is, proceed to the next section. If there is not, skip to the Adding Terracotta locking for code without synchronization section.

Adding Terracotta auto-locking for code with synchronization

If your code already has synchronization, then the only thing you have to do is add the proper Terracotta configuration to auto-lock the code.

Auto-locking is a simple process - it tells Terracotta that for any instance of synchronization it finds within the method you configure, add a Terracotta lock boundary to that code. In effect, this converts a single JVM synchronized boundary into a cluster-wide lock boundary.

In our example above, we can see that in fact the Counter class already has synchronization on it, so we just need to add the correct Terracotta configuration to make this example work.

We add the auto-lock to the application/dso/locks section (if there is not a locks section, you will need to add it). We must specify the method-expression field, and a lock-level. The lock-level defaults to write, so you can leave that off if you like. When we are done, our config file will look like this:

tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>Counter</class-expression>
        </include>
      </instrumented-classes>
      <locks>
        <autolock>
           <method-expression>void Counter.count()</method-expression>
           <lock-level>write</lock-level>
        </autolock>
      </locks>
      <roots>
        <root>
          <field-name>Main.instance</field-name>
        </root>
      </roots>
    </dso>
  </application>
</tc:tc-config>

Adding Terracotta locking for code without synchronization

If no synchronization exists in the method, you can add a Terracotta lock in one of two ways:

  1. Use auto-synchronized
  2. Use named locks

Adding locking using auto-synchronized

Auto-synchronized has the same effect as adding the synchronized keyword to the method before adding Terracotta locking. You can imagine as your class is loaded, Terracotta instruments the class and adds synchronization to the method you have specified as auto-synchronized. Then it adds the auto-lock.

To auto-synchronize a method, use the auto-synchronized="true" attribute on the auto-lock. Adding auto-synchronized, instead of just an auto-lock, to the Counter example, the tc-config.xml file would look like this:

tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>Counter</class-expression>
        </include>
      </instrumented-classes>
      <locks>
        <autolock auto-synchronized="true">
           <method-expression>void Counter.count()</method-expression>
           <lock-level>write</lock-level>
        </autolock>
      </locks>
      <roots>
        <root>
          <field-name>Main.instance</field-name>
        </root>
      </roots>
    </dso>
  </application>
</tc:tc-config>

Adding locking using named locks

If none of the above methods are sufficient for your needs, you can resort to adding a named lock. You should avoid named locks unless absolutely necessary as named locks are global, meaning the lock is very coarse-grained, and may impact the performance of your application.

Adding named locks is very similar to adding auto-synchronized, however in the auto-synchronized case, the lock is acquired on the shared object, whereas a named lock is acquired globally for all instances of shared object.

To add a named lock, use the named lock syntax, like so:

tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-4.xsd">

  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>Counter</class-expression>
        </include>
      </instrumented-classes>
      <locks>
        <named-lock>
            <lock-name>lockOne</lock-name>
           <method-expression>void Counter.count()</method-expression>
           <lock-level>write</lock-level>
        </named-lock>
      </locks>
      <roots>
        <root>
          <field-name>Main.instance</field-name>
        </root>
      </roots>
    </dso>
  </application>
</tc:tc-config>

Confirm that state replication was successful

Once you are finished updating your configuration file, and your application appears to run correctly with Terracotta installed, you can now test to see if the state is truly replicated.

Start a second JVM

You can do this using a second JVM. Start your application again. Of course, depending on your application, you may or may not be able to do this without changes to your application code.

For example, you may need to assign different responsibilities to each JVM. The first JVM may need to be a Producer, and the second a Consumer. Or all JVMs are equivalent. Depending on your application, you may need to factor each Main process into one or more differing startup modes.

Start an Admin Console

You can also launch the Admin Console, and inspect the data in the object browser. This is a good way using a single JVM process to verify that the data you expect to be replicated is in fact shared with Terracotta.

For instructions on running the Admin Console, reference the Admin Console Guide.

Adaptavist Theme Builder Powered by Atlassian Confluence