Commit 8302259f authored by Jonathan Mace's avatar Jonathan Mace

Update transit layer implementation and documentation

parent be145be6
package brown.tracingplane;
/**
* It wouldn't be Java without some a camel-case caravan of nouns.
* It wouldn't be Java without a camel-case caravan of nouns.
*
* {@link BaggageProviderFactory} is expected to have a no-arg constructor so that it can be instantiated by reflection.
*/
......
......@@ -10,6 +10,7 @@
<modules>
<module>api</module>
<module>staticapi</module>
<module>transitlayer</module>
</modules>
<parent>
......
# Tracing Plane - Static API
The static `BaggageContext` API provides the following:
The Tracing Plane provides a static API `brown.tracingplane.Baggage` for manipulating `BaggageContext` instances. This static API is the main entry point for manipulating contexts directly. Supported methods are:
* Enables registration of a `BaggageProvider` class using the `baggage.provider` property
* Provides static methods in the `brown.tracingplane.Baggage` class that proxy to the configured `BaggageProvider`.
* `newInstance()` -- creates a new, empty `BaggageContext`; possibly a `null` value
* `branch(BaggageContext)` -- creates a duplicate `BaggageContext`. Typically this just creates a copy. Changes made to the branched `BaggageContext` will not be visible in the original, and vice versa.
* `join(BaggageContext, BaggageContext)` -- merges the values of two `BaggageContext` instances. If there is no conflicting data within the `BaggageContexts`, this resembles a union of their contents. However, if they both contain, e.g., values mapped to the same key, and those values differ, then the `BaggageProvider` must implement some sort of conflict resolution.
* `serialize(BaggageContext)` -- serializes the `BaggageContext` to a binary representation. For a string-based representation, either use a `BaggageProvider`-specified representation, or `base64` encode the binary representation
* `deserialize(BaggageContext)` -- corresponding deserialization method
* `trim` -- trim is a special operation, that is exposed as `serialize(BaggageContext, maximumSerializedLength)`. `BaggageProvider` is expected to provide a serialization method that drops data if it exceeds a certain length threshold.
By default, `baggage.provider` will not be set; if this is the case, a No-Op provider will be used that simply ignores all baggage.
\ No newline at end of file
Example usage in code is, for example, to pass a `BaggageContext` to a new thread:
BaggageContext currentContext;
MyThread newThread = new MyThread(Baggage.branch(currentContext));
Or to serialize a context for inclusion in network calls:
BaggageContext currentContext;
out.write(Baggage.serialize(currentContext));
The Tracing Plane also provides an `ActiveBaggage` API that mirrors these method calls.
Use of the `Baggage` static API depends on a `BaggageProvider` being configured. Typically, this is done automatically by the Tracing Plane distribution that you use. If this is not the case, or if you wish to override the `BaggageProvider` implementation being used, then you can set the `baggage.provider` property to the `BaggageProviderFactory` of your choice, e.g.,
-Dbaggage.provider=brown.tracingplane.impl.NoOpBaggageContextProviderFactory
or in the typesafe `application.conf`:
baggage.provider = "brown.tracingplane.impl.NoOpBaggageContextProviderFactory"
\ No newline at end of file
......@@ -4,14 +4,15 @@ import java.nio.ByteBuffer;
/**
* <p>
* The static methods on the {@link Baggage} class mirror the methods implemented by {@link BaggageProvider}. Calling
* any of these static methods will simply invoke the corresponding method on the current process's registered
* {@link BaggageProvider}.
* </p>
* The static methods in the {@link Baggage} class are the main entry point for manipulating {@link BaggageContext}
* instances. The methods here mirror those provided by the {@link BaggageProvider} interface.
* <p>
*
* <p>
* To register a {@link BaggageProvider}, you must set the <code>baggage.provider</code> property to the appropriate
* class name.
* Use of the {@link Baggage} API depends on a {@link BaggageProvider} being configured. Typically, this is done
* automatically by the Tracing Plane distribution that you use. However, if this is not the case, or if you wish to
* override the {@link BaggageProvider} implementation in use, then set the <code>baggage.provider</code> property to
* the {@link BaggageProviderFactory} of your choice.
* </p>
*
*/
......
......@@ -17,6 +17,13 @@ public class TestStaticAPI {
assertTrue(provider instanceof NoOpBaggageContextProvider);
}
@Test
public void testNoOpBaggageProviderIsNotWrapped() {
BaggageProvider<?> provider = DefaultBaggageProvider.getWrapped();
assertNotNull(provider);
assertTrue(provider instanceof NoOpBaggageContextProvider);
}
@Test
public void testStaticAPICallsInvokeDefaultProvider() {
assertEquals(DefaultBaggageProvider.get(), Baggage.provider);
......@@ -97,7 +104,7 @@ public class TestStaticAPI {
serialize2++;
return null;
}
public void reset() {
isValid = 0;
newInstance = 0;
......@@ -109,8 +116,9 @@ public class TestStaticAPI {
serialize1 = 0;
serialize2 = 0;
}
public void expect(int isValid, int newInstance, int discard, int branch, int join, int d1, int d2, int s1, int s2) {
public void expect(int isValid, int newInstance, int discard, int branch, int join, int d1, int d2, int s1,
int s2) {
assertEquals(isValid, this.isValid);
assertEquals(newInstance, this.newInstance);
assertEquals(discard, this.discard);
......@@ -132,23 +140,23 @@ public class TestStaticAPI {
Baggage.provider = BaggageProviderProxy.wrap(providerForTest);
try {
providerForTest.expect(0,0,0,0,0,0,0,0,0);
providerForTest.expect(0, 0, 0, 0, 0, 0, 0, 0, 0);
Baggage.newInstance();
providerForTest.expect(0,1,0,0,0,0,0,0,0);
providerForTest.expect(0, 1, 0, 0, 0, 0, 0, 0, 0);
Baggage.branch(null);
providerForTest.expect(1,0,0,1,0,0,0,0,0);
providerForTest.expect(1, 0, 0, 1, 0, 0, 0, 0, 0);
Baggage.join(null, null);
providerForTest.expect(2,0,0,0,1,0,0,0,0);
providerForTest.expect(2, 0, 0, 0, 1, 0, 0, 0, 0);
Baggage.discard(null);
providerForTest.expect(1,0,1,0,0,0,0,0,0);
providerForTest.expect(1, 0, 1, 0, 0, 0, 0, 0, 0);
Baggage.serialize(null);
providerForTest.expect(1,0,0,0,0,0,0,1,0);
providerForTest.expect(1, 0, 0, 0, 0, 0, 0, 1, 0);
Baggage.serialize(null, 10);
providerForTest.expect(1,0,0,0,0,0,0,0,1);
providerForTest.expect(1, 0, 0, 0, 0, 0, 0, 0, 1);
Baggage.deserialize(null);
providerForTest.expect(0,0,0,0,0,0,1,0,0);
Baggage.deserialize(null,0,0);
providerForTest.expect(0,0,0,0,0,1,0,0,0);
providerForTest.expect(0, 0, 0, 0, 0, 0, 1, 0, 0);
Baggage.deserialize(null, 0, 0);
providerForTest.expect(0, 0, 0, 0, 0, 1, 0, 0, 0);
} finally {
Baggage.provider = originalProvider;
}
......
package brown.tracingplane;
package brown.tracingplane.impl;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import brown.tracingplane.impl.NoOpBaggageContextProvider;
import brown.tracingplane.impl.NoOpBaggageContextProviderFactory;
import brown.tracingplane.BaggageContext;
import brown.tracingplane.BaggageProvider;
import brown.tracingplane.BaggageProviderFactory;
public class TestNoOpBaggageContextProvider {
......
.classpath
.settings
.project
bin
target
xtrace-data
application.conf
\ No newline at end of file
# Tracing Plane - Transit Layer
The Tracing Plane provides an out-of-the-box context propagation library called the "Transit Layer". The Transit Layer uses thread-local storage to store `BaggageContext` instances for threads, and provides a static API in `brown.tracingplane.ActiveBaggage` that invokes transit layer methods. `ActiveBaggage` provides methods as follows:
* `set(BaggageContext)` -- set the active `BaggageContext` for this thread
* `get()` -- get the active `BaggageContext` for this thread
* `take()` -- remove and return the active `BaggageContext` for this thread
* `takeBytes()` -- remove, return, and serialize the active `BaggageContext` for this thread
* `discard()` -- discard the active `BaggageContext` for this thread
In addition to methods for manipulating the active baggage, the Transit Layer and `ActiveBaggage` interface provides methods mirroring those in the `Baggage` and `BaggageProvider` interfaces, to implicitly manipulate the current context:
* `branch()` -- creates and returns a duplicate of the currently active `BaggageContext`.
* `join(BaggageContext)` -- merges the values of some `BaggageContext` object into the active context.
The Transit Layer is configurable, and alternative implementations can be used other than the out-of-the-box thread-local implementation. To provide a different implementation, simply implement the `TransitLayer` and `TransitLayerFactory` interfaces, then configure the factory class using the `baggage.transit` property, e.g.:
-Dbaggage.transit=brown.tracingplane.impl.ThreadLocalTransitLayerFactory
or, using typesafe config `application.conf`:
baggage.transit = "brown.tracingplane.impl.ThreadLocalTransitLayerFactory"
\ No newline at end of file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>brown.tracingplane</groupId>
<artifactId>transitlayer</artifactId>
<packaging>jar</packaging>
<name>Baggage Context - Transit Layer Impl</name>
<parent>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext</artifactId>
<version>1.0</version>
</parent>
<dependencies>
<dependency>
<groupId>brown.tracingplane</groupId>
<artifactId>baggagecontext-staticapi</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
</plugins>
</build>
</project>
package brown.tracingplane;
import java.nio.ByteBuffer;
/**
* <p>
* {@link ActiveBaggage} provides static methods that mirror the methods implemented by {@link BaggageProvider} and
* {@link TransitLayer}. Unlike the {@link brown.tracingplane.Baggage} interface, {@link ActiveBaggage} implicitly
* accesses the currently-active {@link BaggageContext} that is being managed by the {@link TransitLayer}. Unless it has
* been configured otherwise, this entails looking up the {@link BaggageContext} in thread-local storage.
* </p>
*
* <p>
* This class also provides static methods to get and set the currently active baggage. These methods proxy to the
* configured {@link TransitLayer}, which is responsible for maintaining active baggage (e.g., in thread-local storage).
* </p>
*
* <p>
* If you wish to manipulate {@link BaggageContext} instances without affecting the currently active baggage context,
* use the static methods on the {@link brown.tracingplane.Baggage} class.
* </p>
*
* <p>
* Using this class requires that a {@link BaggageProvider} has been registered (e.g., using the
* <code>baggage.provider</code> property). By default, the {@link TransitLayer} used will be
* {@link ThreadLocalTransitLayer}; this can be overridden using <code>baggage.transit</code>.
* </p>
*
*/
public class ActiveBaggage {
static TransitLayer transit = DefaultTransitLayer.get();
/** Not instantiable */
private ActiveBaggage() {}
/**
* Discard the currently active {@link BaggageContext}.
*/
public static void discard() {
transit.discard();
}
/**
* Create and return a branched copy of the currently active baggage context. Typically, this will just duplicate
* the active context or increment a reference count. Sometimes it will create a new instance, or even modify the
* contents of the branched context.
*
* @return a baggage instance branched from the currently active context, possibly null
*/
public static BaggageContext branch() {
return transit.branch();
}
/**
* Create and return a branched, serialized copy of the currently active baggage context. Typically, this will just
* duplicate the active context or increment a reference count. Sometimes it will create a new instance, or even
* modify the contents of the branched context.
*
* @return a baggage instance branched from the currently active context and serialized, possibly null
*/
public static byte[] branchBytes() {
return transit.branchBytes();
}
/**
* Merges the contents of <code>otherContext</code> into the currently active context. <code>otherContext</code>
* should not be reused after calling this method, and should be treated as discarded.
*
* @param otherContext another baggage context, possibly null
*/
public static void join(BaggageContext otherContext) {
transit.join(otherContext);
}
/**
* Deserializes the provided context and merges it into the currently active context.
*
* @param serializedContext a serialized baggage context, possibly null
*/
public static void join(ByteBuffer serializedContext) {
transit.join(serializedContext);
}
/**
* Deserializes the provided context and merges it into the currently active context.
*
* @param serializedContext a serialized baggage context, possibly null
*/
public static void join(byte[] serialized, int offset, int length) {
transit.join(serialized, offset, length);
}
/**
* Discards the currently active {@link BaggageContext}, then activates the provided <code>baggage</code>. If
* <code>baggage</code> is just a modified version of the currently active BaggageContext, then it is better to use
* the {@link #update(BaggageContext)} method instead.
*
* @param baggage The new baggage context to activate.
*/
public static void set(BaggageContext baggage) {
transit.set(baggage);
}
/**
* Deserializes the provided context, discards any currently active context, and replaces it with the deserialized
* context.
*
* @param serializedContext a serialized baggage context, possibly null
*/
public static void set(ByteBuffer serializedContext) {
transit.set(serializedContext);
}
/**
* Deserializes the provided context and merges it into the currently active context.
*
* @param serialized a serialized baggage context, possibly null
* @param offset offset into the byte array
* @param length length of serialized bytes
*/
public static void set(byte[] serialized, int offset, int length) {
transit.set(serialized, offset, length);
}
/**
* Gets and removes the currently active {@link BaggageContext}. After calling this method, there will be no active
* {@link BaggageContext}.
*
* @return the current {@link BaggageContext}.
*/
public static BaggageContext take() {
return transit.take();
}
/**
* Gets, removes, and serializes the currently active {@link BaggageContext}. After calling this method, there will
* be no active {@link BaggageContext}.
*
* @return the current {@link BaggageContext}.
*/
public static byte[] takeBytes() {
return transit.takeBytes();
}
/**
* Gets the currently active {@link BaggageContext}. The {@link BaggageContext} instance remains active after
* calling this method. Use {@link #take()} to if you wish to get and remove the currently active context.
*
* @return the active {@link BaggageContext}
*/
public static BaggageContext peek() {
return transit.peek();
}
/**
* Sets the currently active {@link BaggageContext}. A call to this method implies that the provided
* <code>context</code> argument is an updated version of the active context. Conversely, if you intend to replace
* the currently active context (e.g., because a different execution is beginning), use the
* {@link #set(BaggageContext)} method.
*
* @param context an updated version of the currently active baggage context.
*/
public static void update(BaggageContext baggage) {
transit.update(baggage);
}
}
package brown.tracingplane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import brown.tracingplane.impl.NoOpTransitLayerFactory;
import brown.tracingplane.impl.ThreadLocalTransitLayer;
/**
* <p>
* Loads the default configured {@link TransitLayer} using reflection. Checks the <code>baggage.transit</code> property.
* If <code>baggage.transit</code> is not set, then the default transit layer will be {@link ThreadLocalTransitLayer}.
* </p>
*/
public class DefaultTransitLayer {
private static final Logger log = LoggerFactory.getLogger(DefaultTransitLayer.class);
private final TransitLayerFactory factory;
private final TransitLayer transitlayer;
/** Not instantiable */
private DefaultTransitLayer() {
Config config = ConfigFactory.load();
TransitLayerFactory factory = null;
if (!config.hasPath("baggage.transit")) {
log.warn("No TransitLayerFactory has been configured using baggage.transit -- baggage propagation using the ActiveBaggage interface will be disabled");
} else {
try {
String transitLayerClass = config.getString("baggage.transit");
try {
Object instantiated = Class.forName(transitLayerClass).newInstance();
try {
factory = (TransitLayerFactory) instantiated;
} catch (ClassCastException e) {
log.error("The configured baggage.transit should be an instance of TransitLayerFactory; found " +
instantiated.getClass().getName());
}
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
log.error("Unable to instantiate TransitLayerFactory specified by baggage.transit " +
transitLayerClass, e);
}
} catch (ConfigException.WrongType e) {
Object v = config.getAnyRef("baggage.transit");
log.error("Invalid baggage.transit has been configured -- baggage propagation using the ActiveBaggage interface will be disabled. Expected a string for baggage.provider, found " +
v.getClass().getName() + ": " + v);
}
}
if (factory == null) {
this.factory = new NoOpTransitLayerFactory();
this.transitlayer = this.factory.transitlayer();
} else {
this.factory = factory;
this.transitlayer = this.factory.transitlayer();
}
}
private static DefaultTransitLayer instance = null;
private static DefaultTransitLayer instance() {
if (instance == null) {
synchronized (DefaultTransitLayer.class) {
if (instance == null) {
instance = new DefaultTransitLayer();
}
}
}
return instance;
}
/**
* @return the configured {@link TransitLayer} instance. The default transit layer can be set using
* -Dbaggage.transit. If no instance has been configured, this method will return a
* {@link ThreadLocalTransitLayer} that uses simple thread-local storage to store baggage contexts.
*/
public static TransitLayer get() {
return instance().transitlayer;
}
}
package brown.tracingplane;
import java.nio.ByteBuffer;
/**
* <p>
* The static methods in the {@link ActiveBaggage} interface proxy to a {@link TransitLayer} implementation. Typically
* this implementation will be {@link ThreadLocalTransitLayer}, which maintains an active {@link BaggageContext}
* instance using thread-local storage.
* </p>
*
* <p>
* In general, {@link TransitLayer} implementations are responsible for maintaining {@link BaggageContext} instances
* while executions run. They use the notion of an "active" context which represents the {@link BaggageContext} for the
* current execution. Implicitly, if the current execution has no {@link BaggageContext}, it is the same as having an
* empty context.
* </p>
*
* <p>
* A {@link TransitLayer} implementation typically maintains the active {@link BaggageContext} using thread-local
* storage. The purpose of this interface is to enable other implementations to plug in to existing instrumentation.
* </p>
*
* <p>
* {@link TransitLayer} also provides methods that mirror those of {@link BaggageProvider}, such as {@link #branch()}
* which corresponds to {@link BaggageProvider#branch(BaggageContext)}. These methods simply proxy the
* {@link BaggageProvider} method, passing the currently active {@link BaggageContext}.
* </p>
*
* <p>
* All {@link TransitLayer} implementations must have a one-argument constructor that receives the
* {@link BaggageProvider} to use.
* </p>
*/
public interface TransitLayer {
/**
* Discard the currently active {@link BaggageContext}.
*/
public void discard();
/**
* Create and return a branched copy of the currently active baggage context. Typically, this will just duplicate
* the active context or increment a reference count. Sometimes it will create a new instance, or even modify the
* contents of the branched context.
*
* @return a baggage instance branched from the currently active context, possibly null
*/
public BaggageContext branch();
/**
* Create and return a branched, serialized copy of the currently active baggage context. Typically, this will just
* duplicate the active context or increment a reference count. Sometimes it will create a new instance, or even
* modify the contents of the branched context.
*
* @return a baggage instance branched from the currently active context and serialized, possibly null
*/
public byte[] branchBytes();
/**
* Merges the contents of <code>otherContext</code> into the currently active context. <code>otherContext</code>
* should not be reused after calling this method, and should be treated as discarded.
*
* @param otherContext another baggage context, possibly null
*/
public void join(BaggageContext otherContext);
/**
* Deserializes the provided context and merges it into the currently active context.
*
* @param serializedContext a serialized baggage context, possibly null
*/
public void join(ByteBuffer serializedContext);
/**
* Deserializes the provided context and merges it into the currently active context.
*
* @param serializedContext a serialized baggage context, possibly null
*/
public void join(byte[] serialized, int offset, int length);
/**
* Discards the currently active {@link BaggageContext}, then activates the provided <code>baggage</code>. If
* <code>baggage</code> is just a modified version of the currently active BaggageContext, then it is better to use
* the {@link #update(BaggageContext)} method instead.
*
* @param baggage The new baggage context to activate.
*/
public void set(BaggageContext baggage);
/**
* Deserializes the provided context, discards any currently active context, and replaces it with the deserialized
* context.
*
* @param serializedContext a serialized baggage context, possibly null
*/
public void set(ByteBuffer serializedContext);
/**
* Deserializes the provided context and merges it into the currently active context.
*
* @param serialized a serialized baggage context, possibly null
* @param offset offset into the byte array