diff --git a/.gitignore b/.gitignore index 32858aa..62e8b89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +# Build / maven artifacts *.class +target/ +pom.xml.versionsBackup # Mobile Tools for Java (J2ME) .mtj.tmp/ @@ -10,3 +13,8 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +# IntelliJ project files +.idea/ +*.iml + diff --git a/.mvn/wrapper/.gitignore b/.mvn/wrapper/.gitignore new file mode 100644 index 0000000..0781905 --- /dev/null +++ b/.mvn/wrapper/.gitignore @@ -0,0 +1 @@ +!maven-wrapper.jar diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..c6feb8b Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..6637ced --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip \ No newline at end of file diff --git a/.settings.xml b/.settings.xml new file mode 100644 index 0000000..a4250cc --- /dev/null +++ b/.settings.xml @@ -0,0 +1,44 @@ + + + + + + sonatype + ${env.SONATYPE_USER} + ${env.SONATYPE_PASSWORD} + + + bintray + ${env.BINTRAY_USER} + ${env.BINTRAY_KEY} + + + jfrog-snapshots + ${env.BINTRAY_USER} + ${env.BINTRAY_KEY} + + + github.com + ${env.GH_USER} + ${env.GH_TOKEN} + + + + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2b8d189 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,53 @@ +# Run `travis lint` when changing this file to avoid breaking the build. +# Default JDK is really old: 1.8.0_31; Trusty's is less old: 1.8.0_51 +# https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments +sudo: false +dist: trusty + +cache: + directories: + - $HOME/.m2 + +language: java + +jdk: + - oraclejdk8 + +addons: + apt: + packages: + - oracle-java8-installer + +before_install: + # allocate commits to CI, not the owner of the deploy key + - git config user.name "opentracingci" + - git config user.email "opentracingci+opentracing@googlegroups.com" + # setup https authentication credentials, used by ./mvnw release:prepare + - git config credential.helper "store --file=.git/credentials" + - echo "https://$GH_TOKEN:@github.com" > .git/credentials + +install: + # Override default travis to use the maven wrapper + - ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + +script: + - ./travis/publish.sh + +# Don't build release tags. This avoids publish conflicts because the version commit exists both on master and the release tag. +# See https://github.com/travis-ci/travis-ci/issues/1532 +branches: + except: + - /^[0-9]/ + +env: + global: + # Ex. travis encrypt -r opentracing-contrib/java-spanmanager BINTRAY_USER=your_github_account + - secure: "RS267NqO3TR8MuAjP7XK3hItutoXDicHXF5tsKMiPjCTdxlVMWFqFI9zFhXQjKoCAUxsvaRnGIoF3xAZgH/nlC1q7QQzq0XKPpaoSmFv4+EwyjxF1O6DuhaUDDbFbmcFQzofvSUxgl0L45o5z9iJ1Y2Z+4kesWA/6jD499fhC+ekBIskmFrcI7Y+z92VDt5Y9ZPf49/P4iVQHiKk02omHa7OIR2wL3HHXuPy8lmKTky8p/IJa0tGQq7DfoD78ud3OaiXpEr4IjBLcJMGMOCO6DGS2/G8UfPrMEWv8iWGua2MMgfuHvIgtGVxTVJ+OtDuWndbzP558tjz33h2yxE/ASpVGtznRkEQlK6F1Yno3INhq9j3OEU0DG+vLluT1zWdkNriVkza89Wp9CBzi1IszV+9+cjkUWDWxomDu4K+O9y9RpBsXgxrTTBqymopKkg/8KHXWNjTEZRGqlgYvZD37nvpxIFzf35lXcK6HcWsFYoYlIB8h/Iieboymy8uyEcqK1TSsQFR88ZaacT4fK+goa+rFdcsTUO7sUzZJOmLQGgvRCKARu80pN/k2+d+XPRvCnl+pcSB0s8ZoInTSEi+gB3uNw1uyl8bzhy3mIlw6eMaUiT7krZzQCv94tl6tyrVyOXavsnNsRzBbkxC3w9CtK9DVqQSYFYQ8bZKX/KLl4c=" + # Ex. travis encrypt BINTRAY_KEY=xxx-https://bintray.com/profile/edit-xxx --add + - secure: "ZOm+CpZs0hUBc5eREK9i87HL7bmHSDENdpH9hY4N/MPPaYLb04MEkaJ8pW1qGeLbmumATXcE3mfWdJ95LKROn+qUBdUofXbq430EScOkMnIzntkyfKVeq5wr/qigArkhgY6S70u6tUrS2NDLnfwR4gBYCHUKY7InpGUR/cggwdnbYVVKuu31j+xylMZNDAA5EXpVzAfeKSEDEiwS0ej5lPfo0KQCN3f3fDjiJASLsiBl5IdxDHwONxvZWqcWOKpRYIfXYSFOFdgdIGWfQarAPJlUsefVV9jXUtCfB2PXRn6fdScjE0LOWcfQSYdVO+l8sslpufp+DPH0V7edWwZbFgshUIETtu0eoAOLmGbd571WzVmAnYpalY8m4MJFFLVjLe32npvDtxvibo1vFp7l9NPvkQCV6BwS8iLsWJDpraeJmb3ZfOr1hUObJs/JayDVEe6rB3nSkjs4Y5dyBc3hPurPBnYWl4HSDsRmKZ1ieGaIh8FuEOl0MToDO/ZCTQJB3A6CvnlYbI0t4WFU7Q68zRSJih4bUxaQD3aERzGgpMatGAoxMi1hfv0krMAJQTHSZqKpTrftJ/CTjXn2cJavO+Eai3lODzZ42tnz/blzOApfSKU4biu3QwohTMIPqaLOA1KfF6hJkSKfsTzjbSnqqdEc09liYSCoRbLleEXBr9w=" + # Ex. travis encrypt GH_TOKEN=XXX-https://github.com/settings/tokens-XXX --add + - secure: "ktakwcLywOMAMeJpj2+haZHgdkbs5Me1JA1fDoPIcNJMttHSa3A8JEYxTFmwX5/ybNuXJpL98HblGQfYPIH1oL4oyO7oif1t/LzjUibFUDzgMVMgUy8oirGUVRwOH9Uj3/eILBLbMQO+yodbDvHHhiVT+xv1K1q3Qy+vwSabSr8YjYADvlL4Oz60C2BAPLpuG/VjZPQ96nQopVI/a+3IvgEega0kRHw0LFFT87Ht/MOpo++uU9FPJ8QU3Tf7EYuuuR+xJIJ4W04Cewp6TdreiPWynf1aLwVnsy4cXIt+e6K0KJg9PGtlGjwJGllB7TMYtFmQ+qCyVYs+OqmDpUqQ4JCUKpZ6KyRIdh+I+3CNYttMCFYBTvBvMBFEyzSO2OOJWysF3vahCmCVHkZdJacHQ5gaGrVpzZ/9nDdkmO2trH6xlmp+MPWehXsuMLgIVJLyJSEZb/hQCojzjpDY7vTAyleC/5DwyF7iInf7CO6ZNHnWODyMfgLOkB8yuXhRAQjMejXObRYcT5u6wiJaQb/m3IqXOdrZuOo/dbFzMZSnIXmC6TaK6NnWu3Noibh+Hyg9odsy7tAPCAsJUOBy+JQdeIJNeV76cxigN37EQSlFD9k2s82LIFrNqC0za3kuQfE+iWPeTrTBVJfIxq7KZrN9f0jfj/gvpPA0borwRKt+5xQ=" + # Ex. travis encrypt SONATYPE_USER=your_sonatype_account + - secure: "VyTo3xcnUMpXofZlgY5D8qBrypOclWPcyG1u8skwozrJkeJMg31DOkU6s3dLxPq1NMBKaiVnaKMzOrTvGMu11+7pWc+afamZ1UW/I4ZxToZPl2/Dq8iTHqt1PiIwNd6Y1cKrOC2bnBKjXONN0rpoHmgFERvLYqfXTjs0qZrFQRth3TfgxyQux3EXBmxZioQYEgZqwOISPRWyK0tFJ+9VNpUFbzw7JnpP4XwFRe2HaVyCRpbWyQ1pIhcr98dZEa9KsdGJtnOVdZm+Ej8QyNvTOwJgqFOblstaUcYTXVZErZWDQ9OMd8plExhPzpaqymz/0xB/tXaHnHTEf0yQCpjHi18UzrQveXJ20jk+n0unuqGeq6L/XpWLG2kIxTeBPt4MCDd+FCyli55jiQpzlRqAwOcWndiy47qoJzmqseWvp7DKoNvXwwSRgPXJ5e2u/ZQdbRmIwZmzxemhTBoa23ekoP2LvKPJH2mfDc5e1S10boHVjmuahKeKAbSw28CJ1m3mBGg1wyTLVtneauOaAK4zCzXfZ6JkdHKQSoAD+AiYNrYnfZk76kcUNVgi+wqadHgNXinni1WGM8MGu9nfsmYSY2PO4/2zqmASYhc417LRzkx0iu4wNMiy8zowbbhnFNxt8LrLbJV90eu6Z4XEcPAThfL5xzH8bSvyXuiYuT5sXV0=" + # Ex. travis encrypt SONATYPE_PASSWORD=your_sonatype_password + - secure: "kQYGrYzMw8lN0Vp4GbEOHxYIR+w1YtJ6LaYz00x6Lb/3pyaINFDGnLmKuANNXvHr4UyrT+q4bg8EWII4ctdbbvsSOBvxq0VkADBBGpYvlpa92598TsrfeJ9ZDz5kthajyQXK1kZU0kkBRHVbJ4I1CBqlJOHRYv9h2m99D+Fe3sHnPePre2e2BNJbFJj6EomKBTNarZQOdR7EFJ3DBmsalAmcZm52npSaeI/8D06QGNlEqvBufOwF6EYNN+IVJZsmhjoSyrYx/qqPIecQOVZfaDCk5Y6QNU4JWQaSDtLfVZjJ3QTIj86fjRqd30TBUtl9Dq+iBwuRCImBV6I9PpfLXoQMIaguE1UWt4lfDwBhsylnm9g35BQ/V0iOFnWXyhablK6irKAgYXlQMuvxaqrNnLf6tK5DIoPO0nJS9n1Sj8uH6sW/MfDJ4I3V7wfBDBN40Bi3N8LAcmf9vcDfKq6yqi4rXQ45eM2BffV/fWt4miN7vBjnBEyWPAzAfNr4b1m5U5eHtyk6sLrzWUK9eMIlN3CrIAKy7LLOYhgi7vnYuqyU9wCAzxIORFKE6NSn3bokTCr3WchCNmM2fvHpIh9b6BzPBgiO9MGllxq4PpyXpgpG9zygpA1t5ZXnUD1/Gs5UWy6peHqV85RtKBv/M/SwDFlewZG/Aj0LNThV6a9ZEyA=" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b12db64 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing to opentracing-contrib / java-spanmanager + +If you would like to contribute code you can do so through GitHub by forking the repository +and sending a pull request (on a branch other than `master` or `gh-pages`). + + +## License + +By contributing your code, you agree to license your contribution under the terms of the APLv2: +https://github.com/opentracing-contrib/java-spanmanager/blob/master/LICENSE + +All files are released with the Apache 2.0 license. + +If you are adding a new file it should have a header like below. +This can be automatically added by running `./mvnw license:format`. + +``` +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + ``` diff --git a/README.md b/README.md index 2053e3d..822a1b2 100644 --- a/README.md +++ b/README.md @@ -1 +1,229 @@ -# java-spanmanager +[![Build Status][ci-img]][ci] [![Released Version][maven-img]][maven] + +# Span manager for Java + +:heavy_exclamation_mark: **Warning:** This library is still a work in progress! + +This library provides a way to manage spans and propagate them to other threads. + +## SpanManager + +Defines _current span_ management. + +A SpanManager separates the creation of a `Span` from its use later on. +This relieves application developers from passing the current span around through their code. +Only tracing-related code will need access to a SpanManager reference, provided as an ordinary dependency. + +SpanManager provides the following methods: + + 1. `manage(span)` makes the given span the _current_ managed span. + Returns a `ManagedSpan` containing a `release()` method + to later 'unmanage' the span with. + 2. `currentSpan()` returns the _current_ managed span, + or the `NoopSpan` if no span is managed. + 3. `clear()` provides unconditional cleanup of _all managed spans_ for the current process. + +## DefaultSpanManager + +A _default_ SpanManager maintaining a `Stack`-like `ThreadLocal` storage of _linked managed spans_. + +Releasing a _linked managed span_ uses the following algorithm: + 1. If the released span is not the _current_ span, the current span is left alone. + 2. Otherwise, the first parent that is not yet released is set as the new current span. + 3. If no current parents remain, the current span is cleared. + 4. Consecutive `release()` calls for already-released spans will be ignored. + +## Concurrency + +### SpanPropagatingExecutorService + +This `ExecutorService` _propagates the current span_ +from the caller into each call that is executed. +The current span of the caller is obtained from the configured `SpanManager`. + +_Please Note:_ The current span is merely _propagated_ (as-is). +It is explicitly **not** finished when the calls end, +nor will new spans be automatically related to the propagated span. + +### SpanPropagatingExecutors + +Contains factory-methods similar to standard java `Executors`: + - `SpanPropagatingExecutors.newFixedThreadPool(int, SpanManager)` + - `SpanPropagatingExecutors.newSingleThreadExecutor(SpanManager)` + - `SpanPropagatingExecutors.newCachedThreadPool(SpanManager)` + - Variants of the above with additional `ThreadFactory` argument. + +## ManagedSpanTracer + +This convenience `Tracer` automates managing the _current span_: + 1. It wraps another `Tracer`. + 2. `Spans` created with this tracer are: + - automatically _managed_ when started, and + - automatically _released_ when finished. + +## Examples + +### Manually propagating any Span into a background thread + +To propagate a `Span` into a new `Thread`, the _currentSpan_ from the caller must be +remembered by the `Runnable`: + +```java + class ExampleRunnable implements Runnable { + private final SpanManager spanManager; + private final Span currentSpanFromCallingThread; + + ExampleRunnable(SpanManager spanManager) { + this(spanManager, NoopSpan.INSTANCE); + } + + private ExampleRunnable(SpanManager spanManager, Span currentSpanFromCallingThread) { + this.spanManager = spanManager; + this.currentSpanFromCallingThread = currentSpanFromCallingThread; + } + + ExampleRunnable withCurrentSpan() { + return new ExampleRunnable(spanManager, spanManager.currentSpan()); + } + + @Override + public void run() { + try (ManagedSpan parent = spanManager.manage(currentSpanFromCallingThread)) { + + // Any background code that requires tracing + // and may use spanManager.currentSpan(). + + } // parent.release() restores spanManager.currentSpan() to NoopSpan in new thread. + } + } +``` + +Then the application can propagate this _currentSpan_ into background threads: + +```java + class App { + public static void main(String... args) throws InterruptedException { + Config config = ...; + Tracer tracer = config.getTracer(); + SpanManager spanManager = config.getSpanManager(); + ExampleRunnable runnable = new ExampleRunnable(spanManager); + + try (Span appSpan = tracer.buildSpan("main").start(); // start appSpan + ManagedSpan managed = spanManager.manage(appSpan)) { // update currentSpan + + Thread example = new Thread(runnable.withCurrentSpan()); + example.start(); + example.join(); + + } // managed.release() + appSpan.finish() + + System.exit(0); + } + } + +``` + +### Threadpool that propagates SpanManager.currentSpan() into threads + +```java + class TracedCall implements Callable { + SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance(); + + @Override + public String call() { + Span currentSpan = spanManager.currentSpan(); // Propagated span from caller + // ... + } + } + + class Caller { + SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance(); + ExecutorService threadpool = new SpanPropagatingExecutorService(anyThreadpool(), spanManager); + + void run() { + // ...code that sets the current Span somewhere: + try (ManagedSpan current = spanManager.manage(someSpan)) { + + // scheduling the traced call: + Future result = threadpool.submit(new TracedCall()); + + } + } + } + +``` + +### Propagating threadpool with 'ManagedSpan' Tracer + +When starting a new span and making it the _currentSpan_, the manual example above used: +```java + try (Span span = tracer.buildSpan("main").start(); // start span + ManagedSpan managed = spanManager.manage(span)) { // set currentSpan() to span + // ...traced block of code... + } +``` + +The `ManagedSpanTracer` automatically makes every started span the current span. +It also releases it again when the span is finished: + +```java + class Caller { + SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance(); + Tracer tracer = new ManagedSpanTracer(anyTracer(), spanManager); + ExecutorService threadpool = new SpanPropagatingExecutorService(anyThreadpool(), spanManager); + + void run() { + try (Span parent = tracer.buildSpan("parentOperation").start()) { // parent == currentSpan + + // Scheduling the traced call: + Future result = threadpool.submit(new TracedCall()); + + } // parent.finish() + ((ManagedSpan) parent).release() + } + } +``` + +### Example with asynchronous request / response filters + +When asynchronous processing is handled by separate request/response filters, +a `try-with-resources` code block is insufficient. + +Existing filters that start / finish new spans asynchronously can simply +be supplied with the `ManagedSpanTracer` around the existing tracer. +This sets the _currentSpan_ from the request filter +and calls `release()` automatically from the response filter +when the existing filter finishes the span. +An example would be using the [opentracing jaxrs filters](https://github.com/opentracing-contrib/java-jaxrs) +in combination with the ManagedSpanTracer: +```java + // Add example when opentracing jaxrs library stabilizes +``` + +Alternatively, the following hypothetic filter pair could be used on an asynchronous server: +Handling the request: +```java + final SpanManager spanManager = ... // inject or DefaultSpanManager.getInstance(); + void onRequest(RequestContext reqCtx) { + Span span = ... // either obtain Span from previous filter or start from the request + ManagedSpan managedSpan = spanManager.manage(span); // span is now currentSpan. + reqCtx.put(SOMEKEY, managedSpan); + } +``` + +For the response: +```java + final SpanManager spanManager = ... + void onResponse(RequestContext reqCtx, ResponseContext resCtx) { + spanManager.clear(); // Clear stack containing the currentSpan if this is a boundary-filter + // or: + // ManagedSpan managedSpan = reqCtx.get(SOMEKEY); + // managedSpan.release(); + + // If the corresponding request filter starts a span, don't forget to call span.finish() here! + } +``` + + [ci-img]: https://img.shields.io/travis/opentracing-contrib/java-spanmanager/master.svg + [ci]: https://travis-ci.org/opentracing-contrib/java-spanmanager + [maven-img]: https://img.shields.io/maven-central/v/io.opentracing.contrib/java-spanmanager.svg + [maven]: http://search.maven.org/#search%7Cga%7C1%7Cjava-spanmanager diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..9fa4aa1 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,6 @@ +# OpenTracing Release Process + +This repo uses semantic versions. Please keep this in mind when choosing version numbers. + +For the up-to-date release process, please refer the +[release process from the OpenTracing Java API](https://github.com/opentracing/opentracing-java/blob/master/RELEASE.md). diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..fc7efd1 --- /dev/null +++ b/mvnw @@ -0,0 +1,234 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CMD_LINE_ARGS + diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..0010480 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..690180f --- /dev/null +++ b/pom.xml @@ -0,0 +1,256 @@ + + + + + 4.0.0 + + + io.opentracing.contrib + opentracing-spanmanager + 0.0.1-SNAPSHOT + jar + + + SpanManager library + Manager of current span and propagating it accross threads + https://github.com/opentracing-contrib/java-spanmanager + 2017 + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + UTF-8 + UTF-8 + 1.6 + + 0.20.7 + 4.12 + 1.3 + 1.10.19 + + 0.3.3 + 2.5.1 + 2.4 + 2.10.3 + 2.11 + 2.5.3 + 0.1.0 + + + + + Sjoerd Talsma + sjoerd@talsma-ict.nl + https://github.com/sjoerdtalsma + Talsma ICT + + + + + https://github.com/opentracing-contrib/java-spanmanager/tree/master + scm:git:git://github.com/opentracing-contrib/java-spanmanager.git + scm:git:git@github.com:opentracing-contrib/java-spanmanager.git + + + + Github + https://github.com/opentracing-contrib/java-spanmanager/issues + + + + + bintray + https://api.bintray.com/maven/opentracing/maven/java-spanmanager/;publish=1 + + + jfrog-snapshots + http://oss.jfrog.org/artifactory/oss-snapshot-local + + + + + + io.opentracing + opentracing-api + ${opentracing-api.version} + + + io.opentracing + opentracing-noop + ${opentracing-api.version} + + + + io.opentracing + opentracing-mock + ${opentracing-api.version} + test + + + junit + junit + ${junit.version} + test + + + org.hamcrest + hamcrest-library + ${hamcrest.version} + test + + + org.mockito + mockito-all + ${mockito.version} + test + + + + + + + + + io.takari + maven + ${maven-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + + io.zipkin.centralsync-maven-plugin + centralsync-maven-plugin + ${centralsync-maven-plugin.version} + + + + + + maven-compiler-plugin + + ${build.java.version} + ${build.java.version} + ${project.build.sourceEncoding} + true + + + + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + maven-javadoc-plugin + + + generate-javadoc + + jar + + + + + + com.mycila + license-maven-plugin + ${license-maven-plugin.version} + +
${project.basedir}/src/etc/header.txt
+ + .travis.yml + .gitignore + .mvn/** + mvnw* + etc/header.txt + **/.idea/** + LICENSE + **/*.md + src/test/resources/** + src/main/resources/** + + true +
+ + + com.mycila + license-maven-plugin-git + ${license-maven-plugin.version} + + + + + + check + + compile + + +
+ + maven-release-plugin + + false + release + true + @{project.version} + + + + io.zipkin.centralsync-maven-plugin + centralsync-maven-plugin + + opentracing + maven + java-spanmanager + + +
+
+ +
diff --git a/src/etc/header.txt b/src/etc/header.txt new file mode 100644 index 0000000..a9328a1 --- /dev/null +++ b/src/etc/header.txt @@ -0,0 +1,11 @@ +Copyright ${license.git.copyrightYears} The OpenTracing Authors + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License +is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +or implied. See the License for the specific language governing permissions and limitations under +the License. diff --git a/src/main/java/io/opentracing/contrib/spanmanager/DefaultSpanManager.java b/src/main/java/io/opentracing/contrib/spanmanager/DefaultSpanManager.java new file mode 100644 index 0000000..469adf9 --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/DefaultSpanManager.java @@ -0,0 +1,130 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager; + +import io.opentracing.NoopSpan; +import io.opentracing.Span; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Default {@link SpanManager} implementation using {@link ThreadLocal} storage + * maintaining a stack-like structure of linked managed spans. + *

+ * The linked managed spans provide the following stack unwinding algorithm: + *

    + *
  1. If the released span is not the managed span, the current managed span is left alone.
  2. + *
  3. Otherwise, the first parent that is not yet released is set as the new managed span.
  4. + *
  5. If no managed parents remain, the managed span is cleared.
  6. + *
  7. Consecutive release() calls for already-released spans will be ignored.
  8. + *
+ */ +public final class DefaultSpanManager implements SpanManager { + + private static final Logger LOGGER = Logger.getLogger(DefaultSpanManager.class.getName()); + + private static final DefaultSpanManager INSTANCE = new DefaultSpanManager(); + private final ThreadLocal managed = new ThreadLocal(); + + private DefaultSpanManager() { + } + + /** + * @return The singleton instance of the default span manager. + */ + public static SpanManager getInstance() { + return INSTANCE; + } + + /** + * Stack unwinding algorithm that refreshes the currently managed span. + *

+ * See {@link DefaultSpanManager class javadoc} for a full description. + * + * @return The current non-released LinkedManagedSpan or null if none remained. + */ + private LinkedManagedSpan refreshCurrent() { + LinkedManagedSpan managedSpan = managed.get(); + LinkedManagedSpan current = managedSpan; + while (current != null && current.released.get()) { // Unwind stack if necessary. + current = current.parent; + } + if (current != managedSpan) { // refresh current if necessary. + if (current == null) managed.remove(); + else managed.set(current); + } + return current; + } + + @Override + public Span currentSpan() { + LinkedManagedSpan current = refreshCurrent(); + return current != null && current.span != null ? current.span : NoopSpan.INSTANCE; + } + + @Override + public SpanManager.ManagedSpan manage(Span span) { + LinkedManagedSpan managedSpan = new LinkedManagedSpan(span, refreshCurrent()); + managed.set(managedSpan); + return managedSpan; + } + + @Override + public void clear() { + managed.remove(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + private final class LinkedManagedSpan implements SpanManager.ManagedSpan { + private final LinkedManagedSpan parent; + private final Span span; + private final AtomicBoolean released = new AtomicBoolean(false); + + private LinkedManagedSpan(Span span, LinkedManagedSpan parent) { + this.parent = parent; + this.span = span; + } + + @Override + public Span getSpan() { + return span; + } + + public void release() { + if (released.compareAndSet(false, true)) { + LinkedManagedSpan current = refreshCurrent(); // Trigger stack-unwinding algorithm. + LOGGER.log(Level.FINER, "Released {0}, current span is {1}.", new Object[]{this, current}); + } else { + LOGGER.log(Level.FINEST, "No action needed, {0} was already released.", this); + } + } + + @Override + public void close() { + release(); + } + + @Override + public String toString() { + return "LinkedManagedSpan{" + span + '}'; + } + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/SpanManager.java b/src/main/java/io/opentracing/contrib/spanmanager/SpanManager.java new file mode 100644 index 0000000..6dd1295 --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/SpanManager.java @@ -0,0 +1,92 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager; + +import io.opentracing.Span; + +import java.io.Closeable; + +/** + * Defines {@linkplain #currentSpan() current span} management. + *

+ * A SpanManager separates the creation of a {@linkplain Span} from its use later on. + * This relieves application developers from passing the current span around through their code. + * Only tracing-related code will need access to a SpanManager reference, provided as an ordinary dependency. + */ +public interface SpanManager { + + /** + * Return the currently-managed {@link Span}. + * + * @return The current Span, or the NoopSpan if there is no managed span. + * @see SpanManager#manage(Span) + */ + Span currentSpan(); + + /** + * Makes span the current span within the running process. + * + * @param span The span to become the current span. + * @return A managed object to release the current span with. + * @see SpanManager#currentSpan() + * @see ManagedSpan#release() + */ + ManagedSpan manage(Span span); + + /** + * Unconditional cleanup of all managed spans including any parents. + *

+ * This allows boundary filters to release all current spans + * before relinquishing control over their process, + * which may end up repurposed by a threadpool. + * + * @see ManagedSpan#release() + */ + void clear(); + + /** + * To {@linkplain #release() release} a {@link SpanManager#manage(Span) managed span} with. + *

+ * It must be possible to repeatedly call {@linkplain #release()} without side effects. + * + * @see SpanManager + */ + interface ManagedSpan extends Closeable { + + /** + * The span that became the managed span at some point. + * + * @return The contained span to be released. + */ + Span getSpan(); + + /** + * Makes the {@link #getSpan() contained span} no longer the managed span. + *

+ * Implementation notes: + *

    + *
  1. It is encouraged to restore the managed span as it was before this span became managed + * (providing stack-like behaviour).
  2. + *
  3. It must be possible to repeatedly call release without side effects.
  4. + *
+ */ + void release(); + + /** + * Alias for {@link #release()} to allow easy use from try-with-resources. + */ + void close(); + + } +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/concurrent/CallableWithManagedSpan.java b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/CallableWithManagedSpan.java new file mode 100644 index 0000000..561ac0b --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/CallableWithManagedSpan.java @@ -0,0 +1,56 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.concurrent; + +import io.opentracing.Span; +import io.opentracing.contrib.spanmanager.SpanManager; + +import java.util.concurrent.Callable; + +/** + * {@link Callable} wrapper that will execute with a {@link SpanManager#manage(Span) managed span} + * specified from the scheduling thread. + * + * @see SpanManager + */ +final class CallableWithManagedSpan implements Callable { + + private final Callable delegate; + private final SpanManager spanManager; + private final Span spanToManage; + + CallableWithManagedSpan(Callable callable, SpanManager spanManager, Span spanToManage) { + if (callable == null) throw new NullPointerException("Callable is ."); + if (spanManager == null) throw new NullPointerException("Span manager is ."); + this.delegate = callable; + this.spanManager = spanManager; + this.spanToManage = spanToManage; + } + + /** + * Performs the delegate call with the specified managed span. + * + * @return The result from the original call. + * @throws Exception if the original call threw an exception. + */ + public T call() throws Exception { + final SpanManager.ManagedSpan managedSpan = spanManager.manage(spanToManage); + try { + return delegate.call(); + } finally { + managedSpan.release(); + } + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/concurrent/RunnableWithManagedSpan.java b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/RunnableWithManagedSpan.java new file mode 100644 index 0000000..d52cb1b --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/RunnableWithManagedSpan.java @@ -0,0 +1,51 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.concurrent; + +import io.opentracing.Span; +import io.opentracing.contrib.spanmanager.SpanManager; + +/** + * {@link Runnable} wrapper that will execute with a {@link SpanManager#manage(Span) managed span} + * specified from the scheduling thread. + * + * @see SpanManager + */ +final class RunnableWithManagedSpan implements Runnable { + + private final Runnable delegate; + private final SpanManager spanManager; + private final Span spanToManage; + + RunnableWithManagedSpan(Runnable runnable, SpanManager spanManager, Span spanToManage) { + if (runnable == null) throw new NullPointerException("Runnable is ."); + if (spanManager == null) throw new NullPointerException("Span manager is ."); + this.delegate = runnable; + this.spanManager = spanManager; + this.spanToManage = spanToManage; + } + + /** + * Performs the runnable action with the specified managed span. + */ + public void run() { + SpanManager.ManagedSpan managedSpan = spanManager.manage(spanToManage); + try { + delegate.run(); + } finally { + managedSpan.release(); + } + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorService.java b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorService.java new file mode 100644 index 0000000..3f713d6 --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorService.java @@ -0,0 +1,156 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.concurrent; + +import io.opentracing.Span; +import io.opentracing.contrib.spanmanager.SpanManager; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.*; + +/** + * Propagates the {@link SpanManager#currentSpan() current span} from the caller + * into each call that is executed. + *

+ * Note: The current span is merely propagated. + * It is explicitly not finished when the calls end, + * nor will new spans be automatically related to the propagated span. + */ +public class SpanPropagatingExecutorService implements ExecutorService { + private final ExecutorService delegate; + private final SpanManager spanManager; + + /** + * Wraps the delegate ExecutorService to propagate the {@link SpanManager#currentSpan() current span} + * of callers into the executed calls, using the specified {@link SpanManager}. + * + * @param delegate The executorservice to forward calls to. + * @param spanManager The manager to propagate spans with. + */ + public SpanPropagatingExecutorService(ExecutorService delegate, SpanManager spanManager) { + if (delegate == null) throw new NullPointerException("Delegate executor service is ."); + if (spanManager == null) throw new NullPointerException("SpanManager is ."); + this.delegate = delegate; + this.spanManager = spanManager; + } + + /** + * Propagates the {@link SpanManager#currentSpan() custom current span} into the runnable + * and performs cleanup afterwards. + *

+ * Note: The customCurrentSpan is merely propagated. + * The specified span is explicitly not finished by the runnable. + * + * @param runnable The runnable to be executed. + * @param customCurrentSpan The span to be propagated. + * @return The wrapped runnable to execute with the custom span as current span. + */ + private Runnable runnableWithCurrentSpan(Runnable runnable, Span customCurrentSpan) { + return new RunnableWithManagedSpan(runnable, spanManager, customCurrentSpan); + } + + /** + * Propagates the {@link SpanManager#currentSpan() custom current span} into the callable + * and performs cleanup afterwards. + *

+ * Note: The customCurrentSpan is merely propagated. + * The specified span is explicitly not finished by the callable. + * + * @param The callable result type. + * @param callable The callable to be executed. + * @param customCurrentSpan The span to be propagated. + * @return The wrapped callable to execute with the custom span as current span. + */ + private Callable callableWithCurrentSpan(Callable callable, Span customCurrentSpan) { + return new CallableWithManagedSpan(callable, spanManager, customCurrentSpan); + } + + @Override + public void execute(Runnable command) { + delegate.execute(runnableWithCurrentSpan(command, spanManager.currentSpan())); + } + + @Override + public Future submit(Runnable task) { + return delegate.submit(runnableWithCurrentSpan(task, spanManager.currentSpan())); + } + + @Override + public Future submit(Runnable task, T result) { + return delegate.submit(runnableWithCurrentSpan(task, spanManager.currentSpan()), result); + } + + @Override + public Future submit(Callable task) { + return delegate.submit(callableWithCurrentSpan(task, spanManager.currentSpan())); + } + + @Override + public List> invokeAll(Collection> tasks) throws InterruptedException { + return delegate.invokeAll(tasksWithCurrentSpan(tasks, spanManager.currentSpan())); + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return delegate.invokeAll(tasksWithCurrentSpan(tasks, spanManager.currentSpan()), timeout, unit); + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return delegate.invokeAny(tasksWithCurrentSpan(tasks, spanManager.currentSpan())); + } + + @Override + public T invokeAny(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + return delegate.invokeAny(tasksWithCurrentSpan(tasks, spanManager.currentSpan()), timeout, unit); + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List shutdownNow() { + return delegate.shutdownNow(); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } + + private Collection> tasksWithCurrentSpan( + Collection> tasks, Span customCurrentSpan) { + if (tasks == null) throw new NullPointerException("Collection of tasks is ."); + Collection> result = new ArrayList>(tasks.size()); + for (Callable task : tasks) result.add(callableWithCurrentSpan(task, customCurrentSpan)); + return result; + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutors.java b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutors.java new file mode 100644 index 0000000..262cb00 --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutors.java @@ -0,0 +1,124 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.concurrent; + +import io.opentracing.contrib.spanmanager.SpanManager; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** + * Factory-methods similar to standard java {@link Executors}: + *

    + *
  • {@link #newFixedThreadPool(int, SpanManager)}
  • + *
  • {@link #newSingleThreadExecutor(SpanManager)}
  • + *
  • {@link #newCachedThreadPool(SpanManager)}
  • + *
  • Variants of the above with additional {@link ThreadFactory} argument: + * {@link #newFixedThreadPool(int, ThreadFactory, SpanManager)}, + * {@link #newSingleThreadExecutor(ThreadFactory, SpanManager)}, + * {@link #newCachedThreadPool(ThreadFactory, SpanManager)} + *
  • + *
+ * + * @see SpanPropagatingExecutorService + */ +public final class SpanPropagatingExecutors { + + /** + * Private constructor to avoid instantiation of this utility class. + */ + private SpanPropagatingExecutors() { + throw new UnsupportedOperationException(); + } + + /** + * This method returns a {@link Executors#newFixedThreadPool(int) fixed threadpool} that propagates + * the current span into the started threads. + * + * @param nThreads the number of threads in the pool + * @param spanManager the manager for span propagation. + * @return the newly created thread pool + * @see Executors#newFixedThreadPool(int) + */ + public static SpanPropagatingExecutorService newFixedThreadPool(int nThreads, SpanManager spanManager) { + return new SpanPropagatingExecutorService(Executors.newFixedThreadPool(nThreads), spanManager); + } + + /** + * This method returns a {@link Executors#newFixedThreadPool(int, ThreadFactory) fixed threadpool} that propagates + * the current span into the started threads. + * + * @param nThreads the number of threads in the pool + * @param threadFactory the factory to use when creating new threads + * @param spanManager the manager for span propagation. + * @return the newly created thread pool + * @see Executors#newFixedThreadPool(int, ThreadFactory) + */ + public static SpanPropagatingExecutorService newFixedThreadPool( + int nThreads, ThreadFactory threadFactory, SpanManager spanManager) { + return new SpanPropagatingExecutorService(Executors.newFixedThreadPool(nThreads, threadFactory), spanManager); + } + + /** + * This method returns a {@link Executors#newSingleThreadExecutor() single-threaded executor} that propagates + * the current span into the started thread. + * + * @param spanManager the manager for span propagation. + * @return the newly created single-theaded executor + * @see Executors#newSingleThreadExecutor() + */ + public static SpanPropagatingExecutorService newSingleThreadExecutor(SpanManager spanManager) { + return new SpanPropagatingExecutorService(Executors.newSingleThreadExecutor(), spanManager); + } + + /** + * This method returns a {@link Executors#newSingleThreadExecutor(ThreadFactory) single-threaded executor} + * that propagates the current span into the started thread. + * + * @param threadFactory the factory to use when creating new threads + * @param spanManager the manager for span propagation. + * @return the newly created single-theaded executor + * @see Executors#newSingleThreadExecutor(ThreadFactory) + */ + public static SpanPropagatingExecutorService newSingleThreadExecutor( + ThreadFactory threadFactory, SpanManager spanManager) { + return new SpanPropagatingExecutorService(Executors.newSingleThreadExecutor(threadFactory), spanManager); + } + + /** + * This method returns a {@link Executors#newCachedThreadPool() cached threadpool} that propagates + * the current span into the started threads. + * + * @param spanManager the manager for span propagation. + * @return the newly created thread pool + * @see Executors#newCachedThreadPool() + */ + public static SpanPropagatingExecutorService newCachedThreadPool(SpanManager spanManager) { + return new SpanPropagatingExecutorService(Executors.newCachedThreadPool(), spanManager); + } + + /** + * This method returns a {@link Executors#newCachedThreadPool(ThreadFactory) cached threadpool} that propagates + * the current span into the started threads. + * + * @param threadFactory the factory to use when creating new threads + * @param spanManager the manager for span propagation. + * @return the newly created thread pool + * @see Executors#newCachedThreadPool(ThreadFactory) + */ + public static SpanPropagatingExecutorService newCachedThreadPool(ThreadFactory threadFactory, SpanManager spanManager) { + return new SpanPropagatingExecutorService(Executors.newCachedThreadPool(threadFactory), spanManager); + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/tracer/AutoReleasingManagedSpan.java b/src/main/java/io/opentracing/contrib/spanmanager/tracer/AutoReleasingManagedSpan.java new file mode 100644 index 0000000..eb0e465 --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/tracer/AutoReleasingManagedSpan.java @@ -0,0 +1,171 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.tracer; + +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.contrib.spanmanager.SpanManager; + +import java.util.Map; + +/** + * A {@link Span} that automatically {@link #release() releases} + * when {@link #finish() finished} or {@link #close() closed}. + *

+ * All other methods are forwarded to the actual managed Span. + */ +final class AutoReleasingManagedSpan implements Span, SpanManager.ManagedSpan { + + private final SpanManager.ManagedSpan managedSpan; + + AutoReleasingManagedSpan(SpanManager.ManagedSpan managedSpan) { + if (managedSpan == null) throw new NullPointerException("Managed span was ."); + this.managedSpan = managedSpan; + } + + @Override + public Span getSpan() { + return managedSpan.getSpan(); + } + + /** + * Releases this current ManagedSpan. + */ + @Override + public void release() { + managedSpan.release(); + } + + /** + * {@link Span#finish() Finishes} the delegate and {@link SpanManager.ManagedSpan#release() releases} this ManagedSpan. + */ + @Override + public void finish() { + try { + getSpan().finish(); + } finally { + release(); + } + } + + /** + * {@link Span#finish(long) Finishes} the delegate and {@link SpanManager.ManagedSpan#release() releases} this ManagedSpan. + */ + @Override + public void finish(long finishMicros) { + try { + getSpan().finish(finishMicros); + } finally { + release(); + } + } + + /** + * {@link Span#close() Closes} the delegate and {@link SpanManager.ManagedSpan#release() releases} this ManagedSpan. + */ + @Override + public void close() { + try { + getSpan().close(); + } finally { + release(); + } + } + + // Default behaviour is forwarded to the actual managed Span: + + @Override + public SpanContext context() { + return getSpan().context(); + } + + @Override + public Span setTag(String key, String value) { + getSpan().setTag(key, value); + return this; + } + + @Override + public Span setTag(String key, boolean value) { + getSpan().setTag(key, value); + return this; + } + + @Override + public Span setTag(String key, Number value) { + getSpan().setTag(key, value); + return this; + } + + @Override + public Span log(Map fields) { + getSpan().log(fields); + return this; + } + + @Override + public Span log(long timestampMicroseconds, Map fields) { + getSpan().log(timestampMicroseconds, fields); + return this; + } + + @Override + public Span log(String event) { + getSpan().log(event); + return this; + } + + @Override + public Span log(long timestampMicroseconds, String event) { + getSpan().log(timestampMicroseconds, event); + return this; + } + + @Override + public Span setBaggageItem(String key, String value) { + getSpan().setBaggageItem(key, value); + return this; + } + + @Override + public String getBaggageItem(String key) { + return getSpan().getBaggageItem(key); + } + + @Override + public Span setOperationName(String operationName) { + getSpan().setOperationName(operationName); + return this; + } + + @SuppressWarnings("deprecation") // We simply delegate this method as we're told. + @Override + public Span log(String eventName, Object payload) { + getSpan().log(eventName, payload); + return this; + } + + @SuppressWarnings("deprecation") // We simply delegate this method as we're told. + @Override + public Span log(long timestampMicroseconds, String eventName, Object payload) { + getSpan().log(timestampMicroseconds, eventName, payload); + return this; + } + + @Override + public String toString() { + return getClass().getSimpleName() + '{' + managedSpan + '}'; + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/tracer/ManagedSpanBuilder.java b/src/main/java/io/opentracing/contrib/spanmanager/tracer/ManagedSpanBuilder.java new file mode 100644 index 0000000..b350c33 --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/tracer/ManagedSpanBuilder.java @@ -0,0 +1,108 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.tracer; + +import io.opentracing.References; +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.Tracer.SpanBuilder; +import io.opentracing.contrib.spanmanager.SpanManager; + +import java.util.Map; + +/** + * {@link SpanBuilder} that automatically {@link SpanManager#manage(Span) activates} newly started spans. + *

+ * The activated ManagedSpan is wrapped in an {@linkplain AutoReleasingManagedSpan} + * to automatically release when finished.
+ * All other methods are forwarded to the delegate span builder. + * + * @see SpanManager + * @see AutoReleasingManagedSpan#finish() + */ +final class ManagedSpanBuilder implements SpanBuilder { + + SpanBuilder delegate; + private final SpanManager spanManager; + + ManagedSpanBuilder(SpanBuilder delegate, SpanManager spanManager) { + if (delegate == null) throw new NullPointerException("Delegate SpanBuilder was ."); + if (spanManager == null) throw new NullPointerException("Span manager was ."); + this.delegate = delegate; + this.spanManager = spanManager; + } + + /** + * Replaces the {@link #delegate} SpanBuilder by a delegated-method result. + * + * @param spanBuilder The builder returned from the delegate (normally '== delegate'). + * @return This re-wrapped ManagedSpanBuilder. + */ + SpanBuilder rewrap(SpanBuilder spanBuilder) { + if (spanBuilder != null) { + this.delegate = spanBuilder; + } + return this; + } + + /** + * Starts the built Span and {@link SpanManager#manage(Span) activates} it. + * + * @return a new 'current' Span that releases itself upon finish or close calls. + * @see SpanManager#manage(Span) + * @see AutoReleasingManagedSpan#release() + */ + @Override + public Span start() { + return new AutoReleasingManagedSpan(spanManager.manage(delegate.start())); + } + + // All other methods are forwarded to the delegate SpanBuilder. + + public SpanBuilder asChildOf(SpanContext parent) { + return addReference(References.CHILD_OF, parent); + } + + public SpanBuilder asChildOf(Span parent) { + return addReference(References.CHILD_OF, parent.context()); + } + + public SpanBuilder addReference(String referenceType, SpanContext context) { + if (context instanceof ManagedSpanBuilder) { // Weird that SpanBuilder extends Context! + context = ((ManagedSpanBuilder) context).delegate; + } + return rewrap(delegate.addReference(referenceType, context)); + } + + public SpanBuilder withTag(String key, String value) { + return rewrap(delegate.withTag(key, value)); + } + + public SpanBuilder withTag(String key, boolean value) { + return rewrap(delegate.withTag(key, value)); + } + + public SpanBuilder withTag(String key, Number value) { + return rewrap(delegate.withTag(key, value)); + } + + public SpanBuilder withStartTimestamp(long microseconds) { + return rewrap(delegate.withStartTimestamp(microseconds)); + } + + public Iterable> baggageItems() { + return delegate.baggageItems(); + } + +} diff --git a/src/main/java/io/opentracing/contrib/spanmanager/tracer/ManagedSpanTracer.java b/src/main/java/io/opentracing/contrib/spanmanager/tracer/ManagedSpanTracer.java new file mode 100644 index 0000000..49f964a --- /dev/null +++ b/src/main/java/io/opentracing/contrib/spanmanager/tracer/ManagedSpanTracer.java @@ -0,0 +1,77 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.tracer; + +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.Tracer; +import io.opentracing.contrib.spanmanager.SpanManager; +import io.opentracing.propagation.Format; + +/** + * Convenience {@link Tracer} that automates managing the {@linkplain SpanManager#currentSpan() current span}: + *

    + *
  1. It is a wrapper that forwards all calls to another {@link Tracer} implementation.
  2. + *
  3. {@linkplain Span Spans} created with this tracer are + * automatically {@link SpanManager#manage(Span) managed} when started,
  4. + *
  5. and automatically {@link SpanManager.ManagedSpan#release() released} when they finish.
  6. + *
+ *

+ * Implementation note: This {@link Tracer} wraps the {@linkplain SpanBuilder} and {@linkplain Span} + * in {@linkplain ManagedSpanBuilder} and {@linkplain AutoReleasingManagedSpan} respectively + * because no {@link Span} lifecycle callbacks are available in the opentracing API.
+ * If there were, the {@linkplain ManagedSpanTracer} could be a lot simpler.
+ * However, lifecycle callbacks in the API form a considerable performance risk. + * + * @see SpanManager + */ +public final class ManagedSpanTracer implements Tracer { + + private final Tracer delegate; + private final SpanManager spanManager; + + /** + * Automatically manages created spans from delegate using the the specified {@link SpanManager}. + * + * @param delegate The tracer to be wrapped. + * @param spanManager The manager providing span propagation. + */ + public ManagedSpanTracer(Tracer delegate, SpanManager spanManager) { + if (delegate == null) throw new NullPointerException("Delegate Tracer is ."); + if (spanManager == null) throw new NullPointerException("Span manager is ."); + this.delegate = delegate; + this.spanManager = spanManager; + } + + public void inject(SpanContext spanContext, Format format, C carrier) { + if (spanContext instanceof ManagedSpanBuilder) { // Weird that SpanBuilder extends Context! + spanContext = ((ManagedSpanBuilder) spanContext).delegate; + } + delegate.inject(spanContext, format, carrier); + } + + public SpanContext extract(Format format, C carrier) { + return delegate.extract(format, carrier); + } + + public SpanBuilder buildSpan(String operationName) { + return new ManagedSpanBuilder(delegate.buildSpan(operationName), spanManager); + } + + @Override + public String toString() { + return "ManagedSpanTracer{" + delegate + '}'; + } + +} diff --git a/src/test/java/io/opentracing/contrib/spanmanager/DefaultSpanManagerTest.java b/src/test/java/io/opentracing/contrib/spanmanager/DefaultSpanManagerTest.java new file mode 100644 index 0000000..e6506a0 --- /dev/null +++ b/src/test/java/io/opentracing/contrib/spanmanager/DefaultSpanManagerTest.java @@ -0,0 +1,192 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager; + +import io.opentracing.NoopSpan; +import io.opentracing.Span; +import io.opentracing.contrib.spanmanager.SpanManager.ManagedSpan; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; + +public class DefaultSpanManagerTest { + + SpanManager manager; + + @Before + public void resetManager() { + manager = DefaultSpanManager.getInstance(); + manager.clear(); + } + + @Test + public void testBasicStackBehaviour() { + Span span1 = mock(Span.class); + Span span2 = mock(Span.class); + Span span3 = mock(Span.class); + + assertThat("empty stack", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + ManagedSpan managed1 = manager.manage(span1); + assertThat("pushed span1", manager.currentSpan(), is(sameInstance(span1))); + + ManagedSpan managed2 = manager.manage(span2); + assertThat("pushed span2", manager.currentSpan(), is(sameInstance(span2))); + + ManagedSpan managed3 = manager.manage(span3); + assertThat("pushed span3", manager.currentSpan(), is(sameInstance(span3))); + + managed3.release(); + assertThat("popped span3", manager.currentSpan(), is(sameInstance(span2))); + + managed2.release(); + assertThat("popped span2", manager.currentSpan(), is(sameInstance(span1))); + + managed1.release(); + assertThat("popped span1", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + } + + @Test + public void testMultipleReleases() { + Span span1 = mock(Span.class); + Span span2 = mock(Span.class); + + assertThat("empty stack", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + ManagedSpan managed1 = manager.manage(span1); + assertThat("pushed span1", manager.currentSpan(), is(sameInstance(span1))); + + ManagedSpan managed2 = manager.manage(span2); + assertThat("pushed span2", manager.currentSpan(), is(sameInstance(span2))); + + managed2.release(); + managed2.release(); + assertThat("popped span2", manager.currentSpan(), is(sameInstance(span1))); + + managed1.release(); + managed2.release(); + managed1.release(); + managed2.release(); + assertThat("popped span1", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + } + + + @Test + public void testTemporaryNoSpan() { + Span span1 = mock(Span.class); + Span span2 = null; + Span span3 = mock(Span.class); + + assertThat("empty stack", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + ManagedSpan managed1 = manager.manage(span1); + assertThat("pushed span1", manager.currentSpan(), is(sameInstance(span1))); + + ManagedSpan managed2 = manager.manage(span2); + assertThat("pushed span2", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + ManagedSpan managed3 = manager.manage(span3); + assertThat("pushed span3", manager.currentSpan(), is(sameInstance(span3))); + + managed3.release(); + assertThat("popped span3", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + managed2.release(); + assertThat("popped span2", manager.currentSpan(), is(sameInstance(span1))); + + managed1.release(); + assertThat("popped span1", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + } + + @Test + public void testOutOfOrderRelease() { + Span span1 = mock(Span.class); + Span span2 = mock(Span.class); + Span span3 = mock(Span.class); + + assertThat("empty stack", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + ManagedSpan managed1 = manager.manage(span1); + assertThat("pushed span1", manager.currentSpan(), is(sameInstance(span1))); + + ManagedSpan managed2 = manager.manage(span2); + assertThat("pushed span2", manager.currentSpan(), is(sameInstance(span2))); + + ManagedSpan managed3 = manager.manage(span3); + assertThat("pushed span3", manager.currentSpan(), is(sameInstance(span3))); + + // Pop2: Span1 -> Span2(X) -> Span3 : currentSpan stays Span3 + managed2.release(); + assertThat("released span2", manager.currentSpan(), is(sameInstance(span3))); + + managed3.release(); + assertThat("skipped span2 (already-released)", manager.currentSpan(), is(sameInstance(span1))); + + managed1.release(); + assertThat("popped span1", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + } + + /** + * Note: This is not a normal use-case!
+ * The {@link ManagedSpan} is intended to be created and used in the scope of a try-with-resources block + * (so within the scope of a single thread). + *

+ * This test is merely here to guarantee predictable behaviour when it happens. + */ + @Test + public void testReleaseFromOtherThreads() throws InterruptedException { + Span span1 = mock(Span.class); + Span span2 = mock(Span.class); + Span span3 = mock(Span.class); + + assertThat("empty stack", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + + ManagedSpan managed1 = manager.manage(span1); + assertThat("pushed span1", manager.currentSpan(), is(sameInstance(span1))); + + final ManagedSpan managed2 = manager.manage(span2); + assertThat("pushed span2", manager.currentSpan(), is(sameInstance(span2))); + + ManagedSpan managed3 = manager.manage(span3); + assertThat("pushed span3", manager.currentSpan(), is(sameInstance(span3))); + + // Schedule 10 threads to release managed2 + Thread[] releasers = new Thread[10]; + for (int i = 0; i < releasers.length; i++) { + releasers[i] = new Thread() { + @Override + public void run() { + managed2.release(); + } + }; + } + + // Schedule managed2.release() 10x + for (int i = 0; i < releasers.length; i++) releasers[i].start(); + + managed3.release(); + + // Wait for managed2.releases + for (int i = 0; i < releasers.length; i++) releasers[i].join(); + + assertThat("popped span2+3", manager.currentSpan(), is(sameInstance(span1))); + + managed1.release(); + assertThat("popped span1", manager.currentSpan(), is(instanceOf(NoopSpan.class))); + } + +} diff --git a/src/test/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorServiceMockTest.java b/src/test/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorServiceMockTest.java new file mode 100644 index 0000000..ec0cb6b --- /dev/null +++ b/src/test/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorServiceMockTest.java @@ -0,0 +1,125 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.concurrent; + +import io.opentracing.Span; +import io.opentracing.contrib.spanmanager.SpanManager; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +public class SpanPropagatingExecutorServiceMockTest { + + ExecutorService mockExecutorService; + SpanManager mockSpanManager; + Span mockSpan; + + SpanPropagatingExecutorService service; + + @Before + public void setUp() { + mockExecutorService = mock(ExecutorService.class); + mockSpanManager = mock(SpanManager.class); + mockSpan = mock(Span.class); + when(mockSpanManager.currentSpan()).thenReturn(mockSpan); + + service = new SpanPropagatingExecutorService(mockExecutorService, mockSpanManager); + } + + @After + public void verifyMocks() { + verifyNoMoreInteractions(mockExecutorService, mockSpanManager, mockSpan); + } + + @Test + public void testExecuteRunnable() { + Runnable runnable = mock(Runnable.class); + + service.execute(runnable); + + verify(mockSpanManager).currentSpan(); // current span must be propagated + verify(mockExecutorService).execute(any(RunnableWithManagedSpan.class)); // into RunnableWithManagedSpan + } + + @Test + public void testSubmitRunnable() { + Runnable runnable = mock(Runnable.class); + Future future = mock(Future.class); + when(mockExecutorService.submit(any(Runnable.class))).thenReturn(future); + + assertThat(service.submit(runnable), is(sameInstance(future))); + + verify(mockSpanManager).currentSpan(); // current span must be propagated + verify(mockExecutorService).submit(any(RunnableWithManagedSpan.class)); // into RunnableWithManagedSpan + } + + @Test + public void testSubmitCallable() { + Callable callable = mock(Callable.class); + Future future = mock(Future.class); + when(mockExecutorService.submit(any(Callable.class))).thenReturn(future); + + assertThat(service.submit(callable), is(sameInstance(future))); + + verify(mockSpanManager).currentSpan(); // current span must be propagated + verify(mockExecutorService).submit(any(CallableWithManagedSpan.class)); // into CallableWithManagedSpan + } + + @Test + public void testInvokeAll() throws InterruptedException { + Collection> callables = Arrays.>asList(mock(Callable.class), mock(Callable.class)); + List> futures = mock(List.class); + when(mockExecutorService.invokeAll((Collection>) anyCollection())).thenReturn(futures); + + assertThat(service.invokeAll(callables), is(sameInstance(futures))); + + verify(mockSpanManager, times(1)).currentSpan(); // current span must be obtained once + verify(mockExecutorService).invokeAll(anyCollection()); + } + + @Test + public void testConstructorWithoutDelegate() { + try { + new SpanPropagatingExecutorService(null, mock(SpanManager.class)); + fail("Exception with message expected."); + } catch (RuntimeException expected) { + assertThat(expected.getMessage(), is(Matchers.notNullValue())); + } + } + + @Test + public void testConstructorWithoutSpanManager() { + try { + new SpanPropagatingExecutorService(mock(ExecutorService.class), null); + fail("Exception with message expected."); + } catch (RuntimeException expected) { + assertThat(expected.getMessage(), is(Matchers.notNullValue())); + } + } + +} diff --git a/src/test/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorServiceTest.java b/src/test/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorServiceTest.java new file mode 100644 index 0000000..54d0141 --- /dev/null +++ b/src/test/java/io/opentracing/contrib/spanmanager/concurrent/SpanPropagatingExecutorServiceTest.java @@ -0,0 +1,164 @@ +/** + * Copyright 2017 The OpenTracing Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package io.opentracing.contrib.spanmanager.concurrent; + +import io.opentracing.NoopSpan; +import io.opentracing.Span; +import io.opentracing.contrib.spanmanager.DefaultSpanManager; +import io.opentracing.contrib.spanmanager.SpanManager; +import io.opentracing.contrib.spanmanager.SpanManager.ManagedSpan; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.*; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; + +public class SpanPropagatingExecutorServiceTest { + + static final ExecutorService threadpool = Executors.newCachedThreadPool(); + static final SpanManager spanManager = DefaultSpanManager.getInstance(); + + SpanPropagatingExecutorService subject; + + @Before + public void setUp() { + subject = new SpanPropagatingExecutorService(threadpool, spanManager); + spanManager.clear(); + } + + @After + public void tearDown() { + spanManager.clear(); + } + + @AfterClass + public static void shutdownThreadpool() { + assertThat(threadpool.shutdownNow(), equalTo(Collections.emptyList())); + } + + @Test + public void testExecuteRunnable() throws ExecutionException, InterruptedException { + CurrentSpanRunnable runnable = new CurrentSpanRunnable(); + ManagedSpan callerManagedSpan = spanManager.manage(mock(Span.class)); + try { + + // Execute runnable and wait for completion. + FutureTask future = new FutureTask(runnable, null); + subject.execute(future); + future.get(); + + assertThat("Current span in thread", runnable.span, is(sameInstance(callerManagedSpan.getSpan()))); + } finally { + callerManagedSpan.release(); + } + } + + @Test + public void testSubmitRunnable() throws ExecutionException, InterruptedException { + CurrentSpanRunnable runnable = new CurrentSpanRunnable(); + ManagedSpan callerManagedSpan = spanManager.manage(mock(Span.class)); + try { + + subject.submit(runnable).get(); // submit and block. + assertThat("Current span in thread", runnable.span, is(sameInstance(callerManagedSpan.getSpan()))); + + } finally { + callerManagedSpan.release(); + } + } + + @Test + public void testSubmitCallable() throws ExecutionException, InterruptedException { + Callable callable = new CurrentSpanCallable(); + ManagedSpan callerManagedSpan = spanManager.manage(mock(Span.class)); + try { + + Future threadSpan = subject.submit(callable); + assertThat("Current span in thread", threadSpan.get(), is(sameInstance(callerManagedSpan.getSpan()))); + + } finally { + callerManagedSpan.release(); + } + } + + @Test + public void testInvokeAll() throws ExecutionException, InterruptedException { + Collection> callables = Arrays.>asList( + new CurrentSpanCallable(), new CurrentSpanCallable(), new CurrentSpanCallable()); + ManagedSpan callerManagedSpan = spanManager.manage(mock(Span.class)); + try { + + List> futures = subject.invokeAll(callables); + assertThat("Futures", futures, hasSize(equalTo(callables.size()))); + for (Future threadSpan : futures) { + assertThat("Current span in thread", threadSpan.get(), is(sameInstance(callerManagedSpan.getSpan()))); + } + + } finally { + callerManagedSpan.release(); + } + } + + + @Test + public void testExecuteRunnableWithoutCurrentSpan() throws ExecutionException, InterruptedException { + CurrentSpanRunnable runnable = new CurrentSpanRunnable(); + + // Execute runnable and wait for completion. + FutureTask future = new FutureTask(runnable, null); + subject.execute(future); + future.get(); + + assertThat("Current span in thread", runnable.span, allOf(notNullValue(), instanceOf(NoopSpan.class))); + } + + @Test + public void testSubmitRunnableWithoutCurrentSpan() throws ExecutionException, InterruptedException { + CurrentSpanRunnable runnable = new CurrentSpanRunnable(); + subject.submit(runnable).get(); // submit and block. + assertThat("Current span in thread", runnable.span, allOf(notNullValue(), instanceOf(NoopSpan.class))); + } + + @Test + public void testSubmitCallableWithoutCurrentSpan() throws ExecutionException, InterruptedException { + Future threadSpan = subject.submit(new CurrentSpanCallable()); + assertThat("Current span in thread", threadSpan.get(), allOf(notNullValue(), instanceOf(NoopSpan.class))); + } + + static class CurrentSpanRunnable implements Runnable { + Span span = null; + + @Override + public void run() { + span = spanManager.currentSpan(); + } + } + + static class CurrentSpanCallable implements Callable { + @Override + public Span call() { + return spanManager.currentSpan(); + } + } + +} diff --git a/travis/publish.sh b/travis/publish.sh new file mode 100755 index 0000000..b6ef300 --- /dev/null +++ b/travis/publish.sh @@ -0,0 +1,131 @@ +# +# Copyright 2017 The OpenTracing Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# + +set -euo pipefail +set -x + +build_started_by_tag() { + if [ "${TRAVIS_TAG}" == "" ]; then + echo "[Publishing] This build was not started by a tag, publishing snapshot" + return 1 + else + echo "[Publishing] This build was started by the tag ${TRAVIS_TAG}, publishing release" + return 0 + fi +} + +is_pull_request() { + if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then + echo "[Not Publishing] This is a Pull Request" + return 0 + else + echo "[Publishing] This is not a Pull Request" + return 1 + fi +} + +is_travis_branch_master() { + if [ "${TRAVIS_BRANCH}" = master ]; then + echo "[Publishing] Travis branch is master" + return 0 + else + echo "[Not Publishing] Travis branch is not master" + return 1 + fi +} + +check_travis_branch_equals_travis_tag() { + #Weird comparison comparing branch to tag because when you 'git push --tags' + #the branch somehow becomes the tag value + #github issue: https://github.com/travis-ci/travis-ci/issues/1675 + if [ "${TRAVIS_BRANCH}" != "${TRAVIS_TAG}" ]; then + echo "Travis branch does not equal Travis tag, which it should, bailing out." + echo " github issue: https://github.com/travis-ci/travis-ci/issues/1675" + exit 1 + else + echo "[Publishing] Branch (${TRAVIS_BRANCH}) same as Tag (${TRAVIS_TAG})" + fi +} + +check_release_tag() { + tag="${TRAVIS_TAG}" + if [[ "$tag" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then + echo "Build started by version tag $tag. During the release process tags like this" + echo "are created by the 'release' Maven plugin. Nothing to do here." + exit 0 + elif [[ ! "$tag" =~ ^release-[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then + echo "You must specify a tag of the format 'release-0.0.0' to release this project." + echo "The provided tag ${tag} doesn't match that. Aborting." + exit 1 + fi +} + +is_release_commit() { + project_version=$(./mvnw help:evaluate -N -Dexpression=project.version|grep -v '\[') + if [[ "$project_version" =~ ^[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+$ ]]; then + echo "Build started by release commit $project_version. Will synchronize to maven central." + return 0 + else + return 1 + fi +} + +release_version() { + echo "${TRAVIS_TAG}" | sed 's/^release-//' +} + +safe_checkout_master() { + # We need to be on a branch for release:perform to be able to create commits, and we want that branch to be master. + # But we also want to make sure that we build and release exactly the tagged version, so we verify that the remote + # master is where our tag is. + git checkout -B master + git fetch origin master:origin/master + commit_local_master="$(git show --pretty='format:%H' master)" + commit_remote_master="$(git show --pretty='format:%H' origin/master)" + if [ "$commit_local_master" != "$commit_remote_master" ]; then + echo "Master on remote 'origin' has commits since the version under release, aborting" + exit 1 + fi +} + +#---------------------- +# MAIN +#---------------------- + +if ! is_pull_request && build_started_by_tag; then + check_travis_branch_equals_travis_tag + check_release_tag +fi + +./mvnw install -nsu + +# If we are on a pull request, our only job is to run tests, which happened above via ./mvnw install +if is_pull_request; then + true +# If we are on master, we will deploy the latest snapshot or release version +# - If a release commit fails to deploy for a transient reason, delete the broken version from bintray and click rebuild +elif is_travis_branch_master; then + ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DskipTests deploy + + # If the deployment succeeded, sync it to Maven Central. Note: this needs to be done once per project, not module, hence -N + if is_release_commit; then + ./mvnw --batch-mode -s ./.settings.xml -nsu -N io.zipkin.centralsync-maven-plugin:centralsync-maven-plugin:sync + fi + +# If we are on a release tag, the following will update any version references and push a version tag for deployment. +elif build_started_by_tag; then + safe_checkout_master + ./mvnw --batch-mode -s ./.settings.xml -Prelease -nsu -DreleaseVersion="$(release_version)" -Darguments="-DskipTests" release:prepare +fi +