Repository: alibaba/transmittable-thread-local Branch: master Commit: f90093277928 Files: 281 Total size: 1.1 MB Directory structure: gitextract_ll2vt13l/ ├── .editorconfig ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yaml │ └── strong_ci.yaml ├── .gitignore ├── .gitmodules ├── .mvn/ │ └── wrapper/ │ └── maven-wrapper.properties ├── CONTRIBUTING.md ├── LICENSE ├── README-EN.md ├── README.md ├── SECURITY.md ├── docs/ │ ├── TransmittableThreadLocal.asta │ ├── TransmittableThreadLocal.pptx │ ├── developer-guide-en.md │ ├── developer-guide.md │ ├── logo.md │ ├── performance-test.md │ ├── release-action-list.md │ └── requirement-scenario.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── scripts/ │ ├── bump-ttl-version.sh │ ├── check-japi-compliance.sh │ ├── codecov.sh │ ├── integration-test.sh │ ├── perf-test/ │ │ ├── memoryleak-ThreadLocal.sh │ │ ├── memoryleak-TransmittableThreadLocal.sh │ │ ├── tps-ThreadLocal.sh │ │ └── tps-TransmittableThreadLocal.sh │ ├── release.sh │ └── run-agent-demo.sh ├── src/ │ └── package-list/ │ ├── java/ │ │ └── package-list │ ├── jetbrains-annotations/ │ │ └── package-list │ ├── jsr305/ │ │ └── package-list │ └── spotbugs-annotations/ │ └── package-list ├── ttl-agent/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── ttl3/ │ └── agent/ │ ├── TtlAgent.java │ ├── TtlAgentHelper.java │ ├── TtlExtensionTransformletManager.java │ ├── TtlTransformer.java │ ├── logging/ │ │ ├── Logger.java │ │ └── package-info.java │ ├── package-info.java │ └── transformlet/ │ ├── ClassInfo.java │ ├── TtlTransformlet.java │ ├── helper/ │ │ ├── AbstractExecutorTtlTransformlet.java │ │ ├── TtlTransformletHelper.java │ │ └── package-info.java │ ├── internal/ │ │ ├── ForkJoinTtlTransformlet.java │ │ ├── JdkExecutorTtlTransformlet.java │ │ ├── PriorityBlockingQueueTtlTransformlet.java │ │ ├── TimerTaskTtlTransformlet.java │ │ └── package-info.java │ └── package-info.java ├── ttl-bom/ │ └── pom.xml ├── ttl-core/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ ├── crr/ │ │ │ │ ├── TransmitCallback.java │ │ │ │ ├── Transmittable.java │ │ │ │ ├── composite/ │ │ │ │ │ ├── Backup.java │ │ │ │ │ ├── Capture.java │ │ │ │ │ ├── CompositeTransmitCallback.java │ │ │ │ │ ├── CompositeTransmittable.java │ │ │ │ │ └── package-info.java │ │ │ │ └── package-info.java │ │ │ └── ttl3/ │ │ │ ├── TransmittableThreadLocal.java │ │ │ ├── TtlCallable.java │ │ │ ├── TtlRecursiveAction.java │ │ │ ├── TtlRecursiveTask.java │ │ │ ├── TtlRunnable.java │ │ │ ├── TtlTimerTask.java │ │ │ ├── TtlWrappers.java │ │ │ ├── agent/ │ │ │ │ ├── EmptyTtlAgentStatus.java │ │ │ │ ├── TtlAgentStatus.java │ │ │ │ └── package-info.java │ │ │ ├── executor/ │ │ │ │ ├── ComparableComparator.java │ │ │ │ ├── DisableInheritableForkJoinWorkerThreadFactoryWrapper.java │ │ │ │ ├── DisableInheritableThreadFactoryWrapper.java │ │ │ │ ├── ExecutorServiceTtlWrapper.java │ │ │ │ ├── ExecutorTtlWrapper.java │ │ │ │ ├── ScheduledExecutorServiceTtlWrapper.java │ │ │ │ ├── TtlExecutors.java │ │ │ │ ├── TtlUnwrapComparator.java │ │ │ │ └── package-info.java │ │ │ ├── internal/ │ │ │ │ └── util/ │ │ │ │ ├── Assert.java │ │ │ │ ├── ConcurrentReferenceHashMap.java │ │ │ │ ├── ObjectUtils.java │ │ │ │ └── Utils.java │ │ │ ├── package-info.java │ │ │ ├── spi/ │ │ │ │ ├── TtlAttachments.java │ │ │ │ ├── TtlAttachmentsDelegate.java │ │ │ │ ├── TtlEnhanced.java │ │ │ │ ├── TtlWrapper.java │ │ │ │ └── package-info.java │ │ │ └── transmitter/ │ │ │ ├── ThreadLocalTransmitRegistry.java │ │ │ ├── Transmittee.java │ │ │ ├── TransmitteeRegistry.java │ │ │ ├── Transmitter.java │ │ │ └── package-info.java │ │ └── javadoc/ │ │ └── overview.html │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ ├── Utils.kt │ ├── demo/ │ │ ├── cow/ │ │ │ └── CowDemo.kt │ │ ├── forkjoinpool/ │ │ │ ├── ForkJoinPoolDemo.kt │ │ │ └── ParallelStreamDemo.kt │ │ ├── scheduled_thread_pool_executor/ │ │ │ └── ScheduledFutureTaskDemo.kt │ │ ├── session_cache/ │ │ │ └── SessionCacheDemo.kt │ │ ├── timer/ │ │ │ └── TimerTaskDemo.kt │ │ └── ttl3/ │ │ ├── CustomizedBlockingQueueWithTtlDemo.java │ │ ├── SimpleDemo.kt │ │ ├── TtlExecutorServiceWithPriorityBlockingQueueDemo.kt │ │ ├── TtlExecutorWrapperDemo.kt │ │ ├── TtlForkJoinTaskDemo.kt │ │ └── TtlWrapperDemo.kt │ ├── perf/ │ │ ├── Utils.kt │ │ ├── memoryleak/ │ │ │ ├── NoMemoryLeak_ThreadLocal_NoRemove.kt │ │ │ └── NoMemoryLeak_TransmittableThreadLocal_NoRemove.kt │ │ ├── package-info.java │ │ └── tps/ │ │ ├── CreateThreadLocalInstanceTps.kt │ │ ├── CreateTransmittableThreadLocalInstanceTps.kt │ │ └── TpsCounter.kt │ ├── third_part_lib_test/ │ │ ├── ExecutorsTest.kt │ │ └── ForkJoinPoolTest.kt │ ├── ttl3/ │ │ ├── TtlCallableTest.kt │ │ ├── TtlFlowTester.kt │ │ ├── TtlRunnableTest.kt │ │ └── TtlTimerTaskTest.kt │ └── user_api_test/ │ └── ttl3/ │ ├── DisableIgnoreNullValueSemanticsTest.kt │ ├── TransmittableThreadLocal_Transmitter_UserTest.kt │ ├── TransmittableThreadLocal_Transmitter_registerTransmittee_UserTest.kt │ ├── TransmittableThreadLocal_withInit_Null_Test.java │ └── TransmittableThreadLocal_withInit_Test.kt ├── ttl-integrations/ │ ├── sample-ttl-agent-extension-transformlet/ │ │ ├── README.md │ │ ├── pom.xml │ │ ├── scripts/ │ │ │ ├── integration-test.sh │ │ │ └── run.sh │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── ttl/ │ │ │ │ └── agent/ │ │ │ │ └── extension_transformlet/ │ │ │ │ └── sample/ │ │ │ │ ├── biz/ │ │ │ │ │ ├── SampleMain.java │ │ │ │ │ └── ToBeTransformedClass.java │ │ │ │ └── transformlet/ │ │ │ │ └── SampleExtensionTransformlet.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── ttl.agent.transformlets │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── ttl/ │ │ └── agent/ │ │ └── extension_transformlet/ │ │ └── sample/ │ │ └── biz/ │ │ └── ToBeTransformedClassTest.java │ ├── vertx3-ttl-integration/ │ │ ├── README-EN.md │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── com/ │ │ │ │ └── alibaba/ │ │ │ │ └── ttl/ │ │ │ │ └── integration/ │ │ │ │ └── vertx3/ │ │ │ │ ├── TtlVertxHandler.java │ │ │ │ └── agent/ │ │ │ │ └── transformlet/ │ │ │ │ ├── NettySingleThreadEventExecutorTtlTransformlet.java │ │ │ │ └── VertxFutureTtlTransformlet.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── ttl.agent.transformlets │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── ttl/ │ │ └── integration/ │ │ └── vertx3/ │ │ └── VertxTransformletTest.java │ └── vertx4-ttl-integration/ │ ├── README-EN.md │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── alibaba/ │ │ │ └── ttl/ │ │ │ └── integration/ │ │ │ └── vertx4/ │ │ │ ├── TtlVertxHandler.java │ │ │ └── agent/ │ │ │ └── transformlet/ │ │ │ ├── NettySingleThreadEventExecutorTtlTransformlet.java │ │ │ └── VertxFutureTtlTransformlet.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── ttl.agent.transformlets │ └── test/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── ttl/ │ └── integration/ │ └── vertx4/ │ └── VertxTransformletTest.java ├── ttl-kotlin/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── com/ │ │ └── alibaba/ │ │ └── ttl3/ │ │ └── kotlin/ │ │ ├── Transmitter.kt │ │ └── TtlExtensions.kt │ └── test/ │ └── kotlin/ │ └── com/ │ └── alibaba/ │ ├── Utils.kt │ └── ttl3/ │ └── kotlin/ │ └── TtlExtensionsTests.kt └── ttl2-compatible/ ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── ttl/ │ │ ├── TransmittableThreadLocal.java │ │ ├── TtlCallable.java │ │ ├── TtlCopier.java │ │ ├── TtlEnhanced.java │ │ ├── TtlRecursiveAction.java │ │ ├── TtlRecursiveTask.java │ │ ├── TtlRunnable.java │ │ ├── TtlTimerTask.java │ │ ├── TtlUnwrap.java │ │ ├── TtlWrappers.java │ │ ├── package-info.java │ │ ├── spi/ │ │ │ ├── TtlAttachments.java │ │ │ ├── TtlAttachmentsDelegate.java │ │ │ ├── TtlEnhanced.java │ │ │ ├── TtlWrapper.java │ │ │ └── package-info.java │ │ └── threadpool/ │ │ ├── ComparableComparator.java │ │ ├── DisableInheritableForkJoinWorkerThreadFactory.java │ │ ├── DisableInheritableForkJoinWorkerThreadFactoryWrapper.java │ │ ├── DisableInheritableThreadFactory.java │ │ ├── DisableInheritableThreadFactoryWrapper.java │ │ ├── ExecutorServiceTtlWrapper.java │ │ ├── ExecutorTtlWrapper.java │ │ ├── ScheduledExecutorServiceTtlWrapper.java │ │ ├── TtlExecutors.java │ │ ├── TtlForkJoinPoolHelper.java │ │ ├── TtlUnwrapComparator.java │ │ ├── agent/ │ │ │ ├── TtlAgent.java │ │ │ ├── TtlAgentHelper.java │ │ │ ├── TtlExtensionTransformletManager.java │ │ │ ├── TtlTransformer.java │ │ │ ├── logging/ │ │ │ │ ├── Logger.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── transformlet/ │ │ │ ├── ClassInfo.java │ │ │ ├── TtlTransformlet.java │ │ │ ├── helper/ │ │ │ │ ├── AbstractExecutorTtlTransformlet.java │ │ │ │ ├── TtlTransformletHelper.java │ │ │ │ └── package-info.java │ │ │ ├── internal/ │ │ │ │ ├── ForkJoinTtlTransformlet.java │ │ │ │ ├── JdkExecutorTtlTransformlet.java │ │ │ │ ├── PriorityBlockingQueueTtlTransformlet.java │ │ │ │ ├── TimerTaskTtlTransformlet.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ └── package-info.java │ └── javadoc/ │ └── overview.html └── test/ ├── java/ │ └── com/ │ └── alibaba/ │ ├── Utils.kt │ ├── demo/ │ │ ├── coroutine/ │ │ │ ├── CoroutineDemo.kt │ │ │ ├── CoroutineThreadContextElementDemo.kt │ │ │ ├── CoroutineThreadContextElementTest.kt │ │ │ ├── CoroutineThreadLocalContextContinuationInterceptorDemo.kt │ │ │ └── ttl_intergration/ │ │ │ ├── TtlCoroutineContext.kt │ │ │ └── usage/ │ │ │ ├── TtlCoroutineContextDemo.kt │ │ │ └── TtlCoroutineContextTest.kt │ │ ├── cow/ │ │ │ └── CowDemo.kt │ │ ├── distributed_tracer/ │ │ │ ├── refcount/ │ │ │ │ └── DistributedTracerUseDemo.kt │ │ │ └── weakref/ │ │ │ └── DistributedTracerUseDemo_WeakReferenceInsteadOfRefCounter.kt │ │ ├── forkjoinpool/ │ │ │ ├── ForkJoinPoolDemo.kt │ │ │ └── ParallelStreamDemo.kt │ │ ├── scheduled_thread_pool_executor/ │ │ │ └── ScheduledFutureTaskDemo.kt │ │ ├── session_cache/ │ │ │ └── SessionCacheDemo.kt │ │ ├── timer/ │ │ │ └── TimerTaskDemo.kt │ │ └── ttl/ │ │ ├── CustomizedBlockingQueueWithTtlDemo.java │ │ ├── SimpleDemo.kt │ │ ├── TtlExecutorServiceWithPriorityBlockingQueueDemo.kt │ │ ├── TtlExecutorWrapperDemo.kt │ │ ├── TtlForkJoinTaskDemo.kt │ │ ├── TtlWrapperDemo.kt │ │ ├── TtlWrapperTypeInferenceProblemShowcase.java │ │ └── agent/ │ │ ├── AgentDemo.kt │ │ └── YourXxxAgent.java │ ├── it/ │ │ ├── README.md │ │ └── TimerAgentCheck.kt │ ├── perf/ │ │ ├── Utils.kt │ │ ├── memoryleak/ │ │ │ ├── NoMemoryLeak_ThreadLocal_NoRemove.kt │ │ │ └── NoMemoryLeak_TransmittableThreadLocal_NoRemove.kt │ │ └── tps/ │ │ ├── CreateThreadLocalInstanceTps.kt │ │ ├── CreateTransmittableThreadLocalInstanceTps.kt │ │ └── TpsCounter.kt │ ├── third_part_lib_test/ │ │ ├── ExecutorsTest.kt │ │ ├── ForkJoinPoolTest.kt │ │ └── JavassistTest.kt │ ├── ttl/ │ │ ├── InheritableTest.kt │ │ ├── README.md │ │ ├── TtlCallableTest.kt │ │ ├── TtlRunnableTest.kt │ │ ├── TtlTimerTaskTest.kt │ │ ├── TtlWrappersTest.kt │ │ ├── forkjoin/ │ │ │ ├── ForkJoinPool4RunnableCallableTest.kt │ │ │ ├── ForkJoinPool4StreamTest.kt │ │ │ ├── recursive_action/ │ │ │ │ └── TtlRecursiveActionTest.kt │ │ │ └── recursive_task/ │ │ │ └── TtlRecursiveTaskTest.kt │ │ ├── reported_bugs/ │ │ │ └── Bug70_Test.kt │ │ ├── testmodel/ │ │ │ ├── Call.kt │ │ │ ├── DeepCopyFooTransmittableThreadLocal.kt │ │ │ ├── FooPojo.kt │ │ │ ├── FooTask.kt │ │ │ └── Task.kt │ │ ├── threadlocal_integration/ │ │ │ └── ThreadLocalIntegrationTest.kt │ │ └── threadpool/ │ │ ├── BeforeAndAfterExecuteMethodOfExecutorSubclassTest.kt │ │ ├── ExecutorClassesTest.kt │ │ ├── ScheduledExecutorServiceTtlWrapperTest.kt │ │ ├── TtlExecutorsTest.kt │ │ ├── TtlForkJoinPoolHelperTest.kt │ │ └── agent/ │ │ ├── TtlAgentHelperTest.kt │ │ ├── TtlExtensionTransformletManagerTest.kt │ │ └── transformlet/ │ │ ├── helper/ │ │ │ └── TtlTransformletHelperTest.kt │ │ └── internal/ │ │ └── UtilsTest.java │ └── user_api_test/ │ ├── README.md │ └── ttl/ │ ├── DisableIgnoreNullValueSemanticsTest.kt │ ├── TransmittableThreadLocal_Transmitter_UserTest.kt │ ├── TransmittableThreadLocal_Transmitter_registerTransmittee_UserTest.kt │ ├── TransmittableThreadLocal_withInit_Null_Test.java │ └── TransmittableThreadLocal_withInit_Test.kt └── resources/ ├── io/ │ └── mockk/ │ └── settings.properties ├── log4j.xml └── test_extension/ └── foo.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true tab_width = 2 indent_size = 2 ij_continuation_indent_size = 4 indent_style = space trim_trailing_whitespace = true ij_any_blank_lines_after_imports = 2 ij_markdown_wrap_text_if_long = false [*.{java,kt}] indent_size = 4 ij_continuation_indent_size = 8 [*.{md,mkd,markdown}] indent_size = 4 ij_continuation_indent_size = 8 trim_trailing_whitespace = false [*.xml] indent_style = tab ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: maven directory: "/" schedule: interval: daily open-pull-requests-limit: 20 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/ci.yaml ================================================ # Quickstart for GitHub Actions # https://docs.github.com/en/actions/quickstart name: fast CI on: [ push, pull_request, workflow_dispatch ] jobs: test: runs-on: ${{ matrix.os }} timeout-minutes: 10 strategy: matrix: os: [ ubuntu-latest, windows-latest ] java: [ 8, 11, 17, 21, 22 ] fail-fast: false max-parallel: 64 name: Fast CI on Java ${{ matrix.java }} OS ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: zulu cache: maven - name: Build with Maven run: ./mvnw -V --no-transfer-progress clean package - name: Run unit test under ttl agent, include check for ExecutorService, ForkJoinPool, Timer/TimerTask working-directory: ttl2-compatible run: > ../mvnw -V --no-transfer-progress -Penable-ttl-agent-for-test surefire:test '-Dttl.agent.extra.d.options=-Drun-ttl-test-under-agent-with-enable-timer-task=true' - name: Run unit test under ttl agent, and turn on the disable inheritable for thread pool enhancement working-directory: ttl2-compatible run: > ../mvnw -V --no-transfer-progress -Penable-ttl-agent-for-test surefire:test '-Dttl.agent.extra.args=ttl.agent.disable.inheritable.for.thread.pool:true' '-Dttl.agent.extra.d.options=-Drun-ttl-test-under-agent-with-disable-inheritable=true' - name: Run agent check for Timer/TimerTask, explicit "ttl.agent.enable.timer.task" working-directory: ttl2-compatible run: > ../mvnw -V --no-transfer-progress -Penable-ttl-agent-for-test surefire:test '-Dttl.agent.extra.args=ttl.agent.enable.timer.task:true' '-Dttl.agent.extra.d.options=-Drun-ttl-test-under-agent-with-enable-timer-task=true' ================================================ FILE: .github/workflows/strong_ci.yaml ================================================ # Quickstart for GitHub Actions # https://docs.github.com/en/actions/quickstart name: Strong CI on: [ push, pull_request, workflow_dispatch ] jobs: test: # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners runs-on: ubuntu-latest timeout-minutes: 30 name: Strong CI by multiply java versions steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Setup Java uses: actions/setup-java@v4 with: # https://github.com/actions/setup-java?tab=readme-ov-file#install-multiple-jdks java-version: | 8 11 17 21 22 distribution: zulu cache: maven - name: Run integration test run: scripts/integration-test.sh - name: Remove self maven install files run: rm -rf $HOME/.m2/repository/com/alibaba/{transmittable-thread-local,ttl}* - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # https://remarkablemark.org/blog/2017/10/12/check-git-dirty/ - name: Check git dirty run: | git status --short [ -z "$(git status --short)" ] ================================================ FILE: .gitmodules ================================================ [submodule "scripts/bash-buddy"] path = scripts/bash-buddy url = https://github.com/foldright/bash-buddy.git ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # 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. wrapperVersion=3.3.2 distributionType=only-script distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip ================================================ FILE: CONTRIBUTING.md ================================================ # Welcome! Thank you for contributing to TransmittableThreadLocal(TTL)! > ⚠️ This contribution guide is still in progress. We follow the standard GitHub [fork & pull](https://help.github.com/articles/using-pull-requests/#fork--pull) approach to pull requests. Just fork the official repo, develop in a branch, and submit a PR! You're always welcome to submit your PR straight away and start the discussion (without reading the rest of this wonderful doc, or the README.md). The goal of these notes is to make your experience contributing to TransmittableThreadLocal(TTL) as smooth and pleasant as possible. We're happy to guide you through the process once you've submitted your PR. # The TransmittableThreadLocal(TTL) Community Mainly use the github issue: https://github.com/alibaba/transmittable-thread-local/issues In case of questions about the contribution process or for discussion of specific issues please visit the [alibaba/transmittable-thread-local gitter chat](https://gitter.im/alibaba/transmittable-thread-local?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge). # Navigating around the project & codebase ## Branches summary Depending on what you want to work on, you should target a specific branch as explained below: * `master` – active development branch * `incubation/xxx` branches contain big feature. * `dev/xxx` branches contain small feature or bug fix. ## Tags TTL uses tags to categorise issues into groups. Most notably many tags start with a `t:` prefix (as in `topic:`), which categorises issues in terms of which extension module they relate to. Examples are: - [t:vertx](https://github.com/alibaba/transmittable-thread-local/labels/t%3Avertx) - [t:netty](https://github.com/alibaba/transmittable-thread-local/labels/t%3Anetty) Without `t:xxx` tags is related to `TTL` lib. see [all tags here](https://github.com/alibaba/transmittable-thread-local/labels) In general *all issues are open for anyone working on them*, however if you're new to the project and looking for an issue that will be accepted and likely is a nice one to get started you should check out the following tags: - [good first issue](https://github.com/alibaba/transmittable-thread-local/labels/%F0%9F%94%B0%20good%20first%20issue) - which identifies simple entry level tickets, such as improvements of documentation or tests. If you're not sure how to solve a ticket but would like to work on it feel free to ask in the issue about clarification or tips. - [help wanted](https://github.com/alibaba/transmittable-thread-local/labels/help%20wanted) - which identifies issues that the core team will likely not have time to work on, or the issue is a nice entry level ticket. If you're not sure how to solve a ticket but would like to work on it feel free to ask in the issue about clarification or tips. - [nice-to-have (low-priority)](https://github.com/alibaba/transmittable-thread-local/labels/nice-to-have%20%28low-prio%29) - are tasks which make sense, however are not very high priority (in face of other very high priority issues). If you see something interesting in this list, a contribution would be really wonderful! Another group of tickets are those which start from a number. They're used to signal in what phase of development an issue is: - [0 - new](https://github.com/alibaba/transmittable-thread-local/labels/0%20-%20new) - is assigned when a ticket is unclear on its purpose or if it is valid or not. Sometimes the additional tag `discuss` is used to mark such tickets, if they propose large scale changes and need more discussion before moving into triaged (or being closed as invalid). - [1 - triaged](https://github.com/alibaba/transmittable-thread-local/labels/1%20-%20triaged) - roughly speaking means "this ticket makes sense". Triaged tickets are safe to pick up for contributing in terms of likeliness of a patch for it being accepted. It is not recommended to start working on a ticket that is not triaged. - [2 - pick next](https://github.com/alibaba/transmittable-thread-local/labels/2%20-%20pick%20next) - used to mark issues which are next up in the queue to be worked on. Sometimes it's also used to mark which PRs are expected to be reviewed/merged for the next release. The tag is non-binding, and mostly used as an organisational helper. - [3 - in progress](https://github.com/alibaba/transmittable-thread-local/labels/3%20-%20in%20progress) - means someone is working on this ticket. If you see a ticket that has the tag, however seems inactive, it could have been an omission with removing the tag, feel free to ping the ticket then if it's still being worked on. Another group of tags indicate type of a ticket is: - [bug](https://github.com/alibaba/transmittable-thread-local/labels/%F0%9F%90%9E%20bug) tickets indicate potential production issues. Bugs take priority in being fixed above features. The core team dedicates a number of days to working on bugs each sprint. Bugs which have **reproducers** are also great for community contributions as they're well-isolated. Sometimes we're not as lucky to have reproducers though, then a bugfix should also include a test reproducing the original error along with the fix. - [feature](https://github.com/alibaba/transmittable-thread-local/labels/%E2%9C%A8%20feature). - [enhancement](https://github.com/alibaba/transmittable-thread-local/labels/%F0%9F%92%AA%20enhancement). # TransmittableThreadLocal(TTL) contributing guidelines These guidelines are meant to be a living document that should be changed and adapted as needed. We encourage changes that make it easier to achieve our goals in an efficient way. ## General workflow The steps below describe how to get a patch into a main development branch (e.g. `master`). The steps are exactly the same for everyone involved in the project (be it core team, or first time contributor). 1. To avoid duplicated effort, it might be good to check the [issue tracker](https://github.com/alibaba/transmittable-thread-local/issues) and [existing pull requests](https://github.com/alibaba/transmittable-thread-local/pulls) for existing work. - If there is no ticket yet, feel free to [create one](https://github.com/alibaba/transmittable-thread-local/issues/new) to discuss the problem and the approach you want to take to solve it. 1. [Fork the project](https://github.com/alibaba/transmittable-thread-local/fork) on GitHub. You'll need to create a feature-branch for your work on your fork, as this way you'll be able to submit a pull request against the mainline. 1. Create a branch on your fork and work on the feature. For example: `git checkout -b support-fast-thread-local` - Please make sure to follow the general quality guidelines (specified below) when developing your patch. - Please write additional tests covering your feature and adjust existing ones if needed before submitting your pull request. The `validatePullRequest` task ([explained below](#the-validatepullrequest-task)) may come in handy to verify your changes are correct. - Use the `verifyCodeStyle` maven task to make sure your code is properly formatted and includes the proper copyright headers. 1. Once your feature is complete, prepare the commit following our [Creating Commits And Writing Commit Messages](#creating-commits-and-writing-commit-messages). For example, a good commit message would be: `Adding compression support for Manifests #42` (note the reference to the ticket it aimed to resolve). 1. If it's a new feature, or a change of behavior, document it on the [User Guide](https://github.com/alibaba/transmittable-thread-local/blob/master/README.md). If the feature was touching Scala or Java DSL, make sure to document both the Scala and Java APIs. 1. Now it's finally time to [submit the pull request](https://help.github.com/articles/using-pull-requests)! - Please make sure to include a reference to the issue you're solving *in the comment* for the Pull Request, as this will cause the PR to be linked properly with the Issue. Examples of good phrases for this are: "Resolves #1234" or "Refs #1234". 1. If you have not already done so, you will be asked by our CLA bot to [sign the Alibaba CLA](https://cla-assistant.io/alibaba/transmittable-thread-local) online. CLA stands for Contributor License Agreement and is a way of protecting intellectual property disputes from harming the project. 1. Now both committers and interested people will review your code. This process is to ensure the code we merge is of the best possible quality, and that no silly mistakes slip through. You're expected to follow-up these comments by adding new commits to the same branch. The commit messages of those commits can be more loose, for example: `Removed debugging using printline`, as they all will be squashed into one commit before merging into the main branch. - The community and team are really nice people, so don't be afraid to ask follow up questions if you didn't understand some comment, or would like clarification on how to continue with a given feature. We're here to help, so feel free to ask and discuss any kind of questions you might have during review! 1. After the review you should fix the issues as needed (pushing a new commit for new review etc.), iterating until the reviewers give their thumbs up–which is signalled usually by a comment saying `LGTM`, which means "Looks Good To Me". 1. Once everything is said and done, your pull request gets merged :tada: Your feature will be available with the next “earliest” release milestone. And of course you will be given credit for the fix in the release stats during the release's announcement. You've made it! The TL;DR; of the above very precise workflow version is: 1. Fork TTL 2. Hack and test on your feature (on a branch) 3. Document it 4. Submit a PR 5. Sign the CLA if necessary 6. Keep polishing it until received enough LGTM 7. Profit! ## Getting started with maven TTL is using the [maven](https://maven.apache.org/) build system. To compile all the core modules use the `compile` command: ```bash ./mvnw compile ``` You can run tests with the `test` command: ```bash ./mvnw test ``` If you want to deploy the artifacts to your local maven repository (for example, to use from an maven project) use the `install` command: ```bash ./mvnw install ``` ## The Pull Request validation task **TODO** ## Binary compatibility **TODO** ## Pull request requirements For a pull request to be considered at all it has to meet these requirements: 1. Regardless if the code introduces new features or fixes bugs or regressions, it must have comprehensive tests. 1. The code must be well documented in the TTL project's standard documentation format (see the ‘Documentation’ section below). 1. The commit messages must properly describe the changes, see further below. 1. A pull request must indicate (link to) the issue it is aimed to resolve in the description (or comments) of the PR, in order to establish a link between PR and Issue. This can be achieved by writing "Fixes #1234" or similar in PR description. 1. All projects must include TTL copyright notices. Each project can choose between one of two approaches: 1. All source files in the project must have a TTL copyright notice in the file header. 1. The Notices file for the project includes the TTL copyright notice and no other files contain copyright notices. See http://www.apache.org/legal/src-headers.html for instructions for managing this approach for copyrights. TTL projects uses the first choice, having copyright notices in every file header. When absent, these are added automatically during `mvn compile`. ### Additional guidelines Some additional guidelines regarding source code are: - Keep the code [DRY](http://programmer.97things.oreilly.com/wiki/index.php/Don%27t_Repeat_Yourself). - Apply the [Boy Scout Rule](http://programmer.97things.oreilly.com/wiki/index.php/The_Boy_Scout_Rule) whenever you have the chance to. - Never delete or change existing copyright notices, just add additional info. - Do not use ``@author`` tags since it does not encourage [Collective Code Ownership](http://www.extremeprogramming.org/rules/collective.html). TODO - Contributors , each project should make sure that the contributors gets the credit they deserve—in a text file or page on the project website and in the release notes etc. ## Documentation All documentation is preferred to be in Java API doc standard documentation format, which among other things allows all code in the documentation to be externalized into compiled files and imported into the documentation. To build the documentation locally: ``` mvn -Pgen-api-doc javadoc:javadoc ``` ## Creating commits and writing commit messages Follow these guidelines when creating public commits and writing commit messages. 1. If your work spans multiple local commits (for example; if you do safe point commits while working in a feature branch or work in a branch for a long time doing merges/rebases etc.) then please do not commit it all but rewrite the history by squashing the commits into a single big commit which you write a good commit message for (like discussed in the following sections). For more info read this article: [Git Workflow](http://sandofsky.com/blog/git-workflow.html). Every commit should be able to be used in isolation, cherry picked etc. 2. The first line should be a descriptive sentence what the commit is doing, including the ticket number. It should be possible to fully understand what the commit does—but not necessarily how it does it—by just reading this single line. We follow the “imperative present tense” style for commit messages ([more info here](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)). It is **not ok** to only list the ticket number, type "minor fix" or similar. If the commit is a small fix, then you are done. If not, go to 3. 3. Following the single line description should be a blank line followed by an enumerated list with the details of the commit. 4. You can request review by a specific team member for your commit (depending on the degree of automation we reach, the list may change over time): * ``Review by @gituser`` - if you want to notify someone on the team. The others can, and are encouraged to participate. Example: enable Travis CI #1 * Details 1 * Details 2 * Details 3 ## Pull request validation workflow details **TODO** ## Source style Sometimes it is convenient to place 'internal' classes in their own package. In such situations we prefer 'internal' over 'impl' as a package name. ### Java style TTL projects uses xxx maven plugin to format Java sources. **TODO** PR validation includes checking that the Java sources are formatted and will fail if they are not. # Supporting infrastructure ## Reporting security issues If you have found an issue in an TransmittableThreadLocal(TTL) project that might have security implications, you can report it to . We will make sure those will get handled with priority. Thank you for your responsible disclosure! ## Continuous integration TransmittableThreadLocal(TTL) uses GitHub actions for Continuous Integration: https://github.com/alibaba/transmittable-thread-local/actions ## Related links - [alibaba/transmittable-thread-local Contributor License Agreement](https://cla-assistant.io/alibaba/transmittable-thread-local) - [the akka contribution guide](https://github.com/akka/akka/blob/master/CONTRIBUTING.md) (this contribution guide is adapted from it) ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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 https://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. ================================================ FILE: README-EN.md ================================================ #
📌 TransmittableThreadLocal(TTL)
> [!IMPORTANT] > 🚧 This branch is `TransmittableThreadLocal(TTL) v3`, which is in development and has not been released yet. > See [issue 432](https://github.com/alibaba/transmittable-thread-local/issues/432) for the `v3` notes, work item list and its progress. > > 👉 The stable version `v2.x` currently in use is on [**branch `2.x`**](https://github.com/alibaba/transmittable-thread-local/tree/2.x).

Fast CI Strong CI Coverage Status JDK support License Javadocs Maven Central GitHub release GitHub Stars GitHub Forks user repos GitHub issues GitHub Contributors GitHub repo size gitpod: Ready to Code

📖 English Documentation | [📖 中文文档](README.md) ---------------------------------------- - [🔧 Functions](#-functions) - [🎨 Requirements](#-requirements) - [👥 User Guide](#-user-guide) - [1. Simple usage](#1-simple-usage) - [2. Transmit value even using thread pool](#2-transmit-value-even-using-thread-pool) - [2.1 Decorate `Runnable` and `Callable`](#21-decorate-runnable-and-callable) - [2.2 Decorate thread pool](#22-decorate-thread-pool) - [2.3 Use Java Agent to decorate thread pool implementation class](#23-use-java-agent-to-decorate-thread-pool-implementation-class) - [🔌 Java API Docs](#-java-api-docs) - [🍪 Maven Dependency](#-maven-dependency) - [🔨 How to compile and build](#-how-to-compile-and-build) - [🗿 More Documentation](#-more-documentation) - [📚 Related Resources](#-related-resources) - [JDK Core Classes](#jdk-core-classes) - [💗 Who Used](#-who-used) - [👷 Contributors](#-contributors) ---------------------------------------- # 🔧 Functions 👉 `TransmittableThreadLocal`(`TTL`): The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced `InheritableThreadLocal` that transmits values between threads even using thread pooling components. Support `Java 6~21`. Class [`InheritableThreadLocal`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html) in `JDK` can transmit value to child thread from parent thread. But when use thread pool, thread is cached up and used repeatedly. Transmitting value from parent thread to child thread has no meaning. Application need transmit value from the time task is created to the time task is executed. If you have problem or question, please [submit Issue](https://github.com/alibaba/transmittable-thread-local/issues) or play [fork](https://github.com/alibaba/transmittable-thread-local/fork) and pull request dance. > [!NOTE] > From `TTL v2.13+` upgrade to `Java 8`. 🚀 > If you need `Java 6` support, use version `2.12.x` Maven Central # 🎨 Requirements The Requirements listed below is also why I sort out `TransmittableThreadLocal` in my work. - Application container or high layer framework transmit information to low layer sdk. - Transmit context to logging without application code aware. # 👥 User Guide ## 1. Simple usage ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // set in parent thread context.set("value-set-in-parent"); // ===================================================== // read in child thread, value is "value-set-in-parent" String value = context.get(); ``` \# See the executable demo [`SimpleDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/SimpleDemo.kt) with full source code. This is the function of class `InheritableThreadLocal`, should use class `InheritableThreadLocal` instead. But when use thread pool, thread is cached up and used repeatedly. Transmitting value from parent thread to child thread has no meaning. Application need transmit value from the time task is created to the point task is executed. The solution is below usage. ## 2. Transmit value even using thread pool ### 2.1 Decorate `Runnable` and `Callable` Decorate input `Runnable` and `Callable` by [`TtlRunnable`](ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java) and [`TtlCallable`](ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java). Sample code: ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // set in parent thread context.set("value-set-in-parent"); Runnable task = new RunnableTask(); // extra work, create decorated ttlRunnable object Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable); // ===================================================== // read in task, value is "value-set-in-parent" String value = context.get(); ``` **_NOTE_**: Even when the same `Runnable` task is submitted to the thread pool multiple times, the decoration operations(ie: `TtlRunnable.get(task)`) is required for each submission to capture the value of the `TransmittableThreadLocal` context at submission time; That is, if the same task is submitted next time without reperforming decoration and still using the last `TtlRunnable`, the submitted task will run in the context of the last captured context. The sample code is as follows: ```java // first submission Runnable task = new RunnableTask(); executorService.submit(TtlRunnable.get(task)); // ... some biz logic, // and modified TransmittableThreadLocal context ... context.set("value-modified-in-parent"); // next submission // reperform decoration to transmit the modified TransmittableThreadLocal context executorService.submit(TtlRunnable.get(task)); ``` Above code show how to dealing with `Runnable`, `Callable` is similar: ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // set in parent thread context.set("value-set-in-parent"); Callable call = new CallableTask(); // extra work, create decorated ttlCallable object Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable); // ===================================================== // read in call, value is "value-set-in-parent" String value = context.get(); ``` \# See the executable demo [`TtlWrapperDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlWrapperDemo.kt) with full source code. ### 2.2 Decorate thread pool Eliminating the work of `Runnable` and `Callable` Decoration every time it is submitted to thread pool. This work can be completed in the thread pool. Use util class [`TtlExecutors`](ttl-core/src/main/java/com/alibaba/ttl3/executor/TtlExecutors.java) to decorate thread pool. Util class `TtlExecutors` has below methods: - `getTtlExecutor`: decorate interface `Executor` - `getTtlExecutorService`: decorate interface `ExecutorService` - `getTtlScheduledExecutorService`: decorate interface `ScheduledExecutorService` Sample code: ```java ExecutorService executorService = ... // extra work, create decorated executorService object executorService = TtlExecutors.getTtlExecutorService(executorService); TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // set in parent thread context.set("value-set-in-parent"); Runnable task = new RunnableTask(); Callable call = new CallableTask(); executorService.submit(task); executorService.submit(call); // ===================================================== // read in Task or Callable, value is "value-set-in-parent" String value = context.get(); ``` \# See the executable demo [`TtlExecutorWrapperDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlExecutorWrapperDemo.kt) with full source code. ### 2.3 Use Java Agent to decorate thread pool implementation class In this usage, transmittance is transparent\(no decoration operation\). Sample code: ```java // ## 1. upper layer logic of framework ## TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); // ## 2. biz logic ## ExecutorService executorService = Executors.newFixedThreadPool(3); Runnable task = new RunnableTask(); Callable call = new CallableTask(); executorService.submit(task); executorService.submit(call); // ## 3. underlayer logic of framework ## // read in Task or Callable, value is "value-set-in-parent" String value = context.get(); ``` \# See the executable demo [`AgentDemo.kt`](ttl2-compatible/src/test/java/com/alibaba/demo/ttl/agent/AgentDemo.kt) with full source code, run demo by the script [`scripts/run-agent-demo.sh`](scripts/run-agent-demo.sh). At present, `TTL` agent has decorated below `JDK` execution components(aka. thread pool) implementation: - `java.util.concurrent.ThreadPoolExecutor` and `java.util.concurrent.ScheduledThreadPoolExecutor` - decoration implementation code is in [`JdkExecutorTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/JdkExecutorTtlTransformlet.java). - `java.util.concurrent.ForkJoinTask`(corresponding execution component is `java.util.concurrent.ForkJoinPool`) - decoration implementation code is in [`ForkJoinTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/ForkJoinTtlTransformlet.java), supports since version **_`2.5.1`_**. - **_NOTE_**: [**_`CompletableFuture`_**](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/CompletableFuture.html) and (parallel) [**_`Stream`_**](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/package-summary.html) introduced in Java 8 is executed through `ForkJoinPool` underneath, so after supporting `ForkJoinPool`, `TTL` also supports `CompletableFuture` and `Stream` transparently. 🎉 - `java.util.TimerTask`(corresponding execution component is `java.util.Timer`) - decoration implementation code is in [`TimerTaskTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/TimerTaskTtlTransformlet.java), supports since version **_`2.7.0`_**. - **_NOTE_**: Since version `2.11.2` decoration for `TimerTask` default is enable (because correctness is first concern, not the best practice like "It is not recommended to use `TimerTask`" :); before version `2.11.1` default is disable. - enabled/disable by agent argument `ttl.agent.enable.timer.task`: - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:true` - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false` - more info about `TTL` agent arguments, see [the javadoc of `TtlAgent.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlAgent.java). Add start options on Java command: - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar` Java command example: ```bash java -javaagent:transmittable-thread-local-2.x.y.jar \ -cp classes \ com.alibaba.demo.ttl.agent.AgentDemo # if changed the TTL jar file name or the TTL version is before 2.6.0, # should set argument -Xbootclasspath explicitly. java -javaagent:path/to/ttl-foo-name-changed.jar \ -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \ -cp classes \ com.alibaba.demo.ttl.agent.AgentDemo java -javaagent:path/to/transmittable-thread-local-2.5.1.jar \ -Xbootclasspath/a:path/to/transmittable-thread-local-2.5.1.jar \ -cp classes \ com.alibaba.demo.ttl.agent.AgentDemo ``` Run the script [`scripts/run-agent-demo.sh`](scripts/run-agent-demo.sh) to start demo of "Use Java Agent to decorate thread pool implementation class". **NOTE**: - Because TTL agent modified the `JDK` std lib classes, make code refer from std lib class to the TTL classes, so the TTL Agent jar must be added to `boot classpath`. - Since `v2.6.0`, TTL agent jar will auto add self to `boot classpath`. But you **should _NOT_** modify the downloaded TTL jar file name in the maven repo(eg: `transmittable-thread-local-2.x.y.jar`). - if you modified the downloaded TTL jar file name(eg: `ttl-foo-name-changed.jar`), you must add TTL agent jar to `boot classpath` manually by java option `-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar`. The implementation of auto adding self agent jar to `boot classpath` use the `Boot-Class-Path` property of manifest file(`META-INF/MANIFEST.MF`) in the TTL Java Agent Jar: > [!NOTE] > `Boot-Class-Path` > > A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). > These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed. More info: - [`Java Agent Specification` - `JavaDoc`文档](https://docs.oracle.com/en/java/javase/21/docs/api/java.instrument/java/lang/instrument/package-summary.html#package.description) - [JAR File Specification - JAR Manifest](https://docs.oracle.com/en/java/javase/21/docs/specs/jar/jar.html#jar-manifest) - [Working with Manifest Files - The Java™ Tutorials](https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html) # 🔌 Java API Docs The current version Java API documentation: # 🍪 Maven Dependency ```xml com.alibaba transmittable-thread-local 2.14.4 ``` Check available version at [maven.org](https://repo1.maven.org/maven2/com/alibaba/transmittable-thread-local/maven-metadata.xml). # 🔨 How to compile and build Compilation/build environment require **_`JDK 8+`_**; Compilation can be performed in the normal way of `Maven`. \# The project already contains `Maven` that satisfied the required version, directly run **_`mvnw` in the project root directory_**; there is no need to manually install `Maven` by yourself. ```bash # Run test case ./mvnw test # Compile and package ./mvnw package # Run test case, compile and package, install TTL library to local Maven ./mvnw install ################################################## # If you use maven installed by yourself, the version requirement: maven 3.3.9+ mvn install ``` # 🗿 More Documentation - [🎓 Developer Guide](docs/developer-guide-en.md) # 📚 Related Resources ## JDK Core Classes - [WeakHashMap](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/WeakHashMap.html) - [InheritableThreadLocal](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html) # 💗 Who Used Some open-source projects used `TTL`: - **Middleware** - [`sofastack/sofa-rpc` ![](https://img.shields.io/github/stars/sofastack/sofa-rpc.svg?style=social&label=Star)](https://github.com/sofastack/sofa-rpc) [![star](https://gitee.com/sofastack/sofa-rpc/badge/star.svg?theme=gray)](https://gitee.com/sofastack/sofa-rpc) SOFARPC is a high-performance, high-extensibility, production-level Java RPC framework - [`trpc-group/trpc-java` ![](https://img.shields.io/github/stars/trpc-group/trpc-java.svg?style=social&label=Star)](https://github.com/trpc-group/trpc-java) A pluggable, high-performance RPC framework written in java - [`tencentmusic/supersonic` ![](https://img.shields.io/github/stars/tencentmusic/supersonic.svg?style=social&label=Star)](https://github.com/tencentmusic/supersonic) SuperSonic is an out-of-the-box yet highly extensible framework for building ChatBI - [`dromara/hmily` ![](https://img.shields.io/github/stars/dromara/hmily.svg?style=social&label=Star)](https://github.com/dromara/hmily) [![star](https://gitee.com/dromara/hmily/badge/star.svg?theme=gray)](https://gitee.com/dromara/hmily) Distributed transaction solutions - [`dromara/gobrs-async` ![](https://img.shields.io/github/stars/dromara/gobrs-async.svg?style=social&label=Star)](https://github.com/dromara/gobrs-async) [![star](https://gitee.com/dromara/gobrs-async/badge/star.svg?theme=gray)](https://gitee.com/dromara/gobrs-async) 一款功能强大、配置灵活、带有全链路异常回调、内存优化、异常状态管理于一身的高性能异步编排框架。为企业提供在复杂应用场景下动态任务编排的能力。 针对于复杂场景下,异步线程复杂性、任务依赖性、异常状态难控制性 - [`dromara/dynamic-tp` ![](https://img.shields.io/github/stars/dromara/dynamic-tp.svg?style=social&label=Star)](https://github.com/dromara/dynamic-tp) [![star](https://gitee.com/dromara/dynamic-tp/badge/star.svg?theme=gray)](https://gitee.com/dromara/dynamic-tp) Lightweight dynamic threadpool, with monitoring and alarming functions, base on popular config centers (already support Nacos、Apollo、Zookeeper, can be customized through SPI) - [`opengoofy/hippo4j` ![](https://img.shields.io/github/stars/opengoofy/hippo4j.svg?style=social&label=Star)](https://github.com/opengoofy/hippo4j) [![star](https://gitee.com/magestack/hippo4j/badge/star.svg?theme=gray)](https://gitee.com/magestack/hippo4j) 动态线程池框架,附带监控报警功能,支持 JDK、Tomcat、Jetty、Undertow 线程池;Apache RocketMQ、Dubbo、RabbitMQ、Hystrix 消费等线程池。内置两种使用模式:轻量级依赖配置中心以及无中间件依赖版本 - [`siaorg/sia-gateway` ![](https://img.shields.io/github/stars/siaorg/sia-gateway.svg?style=social&label=Star)](https://github.com/siaorg/sia-gateway) microservice route gateway(zuul-plus) - [`huaweicloud/Sermant` ![](https://img.shields.io/github/stars/huaweicloud/Sermant.svg?style=social&label=Star)](https://github.com/huaweicloud/Sermant) Sermant, a proxyless service mesh solution based on Javaagent - [`ZTO-Express/zms` ![](https://img.shields.io/github/stars/ZTO-Express/zms.svg?style=social&label=Star)](https://github.com/ZTO-Express/zms) [![star](https://gitee.com/zto_express/zms/badge/star.svg?theme=gray)](https://gitee.com/zto_express/zms) ZTO Message Service - [`lxchinesszz/tomato` ![](https://img.shields.io/github/stars/lxchinesszz/tomato.svg?style=social&label=Star)](https://github.com/lxchinesszz/tomato) 一款专门为SpringBoot项目设计的幂等组件 - [`ytyht226/taskflow` ![](https://img.shields.io/github/stars/ytyht226/taskflow.svg?style=social&label=Star)](https://github.com/ytyht226/taskflow) 一款轻量、简单易用、可灵活扩展的通用任务编排框架,基于有向无环图(DAG)的方式实现,框架提供了组件复用、同步/异步编排、条件判断、分支选择等能力,可以根据不同的业务场景对任意的业务流程进行编排 - [`foldright/cffu` ![](https://img.shields.io/github/stars/foldright/cffu.svg?style=social&label=star)](https://github.com/foldright/cffu) 🦝 Java CompletableFuture Fu, aka. CF-Fu, pronounced "Shifu"; include best practice/traps guide and a tiny sidekick library to improve user experience and reduce misuse. - [`tuya/connector` ![](https://img.shields.io/github/stars/tuya/connector.svg?style=social&label=Star)](https://github.com/tuya/connector) The connector framework maps cloud APIs to local APIs based on simple configurations and flexible extension mechanisms - **Middleware/Data Processing** - [`apache/shardingsphere` ![](https://img.shields.io/github/stars/apache/shardingsphere.svg?style=social&label=Star)](https://github.com/apache/shardingsphere) [![star](https://gitee.com/Sharding-Sphere/sharding-sphere/badge/star.svg?theme=gray)](https://gitee.com/Sharding-Sphere/sharding-sphere) Ecosystem to transform any database into a distributed database system, and enhance it with sharding, elastic scaling, encryption features & more - [`apache/kylin` ![](https://img.shields.io/github/stars/apache/kylin.svg?style=social&label=Star)](https://github.com/apache/kylin) A unified and powerful OLAP platform for Hadoop and Cloud. - [`mybatis-flex/mybatis-flex` ![](https://img.shields.io/github/stars/mybatis-flex/mybatis-flex.svg?style=social&label=Star)](https://github.com/mybatis-flex/mybatis-flex) [![star](https://gitee.com/mybatis-flex/mybatis-flex/badge/star.svg?theme=gray)](https://gitee.com/mybatis-flex/mybatis-flex) mybatis-flex is an elegant Mybatis Enhancement Framework - [`basicai/xtreme1` ![](https://img.shields.io/github/stars/basicai/xtreme1.svg?style=social&label=Star)](https://github.com/basicai/xtreme1) The Next GEN Platform for Multisensory Training Data. #3D annotation, lidar-camera annotation and image annotation tools are supported - [`oceanbase/odc` ![](https://img.shields.io/github/stars/oceanbase/odc.svg?style=social&label=Star)](https://github.com/oceanbase/odc) An open-source, enterprise-grade database tool for collaborative development - [`sagframe/sagacity-sqltoy` ![](https://img.shields.io/github/stars/sagframe/sagacity-sqltoy.svg?style=social&label=Star)](https://github.com/sagframe/sagacity-sqltoy) Java真正智慧的ORM框架 - [`dromara/stream-query` ![](https://img.shields.io/github/stars/dromara/stream-query.svg?style=social&label=Star)](https://github.com/dromara/stream-query) [![star](https://gitee.com/dromara/stream-query/badge/star.svg?theme=gray)](https://gitee.com/dromara/stream-query) 允许完全摆脱Mapper的mybatis-plus体验;可以使用类似“工具类”这样的静态函数进行数据库操作 - [`luo-zhan/Transformer` ![](https://img.shields.io/github/stars/luo-zhan/Transformer.svg?style=social&label=Star)](https://github.com/luo-zhan/Transformer) Transformer可能是最简单,但最强大的字段转换插件,一个注解搞定任意转换,让开发变得更加丝滑 - [`SimonAlong/Neo` ![](https://img.shields.io/github/stars/SimonAlong/Neo.svg?style=social&label=Star)](https://github.com/SimonAlong/Neo) Orm框架:基于ActiveRecord思想开发的至简化且功能很全的Orm框架 - [`ppdaicorp/das` ![](https://img.shields.io/github/stars/ppdaicorp/das.svg?style=social&label=Star)](https://github.com/ppdaicorp/das) 数据库访问框架(data access service),包括数据库控制台das console,数据库客户端das client和数据库服务端das server三部分 - [`didi/ALITA` ![](https://img.shields.io/github/stars/didi/ALITA.svg?style=social&label=Star)](https://github.com/didi/ALITA) a layer-based data analysis tool - [`didi/daedalus` ![](https://img.shields.io/github/stars/didi/daedalus.svg?style=social&label=Star)](https://github.com/didi/daedalus) 实现快速创建数据构造流程,数据构造流程的可视化、线上化、持久化、标准化 - **Middleware/Flow engine** - [`dromara/liteflow` ![](https://img.shields.io/github/stars/dromara/liteflow.svg?style=social&label=Star)](https://github.com/dromara/liteflow) [![star](https://gitee.com/dromara/liteFlow/badge/star.svg?theme=gray)](https://gitee.com/dromara/liteFlow) a lightweight and practical micro-process framework - [`alibaba/bulbasaur` ![](https://img.shields.io/github/stars/alibaba/bulbasaur.svg?style=social&label=Star)](https://github.com/alibaba/bulbasaur) A pluggable, scalable process engine - **Middleware/Log** - [`dromara/TLog` ![](https://img.shields.io/github/stars/dromara/TLog.svg?style=social&label=Star)](https://github.com/dromara/TLog) [![star](https://gitee.com/dromara/TLog/badge/star.svg?theme=gray)](https://gitee.com/dromara/TLog) Lightweight distributed log label tracking framework - [`fayechenlong/plumelog` ![](https://img.shields.io/github/stars/fayechenlong/plumelog.svg?style=social&label=Star)](https://github.com/fayechenlong/plumelog) [![star](https://gitee.com/plumeorg/plumelog/badge/star.svg?theme=gray)](https://gitee.com/plumeorg/plumelog) 一个java分布式日志组件,支持百亿级别 - [`minbox-projects/minbox-logging` ![](https://img.shields.io/github/stars/minbox-projects/minbox-logging.svg?style=social&label=Star)](https://github.com/minbox-projects/minbox-logging) [![star](https://gitee.com/minbox-projects/minbox-logging/badge/star.svg?theme=gray)](https://gitee.com/minbox-projects/minbox-logging) 分布式零侵入式、链路式请求日志分析框架。提供Admin端点进行采集日志、分析日志、日志告警通知、服务性能分析等。通过Admin Ui可查看实时链路日志信息、在线业务服务列表 - [`minbox-projects/api-boot` ![](https://img.shields.io/github/stars/minbox-projects/api-boot.svg?style=social&label=Star)](https://github.com/minbox-projects/api-boot) [![star](https://gitee.com/minbox-projects/api-boot/badge/star.svg?theme=gray)](https://gitee.com/minbox-projects/api-boot) 为接口服务而生的,基于“ SpringBoot”完成扩展和自动配置,内部封装了一系列的开箱即用Starters - [`ofpay/logback-mdc-ttl` ![](https://img.shields.io/github/stars/ofpay/logback-mdc-ttl.svg?style=social&label=Star)](https://github.com/ofpay/logback-mdc-ttl) logback扩展,集成transmittable-thread-local支持跨线程池的mdc跟踪 - [`oldratlee/log4j2-ttl-thread-context-map` ![](https://img.shields.io/github/stars/oldratlee/log4j2-ttl-thread-context-map.svg?style=social&label=Star)](https://github.com/oldratlee/log4j2-ttl-thread-context-map) Log4j2 TTL ThreadContextMap, Log4j2 extension integrated TransmittableThreadLocal to MDC - **Middleware/Bytecode** - [`ymm-tech/easy-byte-coder` ![](https://img.shields.io/github/stars/ymm-tech/easy-byte-coder.svg?style=social&label=Star)](https://github.com/ymm-tech/easy-byte-coder) Easy-byte-coder is a non-invasive bytecode injection framework based on JVM - **Business service or platform application** - [`OpenBankProject/OBP-API` ![](https://img.shields.io/github/stars/OpenBankProject/OBP-API.svg?style=social&label=Star)](https://github.com/OpenBankProject/OBP-API) An open source RESTful API platform for banks that supports Open Banking, XS2A and PSD2 through access to accounts, transactions, counterparties, payments, entitlements and metadata - plus a host of internal banking and management APIs - [`gz-yami/mall4j` ![](https://img.shields.io/github/stars/gz-yami/mall4j.svg?style=social&label=Star)](https://github.com/gz-yami/mall4j) [![star](https://gitee.com/gz-yami/mall4j/badge/star.svg?theme=gray)](https://gitee.com/gz-yami/mall4j) 电商商城 java电商商城系统 uniapp商城 多用户商城 - [`Joolun/JooLun-wx` ![](https://img.shields.io/github/stars/Joolun/JooLun-wx.svg?style=social&label=Star)](https://github.com/Joolun/JooLun-wx) [![star](https://gitee.com/joolun/JooLun-wx/badge/star.svg?theme=gray)](https://gitee.com/joolun/JooLun-wx) JooLun微信商城 - [`HummerRisk/HummerRisk` ![](https://img.shields.io/github/stars/HummerRisk/HummerRisk.svg?style=social&label=Star)](https://github.com/HummerRisk/HummerRisk) 云原生安全平台,包括混合云安全治理和容器云安全检测 - [`XiaoMi/mone` ![](https://img.shields.io/github/stars/XiaoMi/mone.svg?style=social&label=Star)](https://github.com/XiaoMi/mone) `Mone`以微服务为核心的一站式企业协同研发平台。支持公共云、专有云和混合云多种部署形态;提供从“项目创建->开发->部署->治理->应用观测”端到端的研发全流程服务;通过云原生新技术和研发新模式,打造“双敏”,敏捷研发和敏捷组织,保障小米-中国区高复杂业务、大规模团队的敏捷研发协同,实现多倍效能提升。 - [`yangzongzhuan/RuoYi-Cloud` ![](https://img.shields.io/github/stars/yangzongzhuan/RuoYi-Cloud.svg?style=social&label=Star)](https://github.com/yangzongzhuan/RuoYi-Cloud) [![star](https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=gray)](https://gitee.com/y_project/RuoYi-Cloud) 基于Spring Boot、Spring Cloud & Alibaba的分布式微服务架构权限管理系统 - [`somowhere/albedo` ![](https://img.shields.io/github/stars/somowhere/albedo.svg?style=social&label=Star)](https://github.com/somowhere/albedo) [![star](https://gitee.com/somowhere/albedo/badge/star.svg?theme=gray)](https://gitee.com/somowhere/albedo) 基于 Spring Boot 、Spring Security、Mybatis 的RBAC权限管理系统 - [`qwdigital/LinkWechat` ![](https://img.shields.io/github/stars/qwdigital/LinkWechat.svg?style=social&label=Star)](https://github.com/qwdigital/LinkWechat) [![star](https://gitee.com/LinkWeChat/link-wechat/badge/star.svg?theme=gray)](https://gitee.com/LinkWeChat/link-wechat) 基于企业微信的开源 SCRM 系统,采用主流的 Java 微服务架构,是企业私域流量管理与营销的综合解决方案,助力企业提高客户运营效率,强化营销能力,拓展盈利空间 - [`fushengqian/fuint` ![](https://img.shields.io/github/stars/fushengqian/fuint.svg?style=social&label=Star)](https://github.com/fushengqian/fuint) [![star](https://gitee.com/fuint/fuint-uniapp/badge/star.svg?theme=gray)](https://gitee.com/fuint/fuint-uniapp) fuint会员营销系统是一套开源的实体店铺会员管理和营销系统 - [`hiparker/opsli-boot` ![](https://img.shields.io/github/stars/hiparker/opsli-boot.svg?style=social&label=Star)](https://github.com/hiparker/opsli-boot) [![star](https://gitee.com/hiparker/opsli-boot/badge/star.svg?theme=gray)](https://gitee.com/hiparker/opsli-boot) 一款的低代码快速平台,零代码开发,致力于做更简洁的后台管理系统 - [`topiam/eiam` ![](https://img.shields.io/github/stars/topiam/eiam.svg?style=social&label=Star)](https://github.com/topiam/eiam) [![star](https://gitee.com/topiam/eiam/badge/star.svg?theme=gray)](https://gitee.com/topiam/eiam) EIAM(Employee Identity and Access Management Program)企业级开源IAM平台,实现用户全生命周期的管理、统一认证和单点登录、为数字身份安全赋能 - [`Newspiral/newspiral-business` ![](https://img.shields.io/github/stars/Newspiral/newspiral-business.svg?style=social&label=Star)](https://github.com/Newspiral/newspiral-business) 联盟区块链底层平台 - **Tool product** - [`ssssssss-team/spider-flow` ![](https://img.shields.io/github/stars/ssssssss-team/spider-flow.svg?style=social&label=Star)](https://github.com/ssssssss-team/spider-flow) [![star](https://gitee.com/ssssssss-team/spider-flow/badge/star.svg?theme=gray)](https://gitee.com/ssssssss-team/spider-flow) 新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫 - [`nekolr/slime` ![](https://img.shields.io/github/stars/nekolr/slime.svg?style=social&label=Star)](https://github.com/nekolr/slime) 🍰 一个可视化的爬虫平台 - [`Jackson0714/PassJava-Platform` ![](https://img.shields.io/github/stars/Jackson0714/PassJava-Platform.svg?style=social&label=Star)](https://github.com/Jackson0714/PassJava-Platform) 一款面试刷题的 Spring Cloud 开源系统。零碎时间利用小程序查看常见面试题,夯实Java基础。 该项目可以教会你如何搭建SpringBoot项目,Spring Cloud项目。 采用流行的技术,如 SpringBoot、MyBatis、Redis、 MySql、 MongoDB、 RabbitMQ、Elasticsearch,采用Docker容器化部署 - [`martin-chips/DimpleBlog` ![](https://img.shields.io/github/stars/martin-chips/DimpleBlog.svg?style=social&label=Star)](https://github.com/martin-chips/DimpleBlog) 基于`SpringBoot2`搭建的个人博客系统 - [`zjcscut/octopus` ![](https://img.shields.io/github/stars/zjcscut/octopus.svg?style=social&label=Star)](https://github.com/zjcscut/octopus) 长链接压缩为短链接的服务 - [`xggz/mqr` ![](https://img.shields.io/github/stars/xggz/mqr.svg?style=social&label=Star)](https://github.com/xggz/mqr) [![star](https://gitee.com/mlyai/mqr/badge/star.svg?theme=gray)](https://gitee.com/mlyai/mqr) 茉莉QQ机器人(简称MQR),采用mirai的Android协议实现的QQ机器人服务,通过web控制机器人的启停和配置 - **Test solution or tool** - [`alibaba/jvm-sandbox-repeater` ![](https://img.shields.io/github/stars/alibaba/jvm-sandbox-repeater.svg?style=social&label=Star)](https://github.com/alibaba/jvm-sandbox-repeater) A Java server-side recording and playback solution based on JVM-Sandbox, 录制/回放通用解决方案 - [`vivo/MoonBox` ![](https://img.shields.io/github/stars/vivo/MoonBox.svg?style=social&label=Star)](https://github.com/vivo/MoonBox) Moonbox(月光宝盒)是JVM-Sandbox生态下的,基于jvm-sandbox-repeater重新开发的,一款流量回放平台产品。相较于jvm-sandbox-repeater,Moonbox功能更加丰富、数据可靠性更高,同时便于快速线上部署和使用 - [`alibaba/testable-mock` ![](https://img.shields.io/github/stars/alibaba/testable-mock.svg?style=social&label=Star)](https://github.com/alibaba/testable-mock) 换种思路写Mock,让单元测试更简单 - [`shulieTech/Takin` ![](https://img.shields.io/github/stars/shulieTech/Takin.svg?style=social&label=Star)](https://github.com/shulieTech/Takin) measure online environmental performance test for full-links, Especially for microservices - [`shulieTech/LinkAgent` ![](https://img.shields.io/github/stars/shulieTech/LinkAgent.svg?style=social&label=Star)](https://github.com/shulieTech/LinkAgent) a Java-based open-source agent designed to collect data and control Functions for Java applications through JVM bytecode, without modifying applications codes - [`alibaba/virtual-environment` ![](https://img.shields.io/github/stars/alibaba/virtual-environment.svg?style=social&label=Star)](https://github.com/alibaba/virtual-environment) Route isolation with service sharing, 阿里测试环境服务隔离和联调机制的`Kubernetes`版实现 - **`Spring Cloud`/`Spring Boot` microservices framework solution or scaffold** - [`YunaiV/ruoyi-vue-pro` ![](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Star)](https://github.com/YunaiV/ruoyi-vue-pro) [![star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=gray)](https://gitee.com/zhijiantianya/ruoyi-vue-pro) 一套全部开源的企业级的快速开发平台。基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 微信小程序,支持 RBAC 动态权限、数据权限、SaaS 多租户、Activiti + Flowable 工作流、三方登录、支付、短信、商城等功能 - [`YunaiV/yudao-cloud` ![](https://img.shields.io/github/stars/YunaiV/yudao-cloud.svg?style=social&label=Star)](https://github.com/YunaiV/yudao-cloud) [![star](https://gitee.com/zhijiantianya/yudao-cloud/badge/star.svg?theme=gray)](https://gitee.com/zhijiantianya/yudao-cloud) RuoYi-Vue 全新 Cloud 版本,优化重构所有功能。基于 Spring Cloud Alibaba + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能 - [`zlt2000/microservices-platform` ![](https://img.shields.io/github/stars/zlt2000/microservices-platform.svg?style=social&label=Star)](https://github.com/zlt2000/microservices-platform) [![star](https://gitee.com/zlt2000/microservices-platform/badge/star.svg?theme=gray)](https://gitee.com/zlt2000/microservices-platform) 基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba并采用前后端分离的企业级微服务多租户系统架构 - [`dromara/lamp-cloud` ![](https://img.shields.io/github/stars/dromara/lamp-cloud.svg?style=social&label=Star)](https://github.com/zuihou/lamp-cloud) [![star](https://gitee.com/dromara/lamp-cloud/badge/star.svg?theme=gray)](https://gitee.com/dromara/lamp-cloud) 基于Jdk11 + SpringCloud + SpringBoot 的微服务快速开发平台,其中的可配置的SaaS功能尤其闪耀, 具备RBAC功能、网关统一鉴权、Xss防跨站攻击、自动代码生成、多种存储系统、分布式事务、分布式定时任务等多个模块,支持多业务系统并行开发, 支持多服务并行开发,可以作为后端服务的开发脚手架 - [`zuihou/lamp-util` ![](https://img.shields.io/github/stars/zuihou/lamp-util.svg?style=social&label=Star)](https://github.com/zuihou/lamp-util) [![star](https://gitee.com/zuihou111/lamp-util/badge/star.svg?theme=gray)](https://gitee.com/zuihou111/lamp-util) 打造一套兼顾 SpringBoot 和 SpringCloud 项目的公共工具类 - [`matevip/matecloud` ![](https://img.shields.io/github/stars/matevip/matecloud.svg?style=social&label=Star)](https://github.com/matevip/matecloud) [![star](https://gitee.com/matevip/matecloud/badge/star.svg?theme=gray)](https://gitee.com/matevip/matecloud) 一款基于Spring Cloud Alibaba的微服务架构 - [`gavenwangcn/vole` ![](https://img.shields.io/github/stars/gavenwangcn/vole.svg?style=social&label=Star)](https://github.com/gavenwangcn/vole) SpringCloud Micro service business framework - [`liuweijw/fw-cloud-framework` ![](https://img.shields.io/github/stars/liuweijw/fw-cloud-framework.svg?style=social&label=Star)](https://github.com/liuweijw/fw-cloud-framework) [![star](https://gitee.com/liuweijw/fw-cloud-framework/badge/star.svg?theme=gray)](https://gitee.com/liuweijw/fw-cloud-framework) 基于springcloud全家桶开发分布式框架(支持oauth2认证授权、SSO登录、统一下单、微信公众号服务、Shardingdbc分库分表、常见服务监控、链路监控、异步日志、redis缓存等功能),实现基于Vue全家桶等前后端分离项目工程 - [`liuht777/Taroco` ![](https://img.shields.io/github/stars/liuht777/Taroco.svg?style=social&label=Star)](https://github.com/liuht777/Taroco) 整合Nacos、Spring Cloud Alibaba,提供了一系列starter组件, 同时提供服务治理、服务监控、OAuth2权限认证,支持服务降级/熔断、服务权重 - [`mingyang66/spring-parent` ![](https://img.shields.io/github/stars/mingyang66/spring-parent.svg?style=social&label=Star)](https://github.com/mingyang66/spring-parent) 数据库多数据源、Redis多数据源、日志组件、全链路日志追踪、埋点扩展点、Netty、微服务、开发基础框架支持、异常统一处理、返回值、跨域、API路由、监控等 - [`budwk/budwk` ![](https://img.shields.io/github/stars/budwk/budwk.svg?style=social&label=Star)](https://github.com/budwk/budwk) [![star](https://gitee.com/budwk/budwk/badge/star.svg?theme=gray)](https://gitee.com/budwk/budwk) `BudWk` 原名 [`NutzWk` ![](https://img.shields.io/github/stars/Wizzercn/NutzWk.svg?style=social&label=Star)](https://github.com/Wizzercn/NutzWk) [![star](https://gitee.com/wizzer/NutzWk/badge/star.svg?theme=gray)](https://gitee.com/wizzer/NutzWk),基于国产框架 nutz 及 nutzboot 开发的开源Web基础项目,集权限体系、系统参数、数据字典、站内消息、定时任务、CMS、微信等最常用功能,不庞杂、不面面俱到,使其具有上手容易、开发便捷、扩展灵活等特性,特别适合各类大中小型定制化项目需求 - [`yinjihuan/spring-cloud` ![](https://img.shields.io/github/stars/yinjihuan/spring-cloud.svg?style=social&label=Star)](https://github.com/yinjihuan/spring-cloud) 《Spring Cloud微服务-全栈技术与案例解析》和《Spring Cloud微服务 入门 实战与进阶》配套源码 - [`louyanfeng25/ddd-demo` ![](https://img.shields.io/github/stars/louyanfeng25/ddd-demo.svg?style=social&label=Star)](https://github.com/louyanfeng25/ddd-demo) 《深入浅出DDD》讲解的演示项目,为了能够更好的理解Demo中的分层与逻辑处理,我强烈建议你配合小册来深入了解DDD - [`nageoffer/12306` ![](https://img.shields.io/github/stars/nageoffer/12306.svg?style=social&label=Star)](https://github.com/nageoffer/12306) 12306 铁路购票服务是与大家生活和出行相关的关键系统,包括会员、购票、订单、支付和网关等服务。 more open-source projects used `TTL`, see [![user repos](https://badgen.net/github/dependents-repo/alibaba/transmittable-thread-local?label=user%20repos)](https://github.com/alibaba/transmittable-thread-local/network/dependents) # 👷 Contributors - Jerry Lee \ [@oldratlee](https://github.com/oldratlee) - Yang Fang \ [@driventokill](https://github.com/driventokill) - Zava Xu \ [@zavakid](https://github.com/zavakid) - wuwen \ [@wuwen5](https://github.com/wuwen5) - rybalkinsd \ [@rybalkinsd](https://github.com/rybalkinsd) - David Dai \<351450944 at qq dot com> [@LNAmp](https://github.com/LNAmp) - Your name here :-) [![GitHub Contributors](https://contrib.rocks/image?repo=alibaba/transmittable-thread-local)](https://github.com/alibaba/transmittable-thread-local/graphs/contributors) ================================================ FILE: README.md ================================================ #
📌 TransmittableThreadLocal(TTL)
> [!IMPORTANT] > 🚧 这个分支是`TransmittableThreadLocal(TTL) v3`,在开发中还没有发布。 > `v3`的版本说明、工作项列表及其进展,参见 [issue 432](https://github.com/alibaba/transmittable-thread-local/issues/432)。 > > 👉 目前使用中的稳定发布版本`v2.x`在 [**分支`2.x`**](https://github.com/alibaba/transmittable-thread-local/tree/2.x)上。

Fast CI Strong CI Coverage Status JDK support License Javadocs Maven Central GitHub release GitHub Stars GitHub Forks user repos GitHub issues GitHub Contributors GitHub repo size gitpod: Ready to Code

[📖 English Documentation](README-EN.md) | 📖 中文文档 ---------------------------------------- - [🔧 功能](#-%E5%8A%9F%E8%83%BD) - [🎨 需求场景](#-%E9%9C%80%E6%B1%82%E5%9C%BA%E6%99%AF) - [👥 User Guide](#-user-guide) - [1. 简单使用](#1-%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8) - [2. 保证线程池中传递值](#2-%E4%BF%9D%E8%AF%81%E7%BA%BF%E7%A8%8B%E6%B1%A0%E4%B8%AD%E4%BC%A0%E9%80%92%E5%80%BC) - [2.1 修饰`Runnable`和`Callable`](#21-%E4%BF%AE%E9%A5%B0runnable%E5%92%8Ccallable) - [整个过程的完整时序图](#%E6%95%B4%E4%B8%AA%E8%BF%87%E7%A8%8B%E7%9A%84%E5%AE%8C%E6%95%B4%E6%97%B6%E5%BA%8F%E5%9B%BE) - [2.2 修饰线程池](#22-%E4%BF%AE%E9%A5%B0%E7%BA%BF%E7%A8%8B%E6%B1%A0) - [2.3 使用`Java Agent`来修饰`JDK`线程池实现类](#23-%E4%BD%BF%E7%94%A8java-agent%E6%9D%A5%E4%BF%AE%E9%A5%B0jdk%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%AE%9E%E7%8E%B0%E7%B1%BB) - [`Java Agent`的启动参数配置](#java-agent%E7%9A%84%E5%90%AF%E5%8A%A8%E5%8F%82%E6%95%B0%E9%85%8D%E7%BD%AE) - [🔌 Java API Docs](#-java-api-docs) - [🍪 Maven依赖](#-maven%E4%BE%9D%E8%B5%96) - [🔨 关于编译构建](#-%E5%85%B3%E4%BA%8E%E7%BC%96%E8%AF%91%E6%9E%84%E5%BB%BA) - [❓ FAQ](#-faq) - [✨ 使用`TTL`的好处与必要性](#-%E4%BD%BF%E7%94%A8ttl%E7%9A%84%E5%A5%BD%E5%A4%84%E4%B8%8E%E5%BF%85%E8%A6%81%E6%80%A7) - [🗿 更多文档](#-%E6%9B%B4%E5%A4%9A%E6%96%87%E6%A1%A3) - [📚 相关资料](#-%E7%9B%B8%E5%85%B3%E8%B5%84%E6%96%99) - [JDK Core Classes](#jdk-core-classes) - [💗 Who Used](#-who-used) - [👷 Contributors](#-contributors) ---------------------------------------- # 🔧 功能 👉 `TransmittableThreadLocal`(`TTL`):在使用线程池等会池化复用线程的执行组件情况下,提供`ThreadLocal`值的传递功能,解决异步执行时上下文传递的问题。一个`Java`标准库本应为框架/中间件设施开发提供的标配能力,本库功能聚焦 & 0依赖,支持`Java 6~21`。 `JDK`的[`InheritableThreadLocal`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html)类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的`ThreadLocal`值传递已经没有意义,应用需要的实际上是把 **任务提交给线程池时**的`ThreadLocal`值传递到 **任务执行时**。 本库提供的[`TransmittableThreadLocal`](ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java)类继承并加强`InheritableThreadLocal`类,解决上述的问题,使用详见 [User Guide](#-user-guide)。 整个`TransmittableThreadLocal`库的核心功能(用户`API`、线程池`ExecutorService`/`ForkJoinPool`/`TimerTask`及其线程工厂的`Wrapper`;开发者`API`、框架/中间件的集成`API`),只有 **_~1000 `SLOC`代码行_**,非常精小。 欢迎 👏 - 建议和提问,[提交 Issue](https://github.com/alibaba/transmittable-thread-local/issues/new) - 贡献和改进,[Fork 后提通过 Pull Request 贡献代码](https://github.com/alibaba/transmittable-thread-local/fork) > [!NOTE] > 从`TTL v2.13+`开始,升级到`Java 8`。🚀 > 如果需要`Java 6`的支持,使用版本`2.12.x` Maven Central # 🎨 需求场景 `ThreadLocal`的需求场景即`TransmittableThreadLocal`的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的执行组件情况下传递`ThreadLocal`值』则是`TransmittableThreadLocal`目标场景。 下面是几个典型场景例子。 1. 分布式跟踪系统 或 全链路压测(即链路打标) 2. 日志收集记录系统上下文 3. `Request`级`Cache` 4. 应用容器或上层框架跨应用代码给下层`SDK`传递信息 各个场景的展开说明参见子文档 [需求场景](docs/requirement-scenario.md)。 # 👥 User Guide 使用类[`TransmittableThreadLocal`](ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java)来保存值,并跨线程池传递。 `TransmittableThreadLocal`继承`InheritableThreadLocal`,使用方式也类似。相比`InheritableThreadLocal`,添加了`protected`的`transmitteeValue()`方法,用于定制 **任务提交给线程池时** 的`ThreadLocal`值传递到 **任务执行时** 的传递方式,缺省是简单的赋值传递。 注意:如果传递的对象(引用类型)会被修改,且没有做深拷贝(如直接传递引用或是浅拷贝),那么 - 因为跨线程传递而不再有线程封闭,传递对象在多个线程之间是有共享的。 - 与`JDK`的[`InheritableThreadLocal.childValue()`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html#childValue(T))一样,需要使用者/业务逻辑注意保证传递对象的线程安全。
关于transmitteeValue方法 的 展开说明

关于构词后缀eree的说明:

  • transmit是动词传递,transmitter动作的执行者/主动方,而transmittee动作的接收者/被动方。
  • eree后缀的常见词是employer(雇主)/employee(雇员)、caller(调用者)/callee(被调用者)。
具体使用方式见下面的说明。 ## 1. 简单使用 父线程给子线程传递值。 示例代码: ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); // ===================================================== // 在子线程中可以读取,值是"value-set-in-parent" String value = context.get(); ``` \# 完整可运行的Demo代码参见[`SimpleDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/SimpleDemo.kt)。 这其实是`InheritableThreadLocal`的功能,应该使用`InheritableThreadLocal`来完成。 但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的`ThreadLocal`值传递已经没有意义,应用需要的实际上是把 **任务提交给线程池时**的`ThreadLocal`值传递到 **任务执行时**。 解决方法参见下面的这几种用法。 ## 2. 保证线程池中传递值 ### 2.1 修饰`Runnable`和`Callable` 使用[`TtlRunnable`](ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java)和[`TtlCallable`](ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java)来修饰传入线程池的`Runnable`和`Callable`。 示例代码: ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); Runnable task = new RunnableTask(); // 额外的处理,生成修饰了的对象ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable); // ===================================================== // Task中可以读取,值是"value-set-in-parent" String value = context.get(); ``` **_注意_**: 即使是同一个`Runnable`任务多次提交到线程池时,每次提交时都需要通过修饰操作(即`TtlRunnable.get(task)`)以抓取这次提交时的`TransmittableThreadLocal`上下文的值;即如果同一个任务下一次提交时不执行修饰而仍然使用上一次的`TtlRunnable`,则提交的任务运行时会是之前修饰操作所抓取的上下文。示例代码如下: ```java // 第一次提交 Runnable task = new RunnableTask(); executorService.submit(TtlRunnable.get(task)); // ...业务逻辑代码, // 并且修改了 TransmittableThreadLocal上下文 ... context.set("value-modified-in-parent"); // 再次提交 // 重新执行修饰,以传递修改了的 TransmittableThreadLocal上下文 executorService.submit(TtlRunnable.get(task)); ``` 上面演示了`Runnable`,`Callable`的处理类似 ```java TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); Callable call = new CallableTask(); // 额外的处理,生成修饰了的对象ttlCallable Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable); // ===================================================== // Call中可以读取,值是"value-set-in-parent" String value = context.get(); ``` \# 完整可运行的Demo代码参见[`TtlWrapperDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlWrapperDemo.kt)。 #### 整个过程的完整时序图 [![时序图](https://user-images.githubusercontent.com/1063891/233595980-ef7f1f8b-36cd-45b3-b55b-45f7b3d1c94f.png)](#dummy) ### 2.2 修饰线程池 省去每次`Runnable`和`Callable`传入线程池时的修饰,这个逻辑可以在线程池中完成。 通过工具类[`TtlExecutors`](ttl-core/src/main/java/com/alibaba/ttl3/executor/TtlExecutors.java)完成,有下面的方法: - `getTtlExecutor`:修饰接口`Executor` - `getTtlExecutorService`:修饰接口`ExecutorService` - `getTtlScheduledExecutorService`:修饰接口`ScheduledExecutorService` 示例代码: ```java ExecutorService executorService = ... // 额外的处理,生成修饰了的对象executorService executorService = TtlExecutors.getTtlExecutorService(executorService); TransmittableThreadLocal context = new TransmittableThreadLocal<>(); // ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); Runnable task = new RunnableTask(); Callable call = new CallableTask(); executorService.submit(task); executorService.submit(call); // ===================================================== // Task或是Call中可以读取,值是"value-set-in-parent" String value = context.get(); ``` \# 完整可运行的Demo代码参见[`TtlExecutorWrapperDemo.kt`](ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlExecutorWrapperDemo.kt)。 ### 2.3 使用`Java Agent`来修饰`JDK`线程池实现类 这种方式,实现线程池的传递是透明的,业务代码中没有修饰`Runnable`或是线程池的代码。即可以做到应用代码 **无侵入**。 \# 关于 **无侵入** 的更多说明参见文档[`Java Agent`方式对应用代码无侵入](docs/developer-guide.md#java-agent%E6%96%B9%E5%BC%8F%E5%AF%B9%E5%BA%94%E7%94%A8%E4%BB%A3%E7%A0%81%E6%97%A0%E4%BE%B5%E5%85%A5)。 示例代码: ```java // ## 1. 框架上层逻辑,后续流程框架调用业务 ## TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); // ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ## ExecutorService executorService = Executors.newFixedThreadPool(3); Runnable task = new RunnableTask(); Callable call = new CallableTask(); executorService.submit(task); executorService.submit(call); // ## 3. 框架下层逻辑 ## // Task或是Call中可以读取,值是"value-set-in-parent" String value = context.get(); ``` Demo参见[`AgentDemo.kt`](ttl2-compatible/src/test/java/com/alibaba/demo/ttl/agent/AgentDemo.kt)。执行工程下的脚本[`scripts/run-agent-demo.sh`](scripts/run-agent-demo.sh)即可运行Demo。 目前`TTL Agent`中,修饰了的`JDK`执行器组件(即如线程池)如下: 1. `java.util.concurrent.ThreadPoolExecutor` 和 `java.util.concurrent.ScheduledThreadPoolExecutor` - 修饰实现代码在[`JdkExecutorTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/JdkExecutorTtlTransformlet.java)。 1. `java.util.concurrent.ForkJoinTask`(对应的执行器组件是`java.util.concurrent.ForkJoinPool`) - 修饰实现代码在[`ForkJoinTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/ForkJoinTtlTransformlet.java)。从版本 **_`2.5.1`_** 开始支持。 - **_注意_**:`Java 8`引入的[**_`CompletableFuture`_**](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/CompletableFuture.html)与(并行执行的)[**_`Stream`_**](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/stream/package-summary.html)底层是通过`ForkJoinPool`来执行,所以支持`ForkJoinPool`后,`TTL`也就透明支持了`CompletableFuture`与`Stream`。🎉 1. `java.util.TimerTask`的子类(对应的执行器组件是`java.util.Timer`) - 修饰实现代码在[`TimerTaskTtlTransformlet.java`](ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/TimerTaskTtlTransformlet.java)。从版本 **_`2.7.0`_** 开始支持。 - **_注意_**:从`2.11.2`版本开始缺省开启`TimerTask`的修饰(因为保证正确性是第一位,而不是最佳实践『不推荐使用`TimerTask`』:);`2.11.1`版本及其之前的版本没有缺省开启`TimerTask`的修饰。 - 使用`Agent`参数`ttl.agent.enable.timer.task`开启/关闭`TimerTask`的修饰: - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:true` - `-javaagent:path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false` - 更多关于`TTL Agent`参数的配置说明详见[`TtlAgent.java`的JavaDoc](ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlAgent.java)。
关于java.util.TimerTask/java.util.Timer 的 展开说明

TimerJDK 1.3的老类,不推荐使用Timer类。

推荐用ScheduledExecutorService
ScheduledThreadPoolExecutor实现更强壮,并且功能更丰富。 如支持配置线程池的大小(Timer只有一个线程);TimerRunnable中抛出异常会中止定时执行。更多说明参见 10. Mandatory Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. - Alibaba Java Coding Guidelines

#### `Java Agent`的启动参数配置 在`Java`的启动参数加上:`-javaagent:path/to/transmittable-thread-local-2.x.y.jar`。 **_注意_**: - 如果修改了下载的`TTL`的`Jar`的文件名(`transmittable-thread-local-2.x.y.jar`),则需要自己手动通过`-Xbootclasspath JVM`参数来显式配置。 比如修改文件名成`ttl-foo-name-changed.jar`,则还需要加上`Java`的启动参数:`-Xbootclasspath/a:path/to/ttl-foo-name-changed.jar`。 - 或使用`v2.6.0`之前的版本(如`v2.5.1`),则也需要自己手动通过`-Xbootclasspath JVM`参数来显式配置(就像`TTL`之前的版本的做法一样)。 加上`Java`的启动参数:`-Xbootclasspath/a:path/to/transmittable-thread-local-2.5.1.jar`。 `Java`命令行示例如下: ```bash java -javaagent:path/to/transmittable-thread-local-2.x.y.jar \ -cp classes \ com.alibaba.demo.ttl.agent.AgentDemo # 如果修改了TTL jar文件名 或 TTL版本是 2.6.0 之前 # 则还需要显式设置 -Xbootclasspath 参数 java -javaagent:path/to/ttl-foo-name-changed.jar \ -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar \ -cp classes \ com.alibaba.demo.ttl.agent.AgentDemo java -javaagent:path/to/transmittable-thread-local-2.5.1.jar \ -Xbootclasspath/a:path/to/transmittable-thread-local-2.5.1.jar \ -cp classes \ com.alibaba.demo.ttl.agent.AgentDemo ```
关于boot class path 的 展开说明

因为修饰了JDK标准库的类,标准库由bootstrap class loader加载;修饰后的JDK类引用了TTL的代码,所以Java Agent使用方式下TTL Jar文件需要配置到boot class path上。

TTLv2.6.0开始,加载TTL Agent时会自动设置TTL Jarboot class path上。
注意:不能修改从Maven库下载的TTL Jar文件名(形如transmittable-thread-local-2.x.y.jar)。 如果修改了,则需要自己手动通过-Xbootclasspath JVM参数来显式配置(就像TTL之前的版本的做法一样)。

自动设置TTL Jarboot class path的实现是通过指定TTL Java Agent Jar文件里manifest文件(META-INF/MANIFEST.MF)的Boot-Class-Path属性:

Boot-Class-Path

A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed.

更多详见

# 🔌 Java API Docs 当前版本的Java API文档地址: # 🍪 Maven依赖 示例: ```xml com.alibaba transmittable-thread-local 2.14.4 ``` 可以在 [maven.org](https://repo1.maven.org/maven2/com/alibaba/transmittable-thread-local/maven-metadata.xml) 查看可用的版本。 # 🔨 关于编译构建 编译构建的环境要求: **_`JDK 8+`_**;用`Maven`常规的方式执行编译构建即可: \# 在工程中已经包含了符合版本要求的`Maven`,直接运行 **_工程根目录下的`mvnw`_**;并不需要先手动自己安装好`Maven`。 ```bash # 运行测试Case ./mvnw test # 编译打包 ./mvnw package # 运行测试Case、编译打包、安装TTL库到Maven本地 ./mvnw install ##################################################### # 如果使用你自己安装的 maven,版本要求:maven 3.3.9+ mvn install ``` # ❓ FAQ **_Q1. `TTL Agent`与其它`Agent`(如`Skywalking`、`Promethues`)配合使用时不生效?_** 配置`TTL Agent`在最前的位置,可以避免与其它其它`Agent`配合使用时,`TTL Agent`可能的不生效问题。配置示例: ```bash java -javaagent:path/to/transmittable-thread-local-2.x.y.jar \ -javaagent:path/to/skywalking-agent.jar \ -jar your-app.jar ``` 原因是: - 像`Skywalking`这样的`Agent`的入口逻辑(`premain`)包含了线程池的启动。 - 如果配置在这样的`Agent`配置在前面,到了`TTL Agent`(的`premain`)时,`TTL`需要加强的线程池类已经加载(`load`)了。 - `TTL Agent`的`TtlTransformer`是在类加载时触发类的增强;如果类已经加载了会跳过`TTL Agent`的增强逻辑。 更多讨论参见 [Issue:`TTL agent`与其他`Agent`的兼容性问题 #226](https://github.com/alibaba/transmittable-thread-local/issues/226)。 **_Q2. `MacOS`下,使用`Java Agent`,可能会报`JavaLaunchHelper`的出错信息_** JDK Bug: 可以换一个版本的`JDK`。我的开发机上`1.7.0_40`有这个问题,`1.6.0_51`、`1.7.0_45`可以运行。 \# `1.7.0_45`还是有`JavaLaunchHelper`的出错信息,但不影响运行。 # ✨ 使用`TTL`的好处与必要性 > [!NOTE] > 不读这一节,并不会影响你使用`TTL`来解决你碰到的问题,可以放心跳过;读了 [User Guide](#-user-guide) 就可以快速用起来了~ 😄 这一节信息密度较高不易读。 **_好处:透明且自动完成所有异步执行上下文的可定制、规范化的捕捉与传递。_** 这个好处也是`TransmittableThreadLocal`的目标。 **_必要性:随着应用的分布式微服务化并使用各种中间件,越来越多的功能与组件会涉及不同的上下文,逻辑流程也越来越长;上下文问题实际上是个大的易错的架构问题,需要统一的对业务透明的解决方案。_** 使用`ThreadLocal`作为业务上下文传递的经典技术手段在中间件、技术与业务框架中广泛大量使用。而对于生产应用,几乎一定会使用线程池等异步执行组件,以高效支撑线上大流量。但使用`ThreadLocal`及其`set/remove`的上下文传递模式,在使用线程池等异步执行组件时,存在多方面的问题: **_1. 从业务使用者角度来看_** 1. **繁琐** - 业务逻辑要知道:有哪些上下文;各个上下文是如何获取的。 - 并需要业务逻辑去一个一个地捕捉与传递。 1. **依赖** - 需要直接依赖不同`ThreadLocal`上下文各自的获取的逻辑或类。 - 像`RPC`的上下文(如`Dubbo`的`RpcContext`)、全链路跟踪的上下文(如`SkyWalking`的`ContextManager`)、不同业务模块中的业务流程上下文,等等。 1. **静态(易漏)** - 因为要 **_事先_** 知道有哪些上下文,如果系统出现了一个新的上下文,业务逻辑就要修改添加上新上下文传递的几行代码。也就是说因 **_系统的_** 上下文新增,**_业务的_** 逻辑就跟进要修改。 - 而对于业务来说,不关心系统的上下文,即往往就可能遗漏,会是线上故障了。 - 随着应用的分布式微服务化并使用各种中间件,越来越多的功能与组件会涉及不同的上下文,逻辑流程也越来越长;上下文问题实际上是个大的易错的架构问题,需要统一的对业务透明的解决方案。 1. **定制性** - 因为需要业务逻辑来完成捕捉与传递,业务要关注『上下文的传递方式』:直接传引用?还是拷贝传值?拷贝是深拷贝还是浅拷贝?在不同的上下文会需要不同的做法。 - 『上下文的传递方式』往往是 **_上下文的提供者_**(或说是业务逻辑的框架部分)才能决策处理好的;而 **_上下文的使用者_**(或说是业务逻辑的应用部分)往往不(期望)知道上下文的传递方式。这也可以理解成是 **_依赖_**,即业务逻辑 依赖/关注/实现了 系统/架构的『上下文的传递方式』。 **_2. 从整体流程实现角度来看_** 关注的是 **上下文传递流程的规范化**。上下文传递到了子线程要做好 **_清理_**(或更准确地说是要 **_恢复_** 成之前的上下文),需要业务逻辑去处理好。如果业务逻辑对**清理**的处理不正确,比如: - 如果清理操作漏了: - 下一次执行可能是上次的,即『上下文的 **_污染_**/**_串号_**』,会导致业务逻辑错误。 - 『上下文的 **_泄漏_**』,会导致内存泄漏问题。 - 如果清理操作做多了,会出现上下文 **_丢失_**。 上面的问题,在业务开发中引发的`Bug`真是**屡见不鲜** !本质原因是:**_`ThreadLocal`的`set/remove`的上下文传递模式_** 在使用线程池等异步执行组件的情况下不再是有效的。常见的典型例子: - 当线程池满了且线程池的`RejectedExecutionHandler`使用的是`CallerRunsPolicy`时,提交到线程池的任务会在提交线程中直接执行,`ThreadLocal.remove`操作**清理**提交线程的上下文导致上下文**丢失**。 - 类似的,使用`ForkJoinPool`(包含并行执行`Stream`与`CompletableFuture`,底层使用`ForkJoinPool`)的场景,展开的`ForkJoinTask`会在任务提交线程中直接执行。同样导致上下文**丢失**。 怎么设计一个『上下文传递流程』方案(即上下文的生命周期),以**保证**没有上面的问题? 期望:上下文生命周期的操作从业务逻辑中分离出来。业务逻辑不涉及生命周期,就不会有业务代码如疏忽清理而引发的问题了。整个上下文的传递流程或说生命周期可以规范化成:捕捉、回放和恢复这3个操作,即[**_`CRR(capture/replay/restore)`模式_**](docs/developer-guide.md#-%E6%A1%86%E6%9E%B6%E4%B8%AD%E9%97%B4%E4%BB%B6%E9%9B%86%E6%88%90ttl%E4%BC%A0%E9%80%92)。更多讨论参见 [Issue:能在详细讲解一下`replay`、`restore`的设计理念吗?#201](https://github.com/alibaba/transmittable-thread-local/issues/201)。 总结上面的说明:在生产应用(几乎一定会使用线程池等异步执行组件)中,使用`ThreadLocal`及其`set/remove`的上下文传递模式**几乎一定是有问题的**,**_只是在等一个出`Bug`的机会_**。 更多`TTL`好处与必要性的展开讨论参见 [Issue:这个库带来怎样的好处和优势? #128](https://github.com/alibaba/transmittable-thread-local/issues/128),欢迎继续讨论 ♥️ # 🗿 更多文档 - [🎨 需求场景说明](docs/requirement-scenario.md) - [❤️ 小伙伴同学们写的`TTL`使用场景 与 设计实现解析的文章(写得都很好!) - Issue #123](https://github.com/alibaba/transmittable-thread-local/issues/123) - [🎓 Developer Guide](docs/developer-guide.md) - [☔ 性能测试](docs/performance-test.md) # 📚 相关资料 ## JDK Core Classes - [WeakHashMap](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/WeakHashMap.html) - [InheritableThreadLocal](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html) # 💗 Who Used 使用了`TTL`的一部分开源项目: - **中间件** - [`sofastack/sofa-rpc` ![](https://img.shields.io/github/stars/sofastack/sofa-rpc.svg?style=social&label=Star)](https://github.com/sofastack/sofa-rpc) [![star](https://gitee.com/sofastack/sofa-rpc/badge/star.svg?theme=gray)](https://gitee.com/sofastack/sofa-rpc) SOFARPC is a high-performance, high-extensibility, production-level Java RPC framework - [`trpc-group/trpc-java` ![](https://img.shields.io/github/stars/trpc-group/trpc-java.svg?style=social&label=Star)](https://github.com/trpc-group/trpc-java) A pluggable, high-performance RPC framework written in java - [`tencentmusic/supersonic` ![](https://img.shields.io/github/stars/tencentmusic/supersonic.svg?style=social&label=Star)](https://github.com/tencentmusic/supersonic) SuperSonic is an out-of-the-box yet highly extensible framework for building ChatBI - [`dromara/hmily` ![](https://img.shields.io/github/stars/dromara/hmily.svg?style=social&label=Star)](https://github.com/dromara/hmily) [![star](https://gitee.com/dromara/hmily/badge/star.svg?theme=gray)](https://gitee.com/dromara/hmily) Distributed transaction solutions - [`dromara/gobrs-async` ![](https://img.shields.io/github/stars/dromara/gobrs-async.svg?style=social&label=Star)](https://github.com/dromara/gobrs-async) [![star](https://gitee.com/dromara/gobrs-async/badge/star.svg?theme=gray)](https://gitee.com/dromara/gobrs-async) 一款功能强大、配置灵活、带有全链路异常回调、内存优化、异常状态管理于一身的高性能异步编排框架。为企业提供在复杂应用场景下动态任务编排的能力。 针对于复杂场景下,异步线程复杂性、任务依赖性、异常状态难控制性 - [`dromara/dynamic-tp` ![](https://img.shields.io/github/stars/dromara/dynamic-tp.svg?style=social&label=Star)](https://github.com/dromara/dynamic-tp) [![star](https://gitee.com/dromara/dynamic-tp/badge/star.svg?theme=gray)](https://gitee.com/dromara/dynamic-tp) 轻量级动态线程池,内置监控告警功能,支持线程池上下文传递,基于主流配置中心(已支持Nacos、Apollo,Zookeeper,可通过SPI自定义实现) - [`opengoofy/hippo4j` ![](https://img.shields.io/github/stars/opengoofy/hippo4j.svg?style=social&label=Star)](https://github.com/opengoofy/hippo4j) [![star](https://gitee.com/opengoofy/hippo4j/badge/star.svg?theme=gray)](https://gitee.com/magestack/hippo4j) 动态线程池框架,附带监控报警功能,支持 JDK、Tomcat、Jetty、Undertow 线程池;Apache RocketMQ、Dubbo、RabbitMQ、Hystrix 消费等线程池。内置两种使用模式:轻量级依赖配置中心以及无中间件依赖版本 - [`siaorg/sia-gateway` ![](https://img.shields.io/github/stars/siaorg/sia-gateway.svg?style=social&label=Star)](https://github.com/siaorg/sia-gateway) 微服务路由网关(zuul-plus) - [`huaweicloud/Sermant` ![](https://img.shields.io/github/stars/huaweicloud/Sermant.svg?style=social&label=Star)](https://github.com/huaweicloud/Sermant) Sermant, a proxyless service mesh solution based on Javaagent - [`ZTO-Express/zms` ![](https://img.shields.io/github/stars/ZTO-Express/zms.svg?style=social&label=Star)](https://github.com/ZTO-Express/zms) [![star](https://gitee.com/zto_express/zms/badge/star.svg?theme=gray)](https://gitee.com/zto_express/zms) ZTO Message Service - [`lxchinesszz/tomato` ![](https://img.shields.io/github/stars/lxchinesszz/tomato.svg?style=social&label=Star)](https://github.com/lxchinesszz/tomato) 一款专门为SpringBoot项目设计的幂等组件 - [`ytyht226/taskflow` ![](https://img.shields.io/github/stars/ytyht226/taskflow.svg?style=social&label=Star)](https://github.com/ytyht226/taskflow) 一款轻量、简单易用、可灵活扩展的通用任务编排框架,基于有向无环图(DAG)的方式实现,框架提供了组件复用、同步/异步编排、条件判断、分支选择等能力,可以根据不同的业务场景对任意的业务流程进行编排 - [`foldright/cffu` ![](https://img.shields.io/github/stars/foldright/cffu.svg?style=social&label=star)](https://github.com/foldright/cffu) 🦝 Java CompletableFuture Fu, aka. CF-Fu, pronounced "Shifu"; include best practice/traps guide and a tiny sidekick library to improve user experience and reduce misuse. - [`tuya/connector` ![](https://img.shields.io/github/stars/tuya/connector.svg?style=social&label=Star)](https://github.com/tuya/connector) The connector framework maps cloud APIs to local APIs based on simple configurations and flexible extension mechanisms - **中间件/数据处理** - [`apache/shardingsphere` ![](https://img.shields.io/github/stars/apache/shardingsphere.svg?style=social&label=Star)](https://github.com/apache/shardingsphere) [![star](https://gitee.com/Sharding-Sphere/sharding-sphere/badge/star.svg?theme=gray)](https://gitee.com/Sharding-Sphere/sharding-sphere) Ecosystem to transform any database into a distributed database system, and enhance it with sharding, elastic scaling, encryption features & more - [`apache/kylin` ![](https://img.shields.io/github/stars/apache/kylin.svg?style=social&label=Star)](https://github.com/apache/kylin) A unified and powerful OLAP platform for Hadoop and Cloud. - [`mybatis-flex/mybatis-flex` ![](https://img.shields.io/github/stars/mybatis-flex/mybatis-flex.svg?style=social&label=Star)](https://github.com/mybatis-flex/mybatis-flex) [![star](https://gitee.com/mybatis-flex/mybatis-flex/badge/star.svg?theme=gray)](https://gitee.com/mybatis-flex/mybatis-flex) mybatis-flex is an elegant Mybatis Enhancement Framework - [`basicai/xtreme1` ![](https://img.shields.io/github/stars/basicai/xtreme1.svg?style=social&label=Star)](https://github.com/basicai/xtreme1) The Next GEN Platform for Multisensory Training Data. #3D annotation, lidar-camera annotation and image annotation tools are supported - [`oceanbase/odc` ![](https://img.shields.io/github/stars/oceanbase/odc.svg?style=social&label=Star)](https://github.com/oceanbase/odc) An open-source, enterprise-grade database tool for collaborative development - [`sagframe/sagacity-sqltoy` ![](https://img.shields.io/github/stars/sagframe/sagacity-sqltoy.svg?style=social&label=Star)](https://github.com/sagframe/sagacity-sqltoy) Java真正智慧的ORM框架 - [`dromara/stream-query` ![](https://img.shields.io/github/stars/dromara/stream-query.svg?style=social&label=Star)](https://github.com/dromara/stream-query) [![star](https://gitee.com/dromara/stream-query/badge/star.svg?theme=gray)](https://gitee.com/dromara/stream-query) 允许完全摆脱Mapper的mybatis-plus体验;可以使用类似“工具类”这样的静态函数进行数据库操作 - [`luo-zhan/Transformer` ![](https://img.shields.io/github/stars/luo-zhan/Transformer.svg?style=social&label=Star)](https://github.com/luo-zhan/Transformer) Transformer可能是最简单,但最强大的字段转换插件,一个注解搞定任意转换,让开发变得更加丝滑 - [`SimonAlong/Neo` ![](https://img.shields.io/github/stars/SimonAlong/Neo.svg?style=social&label=Star)](https://github.com/SimonAlong/Neo) Orm框架:基于ActiveRecord思想开发的至简化且功能很全的Orm框架 - [`ppdaicorp/das` ![](https://img.shields.io/github/stars/ppdaicorp/das.svg?style=social&label=Star)](https://github.com/ppdaicorp/das) 数据库访问框架(data access service),包括数据库控制台das console,数据库客户端das client和数据库服务端das server三部分 - [`didi/ALITA` ![](https://img.shields.io/github/stars/didi/ALITA.svg?style=social&label=Star)](https://github.com/didi/ALITA) a layer-based data analysis tool - [`didi/daedalus` ![](https://img.shields.io/github/stars/didi/daedalus.svg?style=social&label=Star)](https://github.com/didi/daedalus) 实现快速创建数据构造流程,数据构造流程的可视化、线上化、持久化、标准化 - **中间件/流程引擎** - [`dromara/liteflow` ![](https://img.shields.io/github/stars/dromara/liteflow.svg?style=social&label=Star)](https://github.com/dromara/liteflow) [![star](https://gitee.com/dromara/liteFlow/badge/star.svg?theme=gray)](https://gitee.com/dromara/liteFlow) a lightweight and practical micro-process framework - [`alibaba/bulbasaur` ![](https://img.shields.io/github/stars/alibaba/bulbasaur.svg?style=social&label=Star)](https://github.com/alibaba/bulbasaur) A pluggable, scalable process engine - **中间件/日志** - [`dromara/TLog` ![](https://img.shields.io/github/stars/dromara/TLog.svg?style=social&label=Star)](https://github.com/dromara/TLog) [![star](https://gitee.com/dromara/TLog/badge/star.svg?theme=gray)](https://gitee.com/dromara/TLog) Lightweight distributed log label tracking framework - [`fayechenlong/plumelog` ![](https://img.shields.io/github/stars/fayechenlong/plumelog.svg?style=social&label=Star)](https://github.com/fayechenlong/plumelog) [![star](https://gitee.com/plumeorg/plumelog/badge/star.svg?theme=gray)](https://gitee.com/plumeorg/plumelog) 一个java分布式日志组件,支持百亿级别 - [`minbox-projects/minbox-logging` ![](https://img.shields.io/github/stars/minbox-projects/minbox-logging.svg?style=social&label=Star)](https://github.com/minbox-projects/minbox-logging) [![star](https://gitee.com/minbox-projects/minbox-logging/badge/star.svg?theme=gray)](https://gitee.com/minbox-projects/minbox-logging) 分布式零侵入式、链路式请求日志分析框架。提供Admin端点进行采集日志、分析日志、日志告警通知、服务性能分析等。通过Admin Ui可查看实时链路日志信息、在线业务服务列表 - [`minbox-projects/api-boot` ![](https://img.shields.io/github/stars/minbox-projects/api-boot.svg?style=social&label=Star)](https://github.com/minbox-projects/api-boot) [![star](https://gitee.com/minbox-projects/api-boot/badge/star.svg?theme=gray)](https://gitee.com/minbox-projects/api-boot) 为接口服务而生的,基于“ SpringBoot”完成扩展和自动配置,内部封装了一系列的开箱即用Starters - [`ofpay/logback-mdc-ttl` ![](https://img.shields.io/github/stars/ofpay/logback-mdc-ttl.svg?style=social&label=Star)](https://github.com/ofpay/logback-mdc-ttl) logback扩展,集成transmittable-thread-local支持跨线程池的mdc跟踪 - [`oldratlee/log4j2-ttl-thread-context-map` ![](https://img.shields.io/github/stars/oldratlee/log4j2-ttl-thread-context-map.svg?style=social&label=Star)](https://github.com/oldratlee/log4j2-ttl-thread-context-map) Log4j2 TTL ThreadContextMap, Log4j2 extension integrated TransmittableThreadLocal to MDC - [`qqxx6661/log-record` ![](https://img.shields.io/github/stars/qqxx6661/log-record.svg?style=social&label=Star)](https://github.com/qqxx6661/log-record) 业务日志记录框架,使用注解优雅记录日志,支持SpEL表达式,自定义上下文,自定义函数,实体类DIFF等特性。 - **中间件/字节码** - [`ymm-tech/easy-byte-coder` ![](https://img.shields.io/github/stars/ymm-tech/easy-byte-coder.svg?style=social&label=Star)](https://github.com/ymm-tech/easy-byte-coder) Easy-byte-coder is a non-invasive bytecode injection framework based on JVM - **业务服务或平台应用** - [`OpenBankProject/OBP-API` ![](https://img.shields.io/github/stars/OpenBankProject/OBP-API.svg?style=social&label=Star)](https://github.com/OpenBankProject/OBP-API) An open source RESTful API platform for banks that supports Open Banking, XS2A and PSD2 through access to accounts, transactions, counterparties, payments, entitlements and metadata - plus a host of internal banking and management APIs - [`gz-yami/mall4j` ![](https://img.shields.io/github/stars/gz-yami/mall4j.svg?style=social&label=Star)](https://github.com/gz-yami/mall4j) [![star](https://gitee.com/gz-yami/mall4j/badge/star.svg?theme=gray)](https://gitee.com/gz-yami/mall4j) 电商商城 java电商商城系统 uniapp商城 多用户商城 - [`Joolun/JooLun-wx` ![](https://img.shields.io/github/stars/Joolun/JooLun-wx.svg?style=social&label=Star)](https://github.com/Joolun/JooLun-wx) [![star](https://gitee.com/joolun/JooLun-wx/badge/star.svg?theme=gray)](https://gitee.com/joolun/JooLun-wx) JooLun微信商城 - [`HummerRisk/HummerRisk` ![](https://img.shields.io/github/stars/HummerRisk/HummerRisk.svg?style=social&label=Star)](https://github.com/HummerRisk/HummerRisk) 云原生安全平台,包括混合云安全治理和容器云安全检测 - [`XiaoMi/mone` ![](https://img.shields.io/github/stars/XiaoMi/mone.svg?style=social&label=Star)](https://github.com/XiaoMi/mone) `Mone`以微服务为核心的一站式企业协同研发平台。支持公共云、专有云和混合云多种部署形态;提供从“项目创建->开发->部署->治理->应用观测”端到端的研发全流程服务;通过云原生新技术和研发新模式,打造“双敏”,敏捷研发和敏捷组织,保障小米-中国区高复杂业务、大规模团队的敏捷研发协同,实现多倍效能提升。 - [`yangzongzhuan/RuoYi-Cloud` ![](https://img.shields.io/github/stars/yangzongzhuan/RuoYi-Cloud.svg?style=social&label=Star)](https://github.com/yangzongzhuan/RuoYi-Cloud) [![star](https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=gray)](https://gitee.com/y_project/RuoYi-Cloud) 基于Spring Boot、Spring Cloud & Alibaba的分布式微服务架构权限管理系统 - [`somowhere/albedo` ![](https://img.shields.io/github/stars/somowhere/albedo.svg?style=social&label=Star)](https://github.com/somowhere/albedo) [![star](https://gitee.com/somowhere/albedo/badge/star.svg?theme=gray)](https://gitee.com/somowhere/albedo) 基于 Spring Boot 、Spring Security、Mybatis 的RBAC权限管理系统 - [`qwdigital/LinkWechat` ![](https://img.shields.io/github/stars/qwdigital/LinkWechat.svg?style=social&label=Star)](https://github.com/qwdigital/LinkWechat) [![star](https://gitee.com/LinkWeChat/link-wechat/badge/star.svg?theme=gray)](https://gitee.com/LinkWeChat/link-wechat) 基于企业微信的开源 SCRM 系统,采用主流的 Java 微服务架构,是企业私域流量管理与营销的综合解决方案,助力企业提高客户运营效率,强化营销能力,拓展盈利空间 - [`fushengqian/fuint` ![](https://img.shields.io/github/stars/fushengqian/fuint.svg?style=social&label=Star)](https://github.com/fushengqian/fuint) [![star](https://gitee.com/fuint/fuint-uniapp/badge/star.svg?theme=gray)](https://gitee.com/fuint/fuint-uniapp) fuint会员营销系统是一套开源的实体店铺会员管理和营销系统 - [`hiparker/opsli-boot` ![](https://img.shields.io/github/stars/hiparker/opsli-boot.svg?style=social&label=Star)](https://github.com/hiparker/opsli-boot) [![star](https://gitee.com/hiparker/opsli-boot/badge/star.svg?theme=gray)](https://gitee.com/hiparker/opsli-boot) 一款的低代码快速平台,零代码开发,致力于做更简洁的后台管理系统 - [`topiam/eiam` ![](https://img.shields.io/github/stars/topiam/eiam.svg?style=social&label=Star)](https://github.com/topiam/eiam) [![star](https://gitee.com/topiam/eiam/badge/star.svg?theme=gray)](https://gitee.com/topiam/eiam) EIAM(Employee Identity and Access Management Program)企业级开源IAM平台,实现用户全生命周期的管理、统一认证和单点登录、为数字身份安全赋能 - [`Newspiral/newspiral-business` ![](https://img.shields.io/github/stars/Newspiral/newspiral-business.svg?style=social&label=Star)](https://github.com/Newspiral/newspiral-business) 联盟区块链底层平台 - **工具产品** - [`ssssssss-team/spider-flow` ![](https://img.shields.io/github/stars/ssssssss-team/spider-flow.svg?style=social&label=Star)](https://github.com/ssssssss-team/spider-flow) [![star](https://gitee.com/ssssssss-team/spider-flow/badge/star.svg?theme=gray)](https://gitee.com/ssssssss-team/spider-flow) 新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫 - [`nekolr/slime` ![](https://img.shields.io/github/stars/nekolr/slime.svg?style=social&label=Star)](https://github.com/nekolr/slime) 🍰 一个可视化的爬虫平台 - [`Jackson0714/PassJava-Platform` ![](https://img.shields.io/github/stars/Jackson0714/PassJava-Platform.svg?style=social&label=Star)](https://github.com/Jackson0714/PassJava-Platform) 一款面试刷题的 Spring Cloud 开源系统。零碎时间利用小程序查看常见面试题,夯实Java基础。 该项目可以教会你如何搭建SpringBoot项目,Spring Cloud项目。 采用流行的技术,如 SpringBoot、MyBatis、Redis、 MySql、 MongoDB、 RabbitMQ、Elasticsearch,采用Docker容器化部署 - [`martin-chips/DimpleBlog` ![](https://img.shields.io/github/stars/martin-chips/DimpleBlog.svg?style=social&label=Star)](https://github.com/martin-chips/DimpleBlog) 基于`SpringBoot2`搭建的个人博客系统 - [`zjcscut/octopus` ![](https://img.shields.io/github/stars/zjcscut/octopus.svg?style=social&label=Star)](https://github.com/zjcscut/octopus) 长链接压缩为短链接的服务 - [`xggz/mqr` ![](https://img.shields.io/github/stars/xggz/mqr.svg?style=social&label=Star)](https://github.com/xggz/mqr) [![star](https://gitee.com/mlyai/mqr/badge/star.svg?theme=gray)](https://gitee.com/mlyai/mqr) 茉莉QQ机器人(简称MQR),采用mirai的Android协议实现的QQ机器人服务,通过web控制机器人的启停和配置 - **测试解决方案或工具** - [`alibaba/jvm-sandbox-repeater` ![](https://img.shields.io/github/stars/alibaba/jvm-sandbox-repeater.svg?style=social&label=Star)](https://github.com/alibaba/jvm-sandbox-repeater) A Java server-side recording and playback solution based on JVM-Sandbox, 录制/回放通用解决方案 - [`vivo/MoonBox` ![](https://img.shields.io/github/stars/vivo/MoonBox.svg?style=social&label=Star)](https://github.com/vivo/MoonBox) Moonbox(月光宝盒)是JVM-Sandbox生态下的,基于jvm-sandbox-repeater重新开发的,一款流量回放平台产品。相较于jvm-sandbox-repeater,Moonbox功能更加丰富、数据可靠性更高,同时便于快速线上部署和使用 - [`alibaba/testable-mock` ![](https://img.shields.io/github/stars/alibaba/testable-mock.svg?style=social&label=Star)](https://github.com/alibaba/testable-mock) 换种思路写Mock,让单元测试更简单 - [`shulieTech/Takin` ![](https://img.shields.io/github/stars/shulieTech/Takin.svg?style=social&label=Star)](https://github.com/shulieTech/Takin) 全链路压测平台,measure online environmental performance test for full-links, Especially for microservices - [`shulieTech/LinkAgent` ![](https://img.shields.io/github/stars/shulieTech/LinkAgent.svg?style=social&label=Star)](https://github.com/shulieTech/LinkAgent) a Java-based open-source agent designed to collect data and control Functions for Java applications through JVM bytecode, without modifying applications codes - [`alibaba/virtual-environment` ![](https://img.shields.io/github/stars/alibaba/virtual-environment.svg?style=social&label=Star)](https://github.com/alibaba/virtual-environment) Route isolation with service sharing, 阿里测试环境服务隔离和联调机制的`Kubernetes`版实现 - **`Spring Cloud`/`Spring Boot`的框架方案/脚手架** - [`YunaiV/ruoyi-vue-pro` ![](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Star)](https://github.com/YunaiV/ruoyi-vue-pro) [![star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=gray)](https://gitee.com/zhijiantianya/ruoyi-vue-pro) 一套全部开源的企业级的快速开发平台。基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 微信小程序,支持 RBAC 动态权限、数据权限、SaaS 多租户、Activiti + Flowable 工作流、三方登录、支付、短信、商城等功能 - [`YunaiV/yudao-cloud` ![](https://img.shields.io/github/stars/YunaiV/yudao-cloud.svg?style=social&label=Star)](https://github.com/YunaiV/yudao-cloud) [![star](https://gitee.com/zhijiantianya/yudao-cloud/badge/star.svg?theme=gray)](https://gitee.com/zhijiantianya/yudao-cloud) RuoYi-Vue 全新 Cloud 版本,优化重构所有功能。基于 Spring Cloud Alibaba + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能 - [`zlt2000/microservices-platform` ![](https://img.shields.io/github/stars/zlt2000/microservices-platform.svg?style=social&label=Star)](https://github.com/zlt2000/microservices-platform) [![star](https://gitee.com/zlt2000/microservices-platform/badge/star.svg?theme=gray)](https://gitee.com/zlt2000/microservices-platform) 基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba并采用前后端分离的企业级微服务多租户系统架构 - [`dromara/lamp-cloud` ![](https://img.shields.io/github/stars/dromara/lamp-cloud.svg?style=social&label=Star)](https://github.com/zuihou/lamp-cloud) [![star](https://gitee.com/dromara/lamp-cloud/badge/star.svg?theme=gray)](https://gitee.com/dromara/lamp-cloud) 基于Jdk11 + SpringCloud + SpringBoot 的微服务快速开发平台,其中的可配置的SaaS功能尤其闪耀, 具备RBAC功能、网关统一鉴权、Xss防跨站攻击、自动代码生成、多种存储系统、分布式事务、分布式定时任务等多个模块,支持多业务系统并行开发, 支持多服务并行开发,可以作为后端服务的开发脚手架 - [`zuihou/lamp-util` ![](https://img.shields.io/github/stars/zuihou/lamp-util.svg?style=social&label=Star)](https://github.com/zuihou/lamp-util) [![star](https://gitee.com/zuihou111/lamp-util/badge/star.svg?theme=gray)](https://gitee.com/zuihou111/lamp-util) 打造一套兼顾 SpringBoot 和 SpringCloud 项目的公共工具类 - [`matevip/matecloud` ![](https://img.shields.io/github/stars/matevip/matecloud.svg?style=social&label=Star)](https://github.com/matevip/matecloud) [![star](https://gitee.com/matevip/matecloud/badge/star.svg?theme=gray)](https://gitee.com/matevip/matecloud) 一款基于Spring Cloud Alibaba的微服务架构 - [`gavenwangcn/vole` ![](https://img.shields.io/github/stars/gavenwangcn/vole.svg?style=social&label=Star)](https://github.com/gavenwangcn/vole) SpringCloud 微服务业务脚手架 - [`liuweijw/fw-cloud-framework` ![](https://img.shields.io/github/stars/liuweijw/fw-cloud-framework.svg?style=social&label=Star)](https://github.com/liuweijw/fw-cloud-framework) [![star](https://gitee.com/liuweijw/fw-cloud-framework/badge/star.svg?theme=gray)](https://gitee.com/liuweijw/fw-cloud-framework) 基于springcloud全家桶开发分布式框架(支持oauth2认证授权、SSO登录、统一下单、微信公众号服务、Shardingdbc分库分表、常见服务监控、链路监控、异步日志、redis缓存等功能),实现基于Vue全家桶等前后端分离项目工程 - [`liuht777/Taroco` ![](https://img.shields.io/github/stars/liuht777/Taroco.svg?style=social&label=Star)](https://github.com/liuht777/Taroco) 整合Nacos、Spring Cloud Alibaba,提供了一系列starter组件, 同时提供服务治理、服务监控、OAuth2权限认证,支持服务降级/熔断、服务权重 - [`mingyang66/spring-parent` ![](https://img.shields.io/github/stars/mingyang66/spring-parent.svg?style=social&label=Star)](https://github.com/mingyang66/spring-parent) 数据库多数据源、Redis多数据源、日志组件、全链路日志追踪、埋点扩展点、Netty、微服务、开发基础框架支持、异常统一处理、返回值、跨域、API路由、监控等 - [`budwk/budwk` ![](https://img.shields.io/github/stars/budwk/budwk.svg?style=social&label=Star)](https://github.com/budwk/budwk) [![star](https://gitee.com/budwk/budwk/badge/star.svg?theme=gray)](https://gitee.com/budwk/budwk) `BudWk` 原名 [`NutzWk` ![](https://img.shields.io/github/stars/Wizzercn/NutzWk.svg?style=social&label=Star)](https://github.com/Wizzercn/NutzWk) [![star](https://gitee.com/wizzer/NutzWk/badge/star.svg?theme=gray)](https://gitee.com/wizzer/NutzWk),基于国产框架 nutz 及 nutzboot 开发的开源Web基础项目,集权限体系、系统参数、数据字典、站内消息、定时任务、CMS、微信等最常用功能,不庞杂、不面面俱到,使其具有上手容易、开发便捷、扩展灵活等特性,特别适合各类大中小型定制化项目需求 - [`yinjihuan/spring-cloud` ![](https://img.shields.io/github/stars/yinjihuan/spring-cloud.svg?style=social&label=Star)](https://github.com/yinjihuan/spring-cloud) 《Spring Cloud微服务-全栈技术与案例解析》和《Spring Cloud微服务 入门 实战与进阶》配套源码 - [`louyanfeng25/ddd-demo` ![](https://img.shields.io/github/stars/louyanfeng25/ddd-demo.svg?style=social&label=Star)](https://github.com/louyanfeng25/ddd-demo) 《深入浅出DDD》讲解的演示项目,为了能够更好的理解Demo中的分层与逻辑处理,我强烈建议你配合小册来深入了解DDD - [`nageoffer/12306` ![](https://img.shields.io/github/stars/nageoffer/12306.svg?style=social&label=Star)](https://github.com/nageoffer/12306) 12306 铁路购票服务是与大家生活和出行相关的关键系统,包括会员、购票、订单、支付和网关等服务。 更多使用`TTL`的开源项目 参见 [![user repos](https://badgen.net/github/dependents-repo/alibaba/transmittable-thread-local?label=user%20repos)](https://github.com/alibaba/transmittable-thread-local/network/dependents) # 👷 Contributors - Jerry Lee \ [@oldratlee](https://github.com/oldratlee) - Yang Fang \ [@driventokill](https://github.com/driventokill) - Zava Xu \ [@zavakid](https://github.com/zavakid) - wuwen \ [@wuwen5](https://github.com/wuwen5) - rybalkinsd \ [@rybalkinsd](https://github.com/rybalkinsd) - David Dai \<351450944 at qq dot com> [@LNAmp](https://github.com/LNAmp) - Your name here :-) [![GitHub Contributors](https://contrib.rocks/image?repo=alibaba/transmittable-thread-local)](https://github.com/alibaba/transmittable-thread-local/graphs/contributors) ================================================ FILE: SECURITY.md ================================================ # 漏洞奖励计划 ## 报告 如果您认为自己在本程序中发现了任何安全(技术)漏洞,欢迎您通过 https://security.alibaba.com 向我们提交漏洞报告。 如果您报告任何安全漏洞,请注意您可能包含以下信息(合格报告): * `git`程序`URL`地址,运行的环境 * 包含必要屏幕截图的详细说明 * 重现漏洞的步骤以及修复漏洞的建议。 * 其他有用信息 ## 处理 ASRC(Alibaba Security Response Center 阿里安全响应中心)将尽快审核并回复您的提交内容,并在我们努力修复您提交的漏洞时随时通知您。如有必要,我们可能会与您联系以获取更多信息。 ## 条款和条件 1. 仅接受技术漏洞并对其进行评级 2. 出于安全原因,上报者同意与ASRC合作完成他/她提交的漏洞,不向任何第三方透露任何漏洞信息 3. 如果不止一个人报告相同的安全漏洞,奖励将给予完成合格报告的第一个人 4. 为了保护程序的用户,请在修复之前不要直接提交`git`的`issue`,也不要在社区讨论任何漏洞信息 5. 所有奖励和声誉积分将提供给仅向ASRC提交其安全漏洞的上报者 6. 安全漏洞奖励的解释权利归ASRC所有 ## 收集范围 我们的主要收集漏洞类别是: * 服务器端请求伪造(`SSRF`) * `SQL`注入 * 拒绝服务攻击 * 远程执行代码(`RCE`) * XML外部实体攻击(`XXE`) * 访问控制问题(不安全的直接对象参考问题等) * 敏感目录遍历问题 * 本地文件读取(`LFD`) * 敏感信息泄露(密钥、`Cookie`、`Session`等) ## 奖励 * 可直接导致严重问题的每个漏洞奖励7000元人民币 * 存在限制及需要一定特殊环境下才能利用的问题将给予700-5600元人民币不等的奖励,比如需要用户主动点击才会触发的问题或需要admin权限 * 只有在指定环境下才可以运行的利用将有可能被收纳但不给予奖励,或直接被忽略,比如只在`fastjson`+`linux`特定版本才会出现的问题 ## 不在收集范围的报告 * 影响过时浏览器或平台用户的漏洞 * `Self-XSS` * 会话固定 * 内容欺骗 * 缺少`cookie`标记 * 混合内容警告 * `SSL`/`TLS`问题 * `Clickjacking` * 基于`Flash`的漏洞 * 反射文件下载攻击(`RFD`) * 物理或社会工程攻击 * 未验证自动化工具或扫描仪的结果 * 登录/注销/未认证/低影响`CSRF` * 需要`MITM`或物理访问用户设备的攻击 * 与网络协议或行业标准相关的问题 * 不能用于直接攻击的错误信息泄露 * 缺少与安全相关的`HTTP`标头等 # Vulnerability Reward Program ## Reporting If you believe you have found any security (technical) vulnerability in the Program, you are welcomed to submit a vulnerability report to us at https://security.alibaba.com In case of reporting any security vulnerability, please be noted that you may including following information (Qualified Reporting): * The git program URL and running version * A detailed description with necessary screenshots * Steps to reappearance the vulnerability and your advice to fix it * Other useful information ## Processing ASRC (Alibaba Security Response Center) will review and respond as quickly as possible to your submission, and keep you informed as we work to fix the vulnerability you submitted. We may contact you for further information if necessary. ## Terms and Conditions 1. ONLY technical vulnerabilities will be accepted and rated. 2. With regarding to security reasons, reporters agree to cooperate with ASRC exclusively on the vulnerability he/she submitted and not disclose any information of vulnerability to any third-parties. 3. In the case that more than one person report the same security vulnerability, the reward will be given to the first person who accomplish a Qualified Reporting. 4. To protect users of the program, please do not directly submit issue on github or discuss anything with the community 5. All Rewards and Reputation Credits are given to the reporters who submit his/her security vulnerabilities ONLY to ASRC. 6. All rights for the security vulnerability rewards are reserved by ASRC. ## Scope of Collecting The main categories of vulnerabilities that we are sincerely looking for are: * Server-Side Request Forgery (`SSRF`) * SQL Injection * Denial of Service Attack * Remote Code Execution (`RCE`) * XML External Entity Attacks (`XXE`) * Access Control Issues (Insecure Direct Object Reference issues, etc.) * Directory Traversal Issues * Local File Disclosure (`LFD`) * Sensitive Information Leakage (Key, Cookie, Session etc.) ## Reward * $1,000 for one valid report * $100-$800 for Vuls which is limited. For example, Vuls that need user interactions or administrator authority * Vuls which only work on the special version will be accepted but no reward, or directly rejected. For example, Vul runs only on a special linux version ## Ineligible Reports * Vulnerabilities affecting users of outdated browsers or platforms * "Self" `XSS` * Session fixation * Content Spoofing * Missing cookie flags * Mixed content warnings * `SSL`/`TLS` best practices * Clickjacking/UI redressing * Flash-based vulnerabilities * Reflected file download attacks (`RFD`) * Physical or social engineering attacks * Unverified Results of automated tools or scanners * Login/logout/unauthenticated/low-impact `CSRF` * Attacks requiring MITM or physical access to a user's device * Issues related to networking protocols or industry standards * Error information disclosure that cannot be used to make a direct attack * Missing security-related `HTTP` headers which do not lead directly to a vulnerability ================================================ FILE: docs/developer-guide-en.md ================================================ # 🎓 Developer Guide --------------------------- - [📌 Framework/Middleware integration to `TTL` transmittance](#-frameworkmiddleware-integration-to-ttl-transmittance) - [📚 Related material](#-related-material) - [`JDK` core classes](#jdk-core-classes) - [`Java` Agent](#java-agent) - [`Javassist`](#javassist) - [`Maven Shade plugin`](#maven-shade-plugin) --------------------------- # 📌 Framework/Middleware integration to `TTL` transmittance [`TransmittableThreadLocal.Transmitter`](../ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java) to capture all `TTL` values of current thread and replay them in another thread. There are following methods: 1. `capture`: capture all `TTL` values in current thread 2. `replay`: replay the captured `TTL` values in the current thread, and return the backup `TTL` values before replay 3. `restore`: restore `TTL` values before replay Sample code: ```java // =========================================================================== // Thread A // =========================================================================== TransmittableThreadLocal context = new TransmittableThreadLocal(); context.set("value-set-in-parent"); // 1. capture all TTL values in current thread final Object captured = TransmittableThreadLocal.Transmitter.capture(); // =========================================================================== // Thread B // =========================================================================== // 2. replay the captured TTL values in current thread, and return the backup TTL values before replay final Object backup = TransmittableThreadLocal.Transmitter.replay(captured); try { // Your biz code, you can get the TTL value from here String value = context.get(); ... } finally { // 3. restore TTL values before replay TransmittableThreadLocal.Transmitter.restore(backup); } ``` - For more info about `TransmittableThreadLocal.Transmitter`, see [its Javadoc](../ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java). - For more actual implementation code of `TTL` transmittance, see [`TtlRunnable.java`](../ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java) and [`TtlCallable.java`](../ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java). # 📚 Related material ## `JDK` core classes - [WeakHashMap](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/WeakHashMap.html) - [InheritableThreadLocal](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html) ## `Java` Agent - [Java Agent Specification](https://docs.oracle.com/en/java/javase/21/docs/api/java.instrument/java/lang/instrument/package-summary.html) ## `Javassist` - [Getting Started with Javassist](https://www.javassist.org/tutorial/tutorial.html) ## `Maven Shade plugin` - [`Maven Shade plugin` doc](https://maven.apache.org/plugins/maven-shade-plugin/) ================================================ FILE: docs/developer-guide.md ================================================ # 🎓 Developer Guide --------------------------- - [📌 框架/中间件集成`TTL`传递](#-%E6%A1%86%E6%9E%B6%E4%B8%AD%E9%97%B4%E4%BB%B6%E9%9B%86%E6%88%90ttl%E4%BC%A0%E9%80%92) - [📟 关于`Java Agent`](#-%E5%85%B3%E4%BA%8Ejava-agent) - [`Java Agent`方式对应用代码无侵入](#java-agent%E6%96%B9%E5%BC%8F%E5%AF%B9%E5%BA%94%E7%94%A8%E4%BB%A3%E7%A0%81%E6%97%A0%E4%BE%B5%E5%85%A5) - [已有`Java Agent`中嵌入`TTL Agent`](#%E5%B7%B2%E6%9C%89java-agent%E4%B8%AD%E5%B5%8C%E5%85%A5ttl-agent) - [👢 `Bootstrap ClassPath`上添加通用库`Jar`的问题及其解决方法](#-bootstrap-classpath%E4%B8%8A%E6%B7%BB%E5%8A%A0%E9%80%9A%E7%94%A8%E5%BA%93jar%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E5%85%B6%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95) - [🔨 如何编译构建](#-%E5%A6%82%E4%BD%95%E7%BC%96%E8%AF%91%E6%9E%84%E5%BB%BA) - [发布操作列表](#%E5%8F%91%E5%B8%83%E6%93%8D%E4%BD%9C%E5%88%97%E8%A1%A8) - [📚 相关资料](#-%E7%9B%B8%E5%85%B3%E8%B5%84%E6%96%99) - [`JDK` core classes](#jdk-core-classes) - [`Java Agent`](#java-agent) - [`Javassist`](#javassist) - [`Maven Shade`插件](#maven-shade%E6%8F%92%E4%BB%B6) --------------------------- # 📌 框架/中间件集成`TTL`传递 框架/中间件集成`TTL`传递,通过[`TransmittableThreadLocal.Transmitter`](../ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java) 抓取当前线程的所有`TTL`值并在其他线程进行回放;在回放线程执行完业务操作后,恢复为回放线程原来的`TTL`值。 `TransmittableThreadLocal.Transmitter`提供了所有`TTL`值的抓取、回放和恢复方法(即`CRR`操作): 1. `capture`方法:抓取线程(线程A)的所有`TTL`值。 2. `replay`方法:在另一个线程(线程B)中,回放在`capture`方法中抓取的`TTL`值,并返回 回放前`TTL`值的备份 3. `restore`方法:恢复线程B执行`replay`方法之前的`TTL`值(即备份) 示例代码: ```java // =========================================================================== // 线程 A // =========================================================================== TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); // (1) 抓取当前线程的所有TTL值 final Object captured = TransmittableThreadLocal.Transmitter.capture(); // =========================================================================== // 线程 B(异步线程) // =========================================================================== // (2) 在线程 B中回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份 final Object backup = TransmittableThreadLocal.Transmitter.replay(captured); try { // 你的业务逻辑,这里你可以获取到外面设置的TTL值 String value = context.get(); System.out.println("Hello: " + value); ... String result = "World: " + value; } finally { // (3) 恢复线程 B执行replay方法之前的TTL值(即备份) TransmittableThreadLocal.Transmitter.restore(backup); } ``` 更多`TTL`传递的代码实现示例,参见 [`TtlRunnable.java`](../ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java)、[`TtlCallable.java`](../ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java)。 当然可以使用`TransmittableThreadLocal.Transmitter`的工具方法`runSupplierWithCaptured`和`runCallableWithCaptured`和可爱的`Java 8 Lambda`语法 来简化`replay`和`restore`操作,示例代码: ```java // =========================================================================== // 线程 A // =========================================================================== TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); // (1) 抓取当前线程的所有TTL值 final Object captured = TransmittableThreadLocal.Transmitter.capture(); // =========================================================================== // 线程 B(异步线程) // =========================================================================== String result = runSupplierWithCaptured(captured, () -> { // 你的业务逻辑,这里你可以获取到外面设置的TTL值 String value = context.get(); System.out.println("Hello: " + value); ... return "World: " + value; }); // (2) + (3) ``` - 更多`TTL`传递的说明,详见[`TransmittableThreadLocal.Transmitter`的`JavaDoc`](../ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java)。 - 更多`TTL`传递的代码实现,参见[`TtlRunnable.java`](../ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java)、[`TtlCallable.java`](../ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java)。 # 📟 关于`Java Agent` ## `Java Agent`方式对应用代码无侵入 [User Guide - 2.3 使用`Java Agent`来修饰`JDK`线程池实现类](../README.md#23-%E4%BD%BF%E7%94%A8java-agent%E6%9D%A5%E4%BF%AE%E9%A5%B0jdk%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%AE%9E%E7%8E%B0%E7%B1%BB) 说到了,相对修饰`Runnable`或是线程池的方式,`Java Agent`方式是对应用代码无侵入的。下面做一些展开说明。 构架图 按框架图,把前面示例代码操作可以分成下面几部分: 1. 读取信息设置到`TTL`。 这部分在容器中完成,无需应用参与。 2. 提交`Runnable`到线程池。要有修饰操作`Runnable`(无论是直接修饰`Runnable`还是修饰线程池)。 这部分操作一定是在用户应用中触发。 3. 读取`TTL`,做业务检查。 在`SDK`中完成,无需应用参与。 只有第2部分的操作和应用代码相关。 如果不通过`Java Agent`修饰线程池,则修饰操作需要应用代码来完成。 使用`Java Agent`方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。 更多关于应用场景的了解说明参见文档[需求场景](requirement-scenario.md)。 ## 已有`Java Agent`中嵌入`TTL Agent` 这样可以减少`Java`启动命令行上的`Agent`的配置。 在自己的`Agent`中加上`TTL Agent`的逻辑,示例代码如下([`YourXxxAgent.java`](../ttl2-compatible/src/test/java/com/alibaba/demo/ttl/agent/YourXxxAgent.java)): ```java import com.alibaba.ttl.threadpool.agent.TtlAgent; import com.alibaba.ttl.threadpool.agent.TtlTransformer; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.logging.Logger; public final class YourXxxAgent { private static final Logger logger = Logger.getLogger(YourXxxAgent.class.getName()); public static void premain(String agentArgs, Instrumentation inst) { TtlAgent.premain(agentArgs, inst); // add TTL Transformer // add your Transformer ... } } ``` 关于`Java Agent`和`ClassFileTransformer`的如何实现可以参考:[`TtlAgent.java`](../ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/TtlAgent.java)、[`TtlTransformer.java`](../ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/TtlTransformer.java)。 注意,在`bootclasspath`上,还是要加上`TTL Jar`: ```bash -Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.y.jar:/path/to/your/agent/jar/files ``` # 👢 `Bootstrap ClassPath`上添加通用库`Jar`的问题及其解决方法 `TTL Agent`的使用方式,需要将`TTL Jar`加到`Bootstrap ClassPath`上(通过`Java`命令行参数`-Xbootclasspath`);这样`TTL`的类与`JDK`的标准库的类(如`java.lang.String`)的`ClassLoader`是一样的,都在`Bootstrap ClassPath`上。 `Bootstrap ClassPath`上的类会优先于应用`ClassPath`的`Jar`被加载,并且加载`ClassLoader`不能被改。 \# 当然技术上严格地说,通过`Bootstrap ClassPath`上的类(如标准库的类)是可以改`ClassLoader`的,但这样做一般只会带来各种麻烦的问题。关于`ClassLoader`及其使用注意的介绍说明 可以参见[ClassLoader委托关系的完备配置](https://github.com/oldratlee/land#1-classloader%E5%A7%94%E6%89%98%E5%85%B3%E7%B3%BB%E7%9A%84%E5%AE%8C%E5%A4%87%E9%85%8D%E7%BD%AE)。 `TTL Agent`自己内部实现使用了`Javassist`,即在`Bootstrap ClassPath`上也需要添加`Javassist`。如果应用中也使用了`Javassist`,由于运行时会优先使用`TTL Agent`配置`Bootstrap ClassPath`上的`Javassist`,应用逻辑运行时实际不能选择/指定应用自己的`Javassist`的版本,带来了 应用需要的`Javassist`与`TTL Agent`用的`Javassist`之间的兼容性风险。 可以通过 `repackage`依赖(即 重命名/改写 依赖类的包名)来解决这个问题。`Maven`提供了[`Shade`插件](https://maven.apache.org/plugins/maven-shade-plugin/),可以完成下面的操作: - `repackage` `Javassist`的类文件 - 添加`repackage`过的`Javassist`到`TTL Jar`中 这样操作后,`TTL Agent`不需要依赖外部的`Javassist`依赖,效果上这样的`shade`过的`TTL Jar`是自包含的、在使用上是编译/运行时0依赖的,自然也规避了依赖冲突的问题。 # 🔨 如何编译构建 编译构建的环境要求: **_`JDK 8+`_**;用`Maven`常规的方式执行编译构建即可: \# 在工程中已经包含了符合版本要求的`Maven`,直接运行 **_工程根目录下的`mvnw`_**;并不需要先手动自己安装好`Maven`。 ```bash # 运行测试Case ./mvnw test # 编译打包 ./mvnw package # 运行测试Case、编译打包、安装TTL库到Maven本地 ./mvnw install ##################################################### # 如果使用你自己安装的 maven,版本要求:maven 3.3.9+ mvn install ``` # 发布操作列表 详见独立文档 [发布操作列表](release-action-list.md)。 # 📚 相关资料 ## `JDK` core classes - [WeakHashMap](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/WeakHashMap.html) - [InheritableThreadLocal](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html) ## `Java Agent` - 官方文档 - [`Java Agent`规范 - `JavaDoc`](https://docs.oracle.com/en/java/javase/21/docs/api/java.instrument/java/lang/instrument/package-summary.html#package.description) - [JAR File Specification - JAR Manifest](https://docs.oracle.com/en/java/javase/21/docs/specs/jar/jar.html#jar-manifest) - [Working with Manifest Files - The Java™ Tutorials](https://docs.oracle.com/javase/tutorial/deployment/jar/manifestindex.html) - [Java SE 6 新特性: Instrumentation 新功能](https://www.ibm.com/developerworks/cn/java/j-lo-jse61/) - [Creation, dynamic loading and instrumentation with javaagents](https://dhruba.name/2010/02/07/creation-dynamic-loading-and-instrumentation-with-javaagents/) - [JavaAgent加载机制分析](https://www.iteye.com/blog/nijiaben-1847212/) ## `Javassist` - [Getting Started with Javassist](https://www.javassist.org/tutorial/tutorial.html) ## `Maven Shade`插件 - [`Maven Shade`插件文档](https://maven.apache.org/plugins/maven-shade-plugin/) ================================================ FILE: docs/logo.md ================================================ ================================================ FILE: docs/performance-test.md ================================================ # ☔️ 性能测试 - [👻 内存泄漏](#-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F) - [验证结果](#%E9%AA%8C%E8%AF%81%E7%BB%93%E6%9E%9C) - [执行方式](#%E6%89%A7%E8%A1%8C%E6%96%B9%E5%BC%8F) - [🐎 TPS & 压力测试](#-tps--%E5%8E%8B%E5%8A%9B%E6%B5%8B%E8%AF%95) - [验证结果](#%E9%AA%8C%E8%AF%81%E7%BB%93%E6%9E%9C-1) - [TPS略有下降的原因分析](#tps%E7%95%A5%E6%9C%89%E4%B8%8B%E9%99%8D%E7%9A%84%E5%8E%9F%E5%9B%A0%E5%88%86%E6%9E%90) - [FGC次数增多的原因分析](#fgc%E6%AC%A1%E6%95%B0%E5%A2%9E%E5%A4%9A%E7%9A%84%E5%8E%9F%E5%9B%A0%E5%88%86%E6%9E%90) - [执行方式](#%E6%89%A7%E8%A1%8C%E6%96%B9%E5%BC%8F-1) ## 👻 内存泄漏 对比测试[`TransmittableThreadLocal`](../ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java)和[`ThreadLocal`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ThreadLocal.html),测试Case是: 简单一个线程一直循环`new` `TransmittableThreadLocal`、`ThreadLocal`实例,不主动做任何清理操作,即不调用`ThreadLocal`的`remove`方法主动清空。 ### 验证结果 都可以持续运行,不会出内存溢出`OutOfMemoryError`。 ### 执行方式 可以通过执行工程下的脚本来运行Case验证: * 脚本[`memoryleak-ThreadLocal.sh`](../scripts/perf-test/memoryleak-ThreadLocal.sh)运行`ThreadLocal`的测试。 测试类是[`NoMemoryLeak_ThreadLocal_NoRemove`](../ttl-core/src/test/java/com/alibaba/perf/memoryleak/NoMemoryLeak_ThreadLocal_NoRemove.kt)。 * 脚本[`memoryleak-TransmittableThreadLocal.sh`](../scripts/perf-test/memoryleak-TransmittableThreadLocal.sh)运行`TransmittableThreadLocal`的测试。 测试类是[`NoMemoryLeak_TransmittableThreadLocal_NoRemove`](../ttl-core/src/test/java/com/alibaba/perf/memoryleak/NoMemoryLeak_TransmittableThreadLocal_NoRemove.kt)。 ## 🐎 TPS & 压力测试 对比测试[`TransmittableThreadLocal`](../ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java)和[`ThreadLocal`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ThreadLocal.html),测试Case是: 2个线程并发一直循环`new` `TransmittableThreadLocal`、`ThreadLocal`实例,不主动做任何清理操作,即不调用`ThreadLocal`的`remove`方法主动清空。 ### 验证结果 在我的4核开发机上运行了24小时,稳定正常。 TPS结果如下: `ThreadLocal`的TPS稳定在~41K: ```bash ...... tps: 42470 tps: 40940 tps: 41041 tps: 40408 tps: 40610 ``` `TransmittableThreadLocal`的TPS稳定在~40K: ```bash ...... tps: 40461 tps: 40101 tps: 39989 tps: 40684 tps: 41174 ``` GC情况如下(1分钟输出一次): `ThreadLocal`的每分钟GC时间是`5.45s`,FGC次数是`0.09`: ```bash S0 S1 E O P YGC YGCT FGC FGCT GCT ...... 0.00 97.66 0.00 8.33 12.70 1470935 2636.215 41 0.229 2636.444 97.66 0.00 0.00 17.18 12.70 1473968 2640.597 41 0.229 2640.825 98.44 0.00 0.00 25.47 12.70 1477020 2645.265 41 0.229 2645.493 96.88 0.00 33.04 34.03 12.70 1480068 2650.149 41 0.229 2650.378 0.00 97.66 14.01 41.82 12.70 1483113 2655.262 41 0.229 2655.490 0.00 97.66 74.07 50.25 12.70 1486149 2660.596 41 0.229 2660.825 96.88 0.00 0.00 58.32 12.70 1489170 2666.135 41 0.229 2666.364 98.44 0.00 26.07 67.05 12.70 1492162 2671.841 41 0.229 2672.070 0.00 97.66 0.00 76.50 12.70 1495139 2677.809 41 0.229 2678.038 0.00 97.66 0.00 85.95 12.70 1498091 2683.994 41 0.229 2684.222 96.88 0.00 0.00 96.50 12.70 1501038 2690.454 41 0.229 2690.683 97.66 0.00 0.00 7.96 12.70 1504054 2695.583 42 0.233 2695.816 0.00 97.66 0.00 17.46 12.70 1507099 2700.009 42 0.233 2700.241 0.00 97.66 0.00 26.97 12.70 1510133 2704.652 42 0.233 2704.885 97.66 0.00 0.00 36.57 12.70 1513158 2709.592 42 0.233 2709.825 0.00 97.66 0.00 45.59 12.70 1516167 2714.738 42 0.233 2714.971 98.44 0.00 0.00 54.49 12.70 1519166 2720.109 42 0.233 2720.342 0.00 98.44 0.00 63.52 12.70 1522141 2725.688 42 0.233 2725.921 0.00 97.66 84.18 72.00 12.70 1525139 2731.579 42 0.233 2731.812 0.00 98.44 20.04 80.10 12.70 1528121 2737.680 42 0.233 2737.913 0.00 97.66 28.06 87.70 12.70 1531093 2743.991 42 0.233 2744.224 0.00 98.44 0.00 95.63 12.70 1534055 2750.508 42 0.233 2750.741 97.66 0.00 0.00 4.75 12.70 1537062 2756.196 43 0.239 2756.435 ``` `TransmittableThreadLocal`的每分钟GC时间是`5.29s`,FGC次数是`3.27`: ```bash S0 S1 E O P YGC YGCT FGC FGCT GCT ...... 0.00 98.44 8.01 57.38 12.80 1390879 2571.496 1572 9.820 2581.315 0.00 97.66 0.00 78.87 12.80 1393725 2576.784 1575 9.839 2586.623 98.44 0.00 14.04 5.83 12.80 1396559 2582.082 1579 9.866 2591.948 98.44 0.00 0.00 26.41 12.80 1399394 2587.274 1582 9.885 2597.159 98.44 98.44 0.00 50.75 12.80 1402230 2592.506 1585 9.904 2602.410 98.44 0.00 0.00 84.37 12.80 1405077 2597.808 1588 9.925 2607.733 0.00 98.44 0.00 5.19 12.80 1407926 2603.108 1592 9.952 2613.059 0.00 98.44 58.17 29.80 12.80 1410770 2608.314 1595 9.973 2618.287 99.22 0.00 0.00 54.14 12.80 1413606 2613.582 1598 9.992 2623.574 98.44 0.00 0.00 78.18 12.80 1416444 2618.881 1601 10.012 2628.893 0.00 97.66 0.00 7.36 12.80 1419275 2624.167 1605 10.038 2634.205 0.00 99.22 0.00 31.04 12.80 1422125 2629.391 1608 10.057 2639.448 0.00 98.44 0.00 60.41 12.80 1424974 2634.636 1611 10.077 2644.714 0.00 98.44 0.00 84.72 12.80 1427825 2639.929 1614 10.094 2650.024 0.00 97.66 0.00 12.32 12.80 1430679 2645.204 1618 10.119 2655.323 0.00 98.44 12.05 39.31 12.80 1433539 2650.442 1621 10.141 2660.583 86.81 0.00 0.00 67.40 12.80 1436392 2655.743 1624 10.156 2665.899 99.22 0.00 0.00 95.25 12.80 1439244 2661.071 1627 10.175 2671.246 98.44 0.00 0.00 24.63 12.80 1442090 2666.305 1631 10.201 2676.506 0.00 99.22 0.00 52.86 12.80 1444945 2671.546 1634 10.222 2681.769 98.44 0.00 0.00 80.38 12.80 1447802 2676.850 1637 10.241 2687.091 0.00 87.50 0.00 4.22 12.80 1450658 2682.196 1641 10.268 2692.464 99.22 0.00 0.00 33.22 12.80 1453507 2687.386 1644 10.290 2697.676 ``` #### TPS略有下降的原因分析 使用`jvisualvm` Profile方法耗时,`TransmittableThreadLocal`Case的热点方法和`ThreadLocal`Case一样。 略有下降可以认为是Full GC更多引起。 实际使用场景中,`TransmittableThreadLocal`实例个数非常有限,不会有性能问题。 #### FGC次数增多的原因分析 在`TransmittableThreadLocal.holder`中,持有`TransmittableThreadLocal`实例的弱引用,减慢实例的回收,导致Full GC增加。 实际使用场景中,`TransmittableThreadLocal`实例个数非常有限,不会有性能问题。 ### 执行方式 可以通过执行工程下的脚本来运行Case验证: * 脚本[`tps-ThreadLocal.sh`](../scripts/perf-test/tps-ThreadLocal.sh)运行`ThreadLocal`的测试。 测试类是[`CreateThreadLocalInstanceTps`](../ttl-core/src/test/java/com/alibaba/perf/tps/CreateThreadLocalInstanceTps.kt)。 * [`tps-TransmittableThreadLocal.sh`](../scripts/perf-test/tps-TransmittableThreadLocal.sh)运行`TransmittableThreadLocal`的测试。 测试类是[`CreateTransmittableThreadLocalInstanceTps`](../ttl-core/src/test/java/com/alibaba/perf/tps/CreateTransmittableThreadLocalInstanceTps.kt)。 ================================================ FILE: docs/release-action-list.md ================================================ 发布操作列表 =============================== 1. 准备发布分支 1. 如`POM`中有降开发版本,注意 修改 新加`API`的 **_`@since`_** !! 2. 从`master`分支新建发布分支 3. 在发布分支上,更新版本号及相关信息 - 更新`POM`的版本号成要发布的版本号,去掉`SNAPSHOT` - 更新`README` - 更新badge的引用,由master分支名改成Tag名 - `sed 's/master/v2.x.y/g' -i README*` - `javadoc` badge的JavaDoc链接到固定版本 https://alibaba.github.io/transmittable-thread-local/apidocs/2.x.y/index.html - 示例`Maven`依赖的版本 - 更新`JavaDoc`链接到固定版本 2. 新建并Push Tag,如`v2.x.y` - `git tag -m 'release v2.x.y' v2.x.y` - `git push origin v2.x.y` 3. 等待Tag的CI通过 https://github.com/alibaba/transmittable-thread-local/actions 4. 执行[`scripts/check-japi-compliance.sh`](../scripts/check-japi-compliance.sh),检查`API`兼容性 5. 发布版本到`Maven`中央库 `./mvnw clean && ./mvnw deploy -DperformRelease` 6. 更新`JavaDoc` 1. 生成`JavaDoc`,更新到分支`gh-pages` - `git checkout gh-pages` - `mv target/apidocs apidocs/2.x.y` 2. 修改`index.html`的重定向到最新版本的`JavaDoc` 7. 编写Release Note: 8. 升级`Master`分支的开发版本号 - 更新 `README`中的示例`Maven`依赖版本 ================================================ FILE: docs/requirement-scenario.md ================================================ # 🎨 需求场景 在`ThreadLocal`的需求场景即是`TTL`的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的组件情况下传递`ThreadLocal`』则是`TTL`目标场景。 下面是几个典型场景例子。 ------------------------------- - [🔎 1. 分布式跟踪系统](#-1-%E5%88%86%E5%B8%83%E5%BC%8F%E8%B7%9F%E8%B8%AA%E7%B3%BB%E7%BB%9F) - [🌵 2. 日志收集记录系统上下文](#-2-%E6%97%A5%E5%BF%97%E6%94%B6%E9%9B%86%E8%AE%B0%E5%BD%95%E7%B3%BB%E7%BB%9F%E4%B8%8A%E4%B8%8B%E6%96%87) - [`Log4j2 MDC`的`TTL`集成](#log4j2-mdc%E7%9A%84ttl%E9%9B%86%E6%88%90) - [`Logback MDC`的`TTL`集成](#logback-mdc%E7%9A%84ttl%E9%9B%86%E6%88%90) - [👜 3. `Request`级`Cache`](#-3-request%E7%BA%A7cache) - [🛁 4. 应用容器或上层框架跨应用代码给下层`SDK`传递信息](#-4-%E5%BA%94%E7%94%A8%E5%AE%B9%E5%99%A8%E6%88%96%E4%B8%8A%E5%B1%82%E6%A1%86%E6%9E%B6%E8%B7%A8%E5%BA%94%E7%94%A8%E4%BB%A3%E7%A0%81%E7%BB%99%E4%B8%8B%E5%B1%82sdk%E4%BC%A0%E9%80%92%E4%BF%A1%E6%81%AF) - [上面场景使用`TTL`的整体构架](#%E4%B8%8A%E9%9D%A2%E5%9C%BA%E6%99%AF%E4%BD%BF%E7%94%A8ttl%E7%9A%84%E6%95%B4%E4%BD%93%E6%9E%84%E6%9E%B6) ------------------------------- ## 🔎 1. 分布式跟踪系统 或 全链路压测(即链路打标) 关于『分布式跟踪系统』可以了解一下`Google`的`Dapper`(介绍的论文:[中文](https://bigbully.github.io/Dapper-translation/)| [英文](https://research.google.com/pubs/pub36356.html))。分布式跟踪系统作为基础设施,不会限制『使用线程池等会池化复用线程的组件』,并期望对业务逻辑尽可能的透明。 分布式跟踪系统的实现的示意Demo参见[`DistributedTracerUseDemo.kt`](../ttl2-compatible/src/test/java/com/alibaba/demo/distributed_tracer/refcount/DistributedTracerUseDemo.kt) 从技术能力上讲,全链路压测 与 分布式跟踪系统 是一样的,即链路打标。 PS: 多谢 [@wyzssw](https://github.com/https://github.com/wyzssw) 对分布式追踪系统场景说明交流和实现上讨论建议: - [Issue: 分布式追踪系统场景下,如何使用TTL](https://github.com/alibaba/transmittable-thread-local/issues/53) ## 🌵 2. 日志收集记录系统上下文 由于不限制用户应用使用线程池,系统的上下文需要能跨线程的传递,且不影响应用代码。 ### `Log4j2 MDC`的`TTL`集成 `Log4j2`通过[`Thread Context`](https://logging.apache.org/log4j/2.x/manual/thread-context.html)提供了`Mapped Diagnostic Context`(`MDC`,诊断上下文)的功能,通过[`ThreadLocal`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/ThreadLocal.html)/[`InheritableThreadLocal`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/InheritableThreadLocal.html)实现上下文传递。 在[`Thread Context文档`](https://logging.apache.org/log4j/2.x/manual/thread-context.html)中提到了在使用线程池等会池化复用线程的组件(如`Executors`)时有问题,需要提供一个机制方案: > The Stack and the Map are managed per thread and are based on ThreadLocal by default. The Map can be configured to use an InheritableThreadLocal by setting system property isThreadContextMapInheritable to "true". When configured this way, the contents of the Map will be passed to child threads. However, as discussed in the [Executors](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/Executors.html#privilegedThreadFactory%28%29) class and in other cases where thread pooling is utilized, the ThreadContext may not always be automatically passed to worker threads. In those cases the pooling mechanism should provide a means for doing so. The getContext() and cloneStack() methods can be used to obtain copies of the Map and Stack respectively. 即是`TTL`要解决的问题,提供`Log4j2 MDC`的`TTL`集成,详见工程[`log4j2-ttl-thread-context-map`](https://github.com/oldratlee/log4j2-ttl-thread-context-map)。对应依赖: ```xml com.alibaba log4j2-ttl-thread-context-map 1.3.0 ``` 可以在 [maven.org](https://repo1.maven.org/maven2/com/alibaba/log4j2-ttl-thread-context-map/maven-metadata.xml) 查看可用的版本。 PS: 多谢 @bwzhang2011 和 @wuwen5 对日志场景说明交流和实现上讨论建议: - [Issue: 能否提供与LOG4J(2)中的MDC集成或增强](https://github.com/alibaba/transmittable-thread-local/issues/49) [@bwzhang2011](https://github.com/bwzhang2011) - [Issue: slf4j MDCAdapter with multi-thread-context 支持](https://github.com/alibaba/transmittable-thread-local/issues/51) [@bwzhang2011](https://github.com/bwzhang2011) ### `Logback MDC`的`TTL`集成 `Logback`的集成参见[@ofpay](https://github.com/ofpay)提供的[`logback-mdc-ttl`](https://github.com/ofpay/logback-mdc-ttl)。对应依赖: ```xml com.ofpay logback-mdc-ttl 1.0.2 ``` 可以在 [maven.org](https://repo1.maven.org/maven2/com/ofpay/logback-mdc-ttl/maven-metadata.xml) 查看可用的版本。 这个集成已经在 **_线上产品环境_** 使用的。说明详见[欧飞网的使用场景](https://github.com/alibaba/transmittable-thread-local/issues/73#issuecomment-300665308)。 ## 👜 3. `Request`级`Cache` 对于计算逻辑复杂业务流程,基础数据读取服务(这样的读取服务往往是个外部远程服务)可能需要多次调用,期望能缓存起来,以避免多次重复执行高成本操作。 同时,在入口发起不同的请求,处理的是不同用户的数据,所以不同发起请求之间不需要共享数据,这样也能避免请求对应的不同用户之间可能的数据污染。 因为涉及多个上下游线程,其实是`Session`级缓存。 通过`Request`级缓存可以 - 避免重复执行高成本操作,提升性能。 - 避免不同`Request`之间的数据污染。 更多讨论与使用方式参见[**_`@olove`_**](https://github.com/olove) 提的Issue:[讨论:Session级Cache场景下,TransmittableThreadLocal的使用](https://github.com/alibaba/transmittable-thread-local/issues/122)。 ## 🛁 4. 应用容器或上层框架跨应用代码给下层`SDK`传递信息 举个具体的业务场景,在`App Engine`(`PAAS`)上会运行由应用提供商提供的应用(`SAAS`模式)。多个`SAAS`用户购买并使用这个应用(即`SAAS`应用)。`SAAS`应用往往是一个实例为多个`SAAS`用户提供服务。 \# 另一种模式是:`SAAS`用户使用完全独立一个`SAAS`应用,包含独立应用实例及其后的数据源(如`DB`、缓存,etc)。 需要避免的`SAAS`应用拿到多个`SAAS`用户的数据。一个解决方法是处理过程关联好一个`SAAS`用户的上下文,在上下文中应用只能处理(读/写)这个`SAAS`用户的数据。请求由`SAAS`用户发起(如从`Web`请求进入`App Engine`),`App Engine`可以知道是从哪个`SAAS`用户,在`Web`请求时在上下文中设置好`SAAS`用户`ID`。应用处理数据(`DB`、`Web`、消息 etc.)是通过`App Engine`提供的服务`SDK`来完成。当应用处理数据时,`SDK`检查数据所属的`SAAS`用户是否和上下文中的`SAAS`用户`ID`一致,如果不一致则拒绝数据的读写。 应用代码会使用线程池,并且这样的使用是正常的业务需求。`SAAS`用户`ID`的从要`App Engine`传递到下层`SDK`,要支持这样的用法。 ### 上面场景使用`TTL`的整体构架 构架图 构架涉及3个角色:容器、用户应用、`SDK`。 整体流程: 1. 请求进入`PAAS`容器,提取上下文信息并设置好上下文。 2. 进入用户应用处理业务,业务调用`SDK`(如`DB`、消息、etc)。 用户应用会使用线程池,所以调用`SDK`的线程可能不是请求的线程。 3. 进入`SDK`处理。 提取上下文的信息,决定是否符合拒绝处理。 整个过程中,上下文的传递 对于 **用户应用代码** 期望是透明的。 ================================================ FILE: mvnw ================================================ #!/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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.3.2 # # Optional ENV vars # ----------------- # JAVA_HOME - location of a JDK home dir, required when download maven via java source # MVNW_REPOURL - repo url base for downloading maven distribution # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- set -euf [ "${MVNW_VERBOSE-}" != debug ] || set -x # OS specific support. native_path() { printf %s\\n "$1"; } case "$(uname)" in CYGWIN* | MINGW*) [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" native_path() { cygpath --path --windows "$1"; } ;; esac # set JAVACMD and JAVACCMD set_java_home() { # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 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" JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" JAVACCMD="$JAVA_HOME/bin/javac" if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 return 1 fi fi else JAVACMD="$( 'set' +e 'unset' -f command 2>/dev/null 'command' -v java )" || : JAVACCMD="$( 'set' +e 'unset' -f command 2>/dev/null 'command' -v javac )" || : if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 return 1 fi fi } # hash string like Java String::hashCode hash_string() { str="${1:-}" h=0 while [ -n "$str" ]; do char="${str%"${str#?}"}" h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) str="${str#?}" done printf %x\\n $h } verbose() { :; } [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } die() { printf %s\\n "$1" >&2 exit 1 } trim() { # MWRAPPER-139: # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. # Needed for removing poorly interpreted newline sequences when running in more # exotic environments such as mingw bash on Windows. printf "%s" "${1}" | tr -d '[:space:]' } # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties while IFS="=" read -r key value; do case "${key-}" in distributionUrl) distributionUrl=$(trim "${value-}") ;; distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; esac done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" case "${distributionUrl##*/}" in maven-mvnd-*bin.*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; :Linux*x86_64*) distributionPlatform=linux-amd64 ;; *) echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 distributionPlatform=linux-amd64 ;; esac distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" ;; maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; esac # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" distributionUrlName="${distributionUrl##*/}" distributionUrlNameMain="${distributionUrlName%.*}" distributionUrlNameMain="${distributionUrlNameMain%-bin}" MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" exec_maven() { unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } if [ -d "$MAVEN_HOME" ]; then verbose "found existing MAVEN_HOME at $MAVEN_HOME" exec_maven "$@" fi case "${distributionUrl-}" in *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; esac # prepare tmp dir if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } trap clean HUP INT TERM EXIT else die "cannot create temp dir" fi mkdir -p -- "${MAVEN_HOME%/*}" # Download and Install Apache Maven verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." verbose "Downloading from: $distributionUrl" verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" # select .zip or .tar.gz if ! command -v unzip >/dev/null; then distributionUrl="${distributionUrl%.zip}.tar.gz" distributionUrlName="${distributionUrl##*/}" fi # verbose opt __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v # normalize http auth case "${MVNW_PASSWORD:+has-password}" in '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; esac if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then verbose "Found wget ... using wget" wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then verbose "Found curl ... using curl" curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" elif set_java_home; then verbose "Falling back to use Java to download" javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" cat >"$javaSource" <<-END public class Downloader extends java.net.Authenticator { protected java.net.PasswordAuthentication getPasswordAuthentication() { return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); } public static void main( String[] args ) throws Exception { setDefault( new Downloader() ); java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); } } END # For Cygwin/MinGW, switch paths to Windows format before running javac and java verbose " - Compiling Downloader.java ..." "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" verbose " - Running Downloader.java ..." "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" fi # If specified, validate the SHA-256 sum of the Maven distribution zip file if [ -n "${distributionSha256Sum-}" ]; then distributionSha256Result=false if [ "$MVN_CMD" = mvnd.sh ]; then echo "Checksum validation is not supported for maven-mvnd." >&2 echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 elif command -v sha256sum >/dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then distributionSha256Result=true fi elif command -v shasum >/dev/null; then if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then distributionSha256Result=true fi else echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi if [ $distributionSha256Result = false ]; then echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi # unzip and move if command -v unzip >/dev/null; then unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" else tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" fi printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" clean || : exec_maven "$@" ================================================ FILE: mvnw.cmd ================================================ <# : batch portion @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 Apache Maven Wrapper startup batch script, version 3.3.2 @REM @REM Optional ENV vars @REM MVNW_REPOURL - repo url base for downloading maven distribution @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output @REM ---------------------------------------------------------------------------- @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) @SET __MVNW_CMD__= @SET __MVNW_ERROR__= @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% @SET PSModulePath= @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% @SET __MVNW_PSMODULEP_SAVE= @SET __MVNW_ARG0_NAME__= @SET MVNW_USERNAME= @SET MVNW_PASSWORD= @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) @echo Cannot start maven from wrapper >&2 && exit /b 1 @GOTO :EOF : end batch / begin powershell #> $ErrorActionPreference = "Stop" if ($env:MVNW_VERBOSE -eq "true") { $VerbosePreference = "Continue" } # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl if (!$distributionUrl) { Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" } switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { "maven-mvnd-*" { $USE_MVND = $true $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" $MVN_CMD = "mvnd.cmd" break } default { $USE_MVND = $false $MVN_CMD = $script -replace '^mvnw','mvn' break } } # apply MVNW_REPOURL and calculate MAVEN_HOME # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ if ($env:MVNW_REPOURL) { $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" } $distributionUrlName = $distributionUrl -replace '^.*/','' $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" if ($env:MAVEN_USER_HOME) { $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" } $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" exit $? } if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" } # prepare tmp dir $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null trap { if ($TMP_DOWNLOAD_DIR.Exists) { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } } New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null # Download and Install Apache Maven Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." Write-Verbose "Downloading from: $distributionUrl" Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" $webclient = New-Object System.Net.WebClient if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null # If specified, validate the SHA-256 sum of the Maven distribution zip file $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum if ($distributionSha256Sum) { if ($USE_MVND) { Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." } Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." } } # unzip and move Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null try { Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null } catch { if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { Write-Error "fail to move MAVEN_HOME" } } finally { try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } } Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" ================================================ FILE: pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT pom ${project.artifactId} 📌 The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components. https://github.com/alibaba/transmittable-thread-local 2013 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local GitHub Issues https://github.com/alibaba/transmittable-thread-local/issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions Alibaba https://www.alibaba.com Jerry Lee oldratlee oldratlee(AT)gmail(DOT)com Developer +8 https://github.com/oldratlee Alibaba https://www.alibaba.com ttl-core ttl-agent ttl-kotlin ttl2-compatible ttl-bom ttl-integrations/vertx4-ttl-integration ttl-integrations/vertx3-ttl-integration ttl-integrations/sample-ttl-agent-extension-transformlet 1.8 ${maven.compiler.source} UTF-8 false https://alibaba.github.io/transmittable-thread-local/apidocs/${project.version} 4.8.6 3.0.2 24.1.0 1.9.25 ${maven.compiler.source} 1.9.0 1.9.20 8 2.0.7 5.11.4 5.9.1 warn -Dorg.slf4j.simpleLogger.logFile=System.err -Dorg.slf4j.simpleLogger.defaultLogLevel=${ttl.test.logger.level} ${argLine.slf4j.simple.logger.default.d.options} ${argLine.slf4j.simple.logger.extra.d.options} ${argLine.slf4j.simple.logger.d.options} com.github.spotbugs spotbugs-annotations true com.google.code.findbugs jsr305 true org.jetbrains annotations true org.slf4j slf4j-simple test io.github.microutils kotlin-logging-jvm test io.kotest kotest-runner-junit4-jvm test io.kotest kotest-runner-junit5-jvm test io.kotest kotest-assertions-core-jvm test io.kotest kotest-property-jvm test io.mockk mockk-jvm test org.junit.jupiter junit-jupiter-engine test org.junit.vintage junit-vintage-engine test net.bytebuddy byte-buddy-parent 1.15.11 pom import org.jetbrains.kotlin kotlin-bom ${kotlin.version} pom import org.jetbrains.kotlinx kotlinx-coroutines-bom ${kotlin.coroutine.version} pom import org.junit junit-bom ${junit5.version} pom import com.github.spotbugs spotbugs-annotations ${spotbugs.annotations.version} true com.google.code.findbugs jsr305 ${jsr305.version} true org.jetbrains annotations ${jetbrains.annotations.version} true org.javassist javassist 3.30.2-GA true org.apache.commons commons-lang3 3.12.0 io.reactivex.rxjava2 rxjava 2.2.21 io.reactivex.rxjava2 rxkotlin 2.4.0 org.slf4j slf4j-api ${slf4j.version} org.slf4j slf4j-simple ${slf4j.version} test io.github.microutils kotlin-logging-jvm 3.0.0 io.kotest kotest-runner-junit4-jvm ${kotest.version} test io.kotest kotest-runner-junit5-jvm ${kotest.version} test io.kotest kotest-assertions-core-jvm ${kotest.version} test io.kotest kotest-property-jvm ${kotest.version} test io.mockk mockk-jvm 1.14.5 test ossrh https://oss.sonatype.org/content/repositories/snapshots org.jetbrains.kotlin kotlin-maven-plugin compile compile ${project.basedir}/src/main/java test-compile test-compile ${project.basedir}/src/test/java maven-compiler-plugin default-compile none default-testCompile none java-compile compile compile java-test-compile test-compile testCompile maven-enforcer-plugin enforces enforce 3.3.9 maven-clean-plugin 3.4.0 maven-resources-plugin 3.3.1 org.jetbrains.kotlin kotlin-maven-plugin ${kotlin.version} maven-compiler-plugin 3.14.0 maven-surefire-plugin 3.5.2 maven-jar-plugin 3.4.2 maven-shade-plugin 3.6.0 true ${project.build.directory}/dependency-reduced-pom.xml maven-source-plugin 3.3.1 maven-javadoc-plugin 3.8.0 org.jetbrains.dokka dokka-maven-plugin ${dokka.version} prepare-package javadocJar dokka ${dokka.link.jdk.version} maven-gpg-plugin 3.2.7 maven-site-plugin 3.21.0 maven-install-plugin 3.1.3 maven-deploy-plugin 3.1.3 maven-enforcer-plugin 3.5.0 io.github.git-commit-id git-commit-id-maven-plugin 9.0.1 com.github.spotbugs spotbugs-maven-plugin 4.8.3.1 org.jacoco jacoco-maven-plugin 0.8.12 org.codehaus.mojo exec-maven-plugin 3.4.1 ${java.home}/bin/java test -Xmx1g -Xms256m -ea -server -Duser.language=en -Duser.country=US -classpath ${exec.mainClass} org.sonatype.plugins nexus-staging-maven-plugin 1.7.0 gen-src performRelease true maven-source-plugin gen-api-doc performRelease true *.internal:*.internal.*:*.internal.*.*:*.internal.*.*.*:*.internal.*.*.*.* ${maven.multiModuleProjectDirectory}/src/package-list -quiet maven-javadoc-plugin 8 protected UTF-8 UTF-8 UTF-8 true all,-missing ${project.basedir}/src/main/javadoc/overview.html ${javadoc.default.exclude.packages} false -html5 -linkoffline https://www.javadoc.io/doc/com.github.spotbugs/spotbugs-annotations/${spotbugs.annotations.version}/ ${javadoc.package.list.dir}/spotbugs-annotations/ -linkoffline https://www.javadoc.io/doc/org.jetbrains/annotations/${jetbrains.annotations.version}/ ${javadoc.package.list.dir}/jetbrains-annotations/ -linkoffline https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/${jsr305.version}/ ${javadoc.package.list.dir}/jsr305/ -linkoffline https://docs.oracle.com/en/java/javase/21/docs/api/java.base/ ${javadoc.package.list.dir}/java/ ${javadoc.extra.offlineLinks} -J-Duser.language=en -J-Duser.country=US gen-sign performRelease true maven-gpg-plugin sign-artifacts verify sign lint performRelease true com.github.spotbugs spotbugs-maven-plugin verify check gen-git-properties performRelease true io.github.git-commit-id git-commit-id-maven-plugin get-the-git-infos revision validate-the-git-infos validateRevision validating git dirty ${git.dirty} false true ${project.build.outputDirectory}/META-INF/scm/${project.groupId}/${project.artifactId}/git.properties gen-code-cov env.CI true org.jacoco jacoco-maven-plugin prepare-agent report test report enforce-when-release performRelease true maven-enforcer-plugin enforces enforce 17 project.version ^\d\.\d+\.\d+(-(Alpha|Beta|RC)\d+)?$|^\d(\.\d+)?\.(\d+|x)-SNAPSHOT$ "Project version(${project.version}) format is invalid!" ${maven.compiler.source} org.codehaus.mojo extra-enforcer-rules 1.8.0 deploy-settings performRelease true org.sonatype.plugins nexus-staging-maven-plugin true ossrh https://oss.sonatype.org/ true ${maven.deploy.skip} ================================================ FILE: scripts/bump-ttl-version.sh ================================================ #!/bin/bash set -eEuo pipefail # adjust current dir to script dir cd "$(dirname "$(readlink -f "$0")")" # shellcheck disable=SC2154 [ $# -ne 1 ] && cu::die "need only 1 argument for version!$nl${nl}usage:$nl $0 4.x.y" readonly bump_version="$1" cd .. echo "bump TTL version of lib to $bump_version" ./mvnw versions:set \ -DgenerateBackupPoms=false \ -DprocessAllModules=true \ -DnewVersion="$bump_version" ================================================ FILE: scripts/check-japi-compliance.sh ================================================ #!/bin/bash # Java API Compliance Checker (JAPICC), # a tool for checking backward binary and source-level compatibility of a Java library API. # https://github.com/lvc/japi-compliance-checker set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" JCC="$(readlink -f "$(command -v japi-compliance-checker.pl)")" cd .. mvn clean package ttl_jar_path=$(echo "target/transmittable-thread-local-"*.jar) work_dir="target/japi-compliance-checker" mkdir -p $work_dir cd $work_dir rm -rf compat_reports for base_version in 2.5.0 2.6.0 2.7.0 2.10.2; do url="https://repo1.maven.org/maven2/com/alibaba/transmittable-thread-local/$base_version/transmittable-thread-local-$base_version.jar" base_jar="transmittable-thread-local-$base_version.jar" if [ ! -f "$base_jar" ]; then cu::log_then_run wget --quiet "$url" fi "$JCC" -show-packages -check-annotations -skip-internal-packages '\.(javassist|utils?|internal)(\.|$)' \ "$base_jar" \ "$ttl_jar_path" || true done ================================================ FILE: scripts/codecov.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" source bash-buddy/lib/trap_error_info.sh source bash-buddy/lib/common_utils.sh source bash-buddy/lib/prepare_jdks.sh source bash-buddy/lib/java_build_utils.sh cd .. prepare_jdks::switch_to_jdk 8 # about codecov: example-java-maven # https://github.com/codecov/example-java-maven/blob/master/.travis.yml if [ "${1:-}" = "-s" ]; then jvb::mvn_cmd -Pgen-code-cov jacoco:prepare-agent surefire:test jacoco:report else jvb::mvn_cmd -Pgen-code-cov clean test fi bash <(curl -s https://codecov.io/bash) ================================================ FILE: scripts/integration-test.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" BASH_BUDDY_ROOT="$(readlink -f bash-buddy)" readonly BASH_BUDDY_ROOT source "$BASH_BUDDY_ROOT/lib/trap_error_info.sh" source "$BASH_BUDDY_ROOT/lib/common_utils.sh" source "$BASH_BUDDY_ROOT/lib/java_utils.sh" source "$BASH_BUDDY_ROOT/lib/maven_utils.sh" ################################################################################ # ci build logic ################################################################################ readonly default_build_jdk_version=17 # shellcheck disable=SC2034 readonly JDK_VERSIONS=( 8 11 "$default_build_jdk_version" 21 22 ) # here use `install` and `-D performRelease` intended # to check release operations. # # De-activate a maven profile from command line # https://stackoverflow.com/questions/25201430 # # shellcheck disable=SC2034 readonly MVU_MVN_OPTS=( "${MVU_DEFAULT_MVN_OPTS[@]}" -DperformRelease -P'!gen-sign' # Maven Plugin Validation # https://maven.apache.org/guides/plugins/validation/index.html -Dmaven.plugin.validation=NONE ${CI_MORE_MVN_OPTS:+${CI_MORE_MVN_OPTS}} ) cd .. ######################################## # build and test by default version jdk ######################################## jvu::switch_to_jdk "$default_build_jdk_version" cu::head_line_echo "build and test with Java $default_build_jdk_version: $JAVA_HOME" mvu::mvn_cmd clean install ######################################## # test by multi-version jdk ######################################## # about CI env var # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables if [ "${CI:-}" = true ]; then readonly CI_MORE_BEGIN_OPTS=jacoco:prepare-agent CI_MORE_END_OPTS=jacoco:report fi for jdk_version in "${JDK_VERSIONS[@]}"; do jvu::switch_to_jdk "$jdk_version" # just test without build if [ "$jdk_version" != "$default_build_jdk_version" ]; then # default jdk already tested above cu::head_line_echo "test with Java: $JAVA_HOME" mvu::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} surefire:test -Denforcer.skip ${CI_MORE_END_OPTS:-} fi ( cd ttl2-compatible cu::head_line_echo "test with TTL Agent and Java: $JAVA_HOME" cu::blue_echo 'Run unit test under ttl agent, include check for ExecutorService, ForkJoinPool, Timer/TimerTask' mvu::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} \ surefire:test -Denforcer.skip \ -Penable-ttl-agent-for-test \ -Dttl.agent.extra.d.options='-Drun-ttl-test-under-agent-with-enable-timer-task=true' \ ${CI_MORE_END_OPTS:-} cu::blue_echo 'Run unit test under ttl agent, and turn on the disable inheritable for thread pool enhancement' mvu::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} \ surefire:test -Denforcer.skip \ -Penable-ttl-agent-for-test \ -Dttl.agent.extra.args='ttl.agent.disable.inheritable.for.thread.pool:true' \ -Dttl.agent.extra.d.options='-Drun-ttl-test-under-agent-with-disable-inheritable=true' \ ${CI_MORE_END_OPTS:-} cu::blue_echo 'Run agent check for Timer/TimerTask, explicit "ttl.agent.enable.timer.task"' mvu::mvn_cmd ${CI_MORE_BEGIN_OPTS:-} \ surefire:test -Denforcer.skip \ -Penable-ttl-agent-for-test \ -Dttl.agent.extra.args='ttl.agent.enable.timer.task:true' \ -Dttl.agent.extra.d.options='-Drun-ttl-test-under-agent-with-enable-timer-task=true' \ ${CI_MORE_END_OPTS:-} ) done ================================================ FILE: scripts/perf-test/memoryleak-ThreadLocal.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" cd ../.. readonly mainClass=com.alibaba.perf.memoryleak.NoMemoryLeak_ThreadLocal_NoRemove ./mvnw package exec:exec -DskipTests -Dexec.mainClass="$mainClass" ================================================ FILE: scripts/perf-test/memoryleak-TransmittableThreadLocal.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" cd ../.. readonly mainClass=com.alibaba.perf.memoryleak.NoMemoryLeak_TransmittableThreadLocal_NoRemove ./mvnw package exec:exec -DskipTests -Dexec.mainClass="$mainClass" ================================================ FILE: scripts/perf-test/tps-ThreadLocal.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" cd ../.. readonly mainClass=com.alibaba.perf.tps.CreateThreadLocalInstanceTps ./mvnw package exec:exec -DskipTests -Dexec.mainClass="$mainClass" ================================================ FILE: scripts/perf-test/tps-TransmittableThreadLocal.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" cd ../.. readonly mainClass=com.alibaba.perf.tps.CreateTransmittableThreadLocalInstanceTps ./mvnw package exec:exec -DskipTests -Dexec.mainClass="$mainClass" ================================================ FILE: scripts/release.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" readonly BASE="$(pwd)" update_version=false deploy_maven=false create_tag=false deploy_java_doc=false while getopts "djotv" arg; do case $arg in v) update_version=true ;; t) create_tag=true ;; d) deploy_maven=true ;; j) deploy_java_doc=true ;; ?) # UNKNOWN option! echo "UNKNOWN option: $arg!" exit 1 ;; esac done shift $((OPTIND-1)) echo "Current version is: $version" new_version="$(echo $version | sed 's/-SNAPSHOT//')" echo "Use new version: $new_version" $update_version && { echo '================================================================================' echo "Update version!" echo '================================================================================' # update pom version echo "update pom version..." find -name pom.xml | xargs sed "s/$version/$new_version/" -i # update version in docs echo "update badges link in docs..." find -name '*.md' -a -not -name 'release-action-list.md' | xargs sed "s/master/v$new_version/g" -i echo "update maven dependency version in docs..." sed "s#.*#$new_version#" -i *.md $create_tag && { git add -A git commit -m "release v$new_version" git tag -a "v$new_version" -m "release v$new_version" -f git push origin "v$new_version" -f } } $deploy_maven && { echo '================================================================================' echo 'deploy to maven center repo...' echo '================================================================================' ./mvnw deploy -DperformRelease=true } $deploy_java_doc && { ./mvnw install -Dmaven.test.skip=true -PsrcDoc git checkout gh-pages mv target/apidocs "apidocs/$new_version" sed "s#\".*/index.html\"#\"$new_version/index.html\"#" -i apidocs/index.html git add -A git commit -m "add javadoc for $new_version" git push } ================================================ FILE: scripts/run-agent-demo.sh ================================================ #!/bin/bash set -eEuo pipefail cd "$(dirname "$(readlink -f "$0")")" cd .. readonly mainClass=${1:-com.alibaba.demo.ttl.agent.AgentDemo} ./mvnw -Penable-ttl-agent-for-test \ package exec:exec \ -DskipTests \ -Dexec.mainClass="$mainClass" ================================================ FILE: src/package-list/java/package-list ================================================ com.sun.jarsigner com.sun.java.accessibility.util com.sun.javadoc com.sun.jdi com.sun.jdi.connect com.sun.jdi.connect.spi com.sun.jdi.event com.sun.jdi.request com.sun.management com.sun.net.httpserver com.sun.net.httpserver.spi com.sun.nio.sctp com.sun.security.auth com.sun.security.auth.callback com.sun.security.auth.login com.sun.security.auth.module com.sun.security.jgss com.sun.source.doctree com.sun.source.tree com.sun.source.util com.sun.tools.attach com.sun.tools.attach.spi com.sun.tools.doclets com.sun.tools.doclets.standard com.sun.tools.javac com.sun.tools.javadoc com.sun.tools.jconsole java.applet java.awt java.awt.color java.awt.datatransfer java.awt.desktop java.awt.dnd java.awt.event java.awt.font java.awt.geom java.awt.im java.awt.im.spi java.awt.image java.awt.image.renderable java.awt.print java.beans java.beans.beancontext java.io java.lang java.lang.annotation java.lang.instrument java.lang.invoke java.lang.management java.lang.module java.lang.ref java.lang.reflect java.math java.net java.net.spi java.nio java.nio.channels java.nio.channels.spi java.nio.charset java.nio.charset.spi java.nio.file java.nio.file.attribute java.nio.file.spi java.rmi java.rmi.activation java.rmi.dgc java.rmi.registry java.rmi.server java.security java.security.acl java.security.cert java.security.interfaces java.security.spec java.sql java.text java.text.spi java.time java.time.chrono java.time.format java.time.temporal java.time.zone java.util java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks java.util.function java.util.jar java.util.logging java.util.prefs java.util.regex java.util.spi java.util.stream java.util.zip javafx.animation javafx.application javafx.beans javafx.beans.binding javafx.beans.property javafx.beans.property.adapter javafx.beans.value javafx.collections javafx.collections.transformation javafx.concurrent javafx.css javafx.css.converter javafx.embed.swing javafx.event javafx.fxml javafx.geometry javafx.print javafx.scene javafx.scene.canvas javafx.scene.chart javafx.scene.control javafx.scene.control.cell javafx.scene.control.skin javafx.scene.effect javafx.scene.image javafx.scene.input javafx.scene.layout javafx.scene.media javafx.scene.paint javafx.scene.shape javafx.scene.text javafx.scene.transform javafx.scene.web javafx.stage javafx.util javafx.util.converter javax.accessibility javax.activation javax.activity javax.annotation javax.annotation.processing javax.crypto javax.crypto.interfaces javax.crypto.spec javax.imageio javax.imageio.event javax.imageio.metadata javax.imageio.plugins.bmp javax.imageio.plugins.jpeg javax.imageio.plugins.tiff javax.imageio.spi javax.imageio.stream javax.jnlp javax.jws javax.jws.soap javax.lang.model javax.lang.model.element javax.lang.model.type javax.lang.model.util javax.management javax.management.loading javax.management.modelmbean javax.management.monitor javax.management.openmbean javax.management.relation javax.management.remote javax.management.remote.rmi javax.management.timer javax.naming javax.naming.directory javax.naming.event javax.naming.ldap javax.naming.spi javax.net javax.net.ssl javax.print javax.print.attribute javax.print.attribute.standard javax.print.event javax.rmi javax.rmi.CORBA javax.rmi.ssl javax.script javax.security.auth javax.security.auth.callback javax.security.auth.kerberos javax.security.auth.login javax.security.auth.spi javax.security.auth.x500 javax.security.cert javax.security.sasl javax.smartcardio javax.sound.midi javax.sound.midi.spi javax.sound.sampled javax.sound.sampled.spi javax.sql javax.sql.rowset javax.sql.rowset.serial javax.sql.rowset.spi javax.swing javax.swing.border javax.swing.colorchooser javax.swing.event javax.swing.filechooser javax.swing.plaf javax.swing.plaf.basic javax.swing.plaf.metal javax.swing.plaf.multi javax.swing.plaf.nimbus javax.swing.plaf.synth javax.swing.table javax.swing.text javax.swing.text.html javax.swing.text.html.parser javax.swing.text.rtf javax.swing.tree javax.swing.undo javax.tools javax.transaction javax.transaction.xa javax.xml javax.xml.bind javax.xml.bind.annotation javax.xml.bind.annotation.adapters javax.xml.bind.attachment javax.xml.bind.helpers javax.xml.bind.util javax.xml.catalog javax.xml.crypto javax.xml.crypto.dom javax.xml.crypto.dsig javax.xml.crypto.dsig.dom javax.xml.crypto.dsig.keyinfo javax.xml.crypto.dsig.spec javax.xml.datatype javax.xml.namespace javax.xml.parsers javax.xml.soap javax.xml.stream javax.xml.stream.events javax.xml.stream.util javax.xml.transform javax.xml.transform.dom javax.xml.transform.sax javax.xml.transform.stax javax.xml.transform.stream javax.xml.validation javax.xml.ws javax.xml.ws.handler javax.xml.ws.handler.soap javax.xml.ws.http javax.xml.ws.soap javax.xml.ws.spi javax.xml.ws.spi.http javax.xml.ws.wsaddressing javax.xml.xpath jdk.dynalink jdk.dynalink.beans jdk.dynalink.linker jdk.dynalink.linker.support jdk.dynalink.support jdk.incubator.http jdk.javadoc.doclet jdk.jfr jdk.jfr.consumer jdk.jshell jdk.jshell.execution jdk.jshell.spi jdk.jshell.tool jdk.management.cmm jdk.management.jfr jdk.management.resource jdk.nashorn.api.scripting jdk.nashorn.api.tree jdk.net jdk.packager.services jdk.security.jarsigner netscape.javascript org.ietf.jgss org.omg.CORBA org.omg.CORBA_2_3 org.omg.CORBA_2_3.portable org.omg.CORBA.DynAnyPackage org.omg.CORBA.ORBPackage org.omg.CORBA.portable org.omg.CORBA.TypeCodePackage org.omg.CosNaming org.omg.CosNaming.NamingContextExtPackage org.omg.CosNaming.NamingContextPackage org.omg.Dynamic org.omg.DynamicAny org.omg.DynamicAny.DynAnyFactoryPackage org.omg.DynamicAny.DynAnyPackage org.omg.IOP org.omg.IOP.CodecFactoryPackage org.omg.IOP.CodecPackage org.omg.Messaging org.omg.PortableInterceptor org.omg.PortableInterceptor.ORBInitInfoPackage org.omg.PortableServer org.omg.PortableServer.CurrentPackage org.omg.PortableServer.POAManagerPackage org.omg.PortableServer.POAPackage org.omg.PortableServer.portable org.omg.PortableServer.ServantLocatorPackage org.omg.SendingContext org.omg.stub.java.rmi org.w3c.dom org.w3c.dom.bootstrap org.w3c.dom.css org.w3c.dom.events org.w3c.dom.html org.w3c.dom.ls org.w3c.dom.ranges org.w3c.dom.stylesheets org.w3c.dom.traversal org.w3c.dom.views org.w3c.dom.xpath org.xml.sax org.xml.sax.ext org.xml.sax.helpers ================================================ FILE: src/package-list/jetbrains-annotations/package-list ================================================ org.intellij.lang.annotations org.jetbrains.annotations ================================================ FILE: src/package-list/jsr305/package-list ================================================ javax.annotation javax.annotation.concurrent javax.annotation.meta ================================================ FILE: src/package-list/spotbugs-annotations/package-list ================================================ edu.umd.cs.findbugs.annotations ================================================ FILE: ttl-agent/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT ../pom.xml ttl-agent jar TransmittableThreadLocal(TTL) Agent ${project.name} https://github.com/alibaba/transmittable-thread-local 2022 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions Alibaba https://www.alibaba.com Jerry Lee oldratlee oldratlee(AT)gmail(DOT)com Developer +8 https://github.com/oldratlee Alibaba https://www.alibaba.com wuwen wuwen5 wuwen.55(AT)aliyun(DOT)com Developer +8 https://github.com/wuwen5 ofpay https://www.ofpay.com com.alibaba.ttl3 ttl-core ${project.version} org.javassist javassist maven-jar-plugin com.alibaba.ttl3.agent.TtlAgent ${project.build.finalName}.jar false true false maven-shade-plugin shade-when-package package shade javassist com.alibaba.ttl3.agent.transformlet.javassist org.javassist:javassist com.alibaba.ttl3:ttl-core org.javassist:javassist META-INF/MANIFEST.MF com.alibaba.ttl3:ttl-core com/alibaba/ttl3/agent/package-info.class com/alibaba/ttl3/agent/package-info.java META-INF/MANIFEST.MF config-for-jdk16+ [16,) maven-surefire-plugin **/*$* **/JavassistTest* enable-ttl-agent-for-test ${project.build.directory}/${project.artifactId}-${project.version}.jar ttl.agent.logger:STDOUT -javaagent:${ttl.built.agent.jar}=${ttl.agent.args},${ttl.agent.extra.args} -Drun-ttl-test-under-agent=true ${ttl.agent.extra.d.options} ${ttl.agent.jvm.arg} com.alibaba.demo.ttl.agent.AgentDemo maven-surefire-plugin 1 true org.javassist:javassist com.github.spotbugs:spotbugs-annotations com.google.code.findbugs:jsr305 org.jetbrains:annotations @{argLine} ${ttl.agent.jvm.args} org.codehaus.mojo exec-maven-plugin -Xmx1g -Xms256m -ea -server -Duser.language=en -Duser.country=US ${ttl.agent.jvm.arg} -classpath ${exec.mainClass} gen-src performRelease true maven-source-plugin false maven-shade-plugin shade-when-package true gen-api-doc performRelease true -linkoffline ${javadoc.ttl.base.link}/ttl-core/ ${project.basedir}/../ttl-core/target/apidocs/ ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlAgent.java ================================================ package com.alibaba.ttl3.agent; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.agent.transformlet.internal.ForkJoinTtlTransformlet; import com.alibaba.ttl3.agent.transformlet.internal.JdkExecutorTtlTransformlet; import com.alibaba.ttl3.agent.transformlet.internal.PriorityBlockingQueueTtlTransformlet; import com.alibaba.ttl3.agent.transformlet.internal.TimerTaskTtlTransformlet; import com.alibaba.ttl3.executor.TtlExecutors; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * TTL Java Agent. * *

The configuration for TTL agent

*

* Configure TTL agent via {@code -D property}({@link System#getProperties()}) or TTL agent arguments. *

    *
  1. {@code -D property} config format is: {@code -Dkey1=v2 -Dkey2=v2}
  2. *
  3. TTL agent arguments config format is {@code key1:v1,key2:v2}.
    * separate key-value pairs by {@code char ,}, and separate key-value by {@code char :}.
  4. *
* NOTE about the config sources and the precedence:
*
    *
  1. Read {@code -D property}({@link System#getProperties()}) first.
  2. *
  3. if no {@code -D property} configured(including empty property value configured by {@code -Dkey1}/{@code -Dkey1=}), read TTL Agent argument configuration.
  4. *
* Below is available TTL agent configuration keys. * *

Configuration key: Log Type

*

* The log type of TTL Java Agent is configured by key {@code ttl.agent.logger}. Since version {@code 2.6.0}. * *

    *
  • {@code ttl.agent.logger : STDERR}
    * only log to {@code stderr} when error. * This is default, when no/unrecognized configuration for key {@code ttl.agent.logger}.
  • *
  • {@code ttl.agent.logger : STDOUT}
    * Log to {@code stdout}, more info than {@code ttl.agent.logger:STDERR}; This is needed when developing.
  • *
*

* Configuration example: * *

    *
  1. {@code -Dttl.agent.logger=STDOUT}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT}
  4. *
* *

Configuration key: Disable inheritable for thread pool

*

* Enable "disable inheritable" for thread pool, configured by key {@code ttl.agent.disable.inheritable.for.thread.pool}. * When no configuration for this key, default is {@code false}(aka. do NOT disable inheritable). Since version {@code 2.10.1}. * *

    *
  • rewrite the {@link java.util.concurrent.ThreadFactory} constructor parameter * of {@link java.util.concurrent.ThreadPoolExecutor} * to {@code DisableInheritableThreadFactory} * by util method {@link TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) getDisableInheritableThreadFactory}. *
  • *
  • rewrite the {@link java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory} constructor parameter * of {@link java.util.concurrent.ForkJoinPool} * to {@code DisableInheritableForkJoinWorkerThreadFactory} * by util method {@link TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) getDisableInheritableForkJoinWorkerThreadFactory}. *
  • *
* More info about "disable inheritable" see {@link com.alibaba.ttl3.TransmittableThreadLocal}. *

* Configuration example: * *

    *
  1. {@code -Dttl.agent.disable.inheritable.for.thread.pool=true}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.disable.inheritable.for.thread.pool:true}
  4. *
* *

Configuration key: Enable TimerTask class decoration

*

* Enable TimerTask class decoration is configured by key {@code ttl.agent.enable.timer.task}. * Since version {@code 2.7.0}. *

* When no configuration for this key, default is {@code true}(aka. enabled).
* Note: Since version {@code 2.11.2} the default value is {@code true}(enable TimerTask class decoration); * Before version {@code 2.11.1} default value is {@code false}. *

* Configuration example: * *

    *
  1. {@code -Dttl.agent.enable.timer.task=false}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false}
  4. *
* *

Configuration key: logging the transform class received by TTL Agent

*

* Enable logging the transform class received by TTL Agent by key {@code ttl.agent.log.class.transform}, * default is {@code false}(aka. do NOT logging the transform class received by TTL Agent). * Since version {@code 3.0.0}. *

* Configuration example: * *

    *
  1. {@code -Dttl.agent.log.class.transform=true}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.log.class.transform:true}
  4. *
* *

Multi key configuration example

*

* For {@code -D property} config, simply specify multiply {@code -D property}, example:
* {@code -Dttl.agent.logger=STDOUT -Dttl.agent.disable.inheritable.for.thread.pool=true} *

* For TTL agent arguments config, example:
* {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT,ttl.agent.disable.inheritable.for.thread.pool:true} * *

About boot classpath for TTL agent

*

* NOTE: Since {@code v2.6.0}, TTL agent jar will auto add self to {@code boot classpath}.
* But you should NOT modify the downloaded TTL jar file name in the maven repo(eg: {@code transmittable-thread-local-2.x.y.jar}).
* if you modified the downloaded TTL agent jar file name(eg: {@code ttl-foo-name-changed.jar}), * you must add TTL agent jar to {@code boot classpath} manually * by java option {@code -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar}. *

* The implementation of auto adding self agent jar to {@code boot classpath} use * the {@code Boot-Class-Path} property of manifest file({@code META-INF/MANIFEST.MF}) in the TTL Java Agent Jar: * *

*
*
Boot-Class-Path
*
* A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). * These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed. *
*
*
*

* More info about {@code Boot-Class-Path} see * The mechanism for instrumentation. * * @author Jerry Lee (oldratlee at gmail dot com) * @see Instrumentation * @see The mechanism for instrumentation * @see JAR File Specification - JAR Manifest * @see Working with Manifest Files - The Java™ Tutorials * @see com.alibaba.ttl3.TransmittableThreadLocal * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.ForkJoinPool * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) * @see java.util.TimerTask */ public final class TtlAgent implements TtlAgentStatus { /** * the TTL agent configuration key: Log Type * * @see TtlAgent */ public static final String TTL_AGENT_LOGGER_KEY = "ttl.agent.logger"; /** * the TTL agent configuration key: Disable inheritable for thread pool * * @see TtlAgent */ public static final String TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY = "ttl.agent.disable.inheritable.for.thread.pool"; /** * the TTL agent configuration key: Enable TimerTask class decoration * * @see TtlAgent */ public static final String TTL_AGENT_ENABLE_TIMER_TASK_KEY = "ttl.agent.enable.timer.task"; /** * the TTL agent configuration key: logging the transform class received by TTL Agent * * @see TtlAgent */ public static final String TTL_AGENT_LOG_CLASS_TRANSFORM_KEY = "ttl.agent.log.class.transform"; // ======== TTL Agent internal States ======== private static volatile Map kvs; private static volatile boolean ttlAgentLoaded = false; /** * Entrance method of TTL Java Agent. * * @see TtlAgent */ public static void premain(final String agentArgs, @NonNull final Instrumentation inst) { kvs = TtlAgentHelper.splitCommaColonStringToKV(agentArgs); Logger.setLoggerImplType(getLoggerType()); final Logger logger = Logger.getLogger(TtlAgent.class); try { logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst); logger.info(logTtlAgentConfig()); final List transformletList = new ArrayList<>(); transformletList.add(new JdkExecutorTtlTransformlet()); transformletList.add(new PriorityBlockingQueueTtlTransformlet()); transformletList.add(new ForkJoinTtlTransformlet()); if (isEnableTimerTask()) transformletList.add(new TimerTaskTtlTransformlet()); final ClassFileTransformer transformer = new TtlTransformer(transformletList, isLogClassTransform()); inst.addTransformer(transformer, true); logger.info("[TtlAgent.premain] add Transformer " + transformer.getClass().getName() + " success"); logger.info("[TtlAgent.premain] end"); ttlAgentLoaded = true; } catch (Exception e) { String msg = "Fail to load TtlAgent , cause: " + e.toString(); logger.error(msg, e); throw new IllegalStateException(msg, e); } } private static String logTtlAgentConfig() { return "TTL Agent configurations:" + "\n " + TTL_AGENT_LOGGER_KEY + "=" + getLoggerType() + "\n " + TTL_AGENT_LOG_CLASS_TRANSFORM_KEY + "=" + isLogClassTransform() + "\n " + TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY + "=" + isDisableInheritableForThreadPool() + "\n " + TTL_AGENT_ENABLE_TIMER_TASK_KEY + "=" + isEnableTimerTask(); } /** * Whether TTL agent is loaded. */ public boolean isTtlAgentLoaded() { return ttlAgentLoaded; } /** * Whether disable inheritable for thread pool is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. *

* Same as {@code isBooleanOptionSet(TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY)}. * * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) * @see com.alibaba.ttl3.TransmittableThreadLocal * @see TtlAgent * @see #isBooleanOptionSet(String) * @see #TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY */ public static boolean isDisableInheritableForThreadPool() { return isBooleanOptionSet(TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY); } /** * Whether timer task is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. *

* Same as {@code isBooleanOptionSet(TTL_AGENT_ENABLE_TIMER_TASK_KEY, true)}. * * @see java.util.Timer * @see java.util.TimerTask * @see TtlAgent * @see #isBooleanOptionSet(String, boolean) * @see #TTL_AGENT_ENABLE_TIMER_TASK_KEY */ public static boolean isEnableTimerTask() { return isBooleanOptionSet(TTL_AGENT_ENABLE_TIMER_TASK_KEY, true); } /** * Whether logging the transform class received by {@link TtlAgent}. *

* Same as {@code isBooleanOptionSet(TTL_AGENT_LOG_CLASS_TRANSFORM_KEY)}. * * @see TtlAgent * @see #isBooleanOptionSet(String) * @see #TTL_AGENT_LOG_CLASS_TRANSFORM_KEY */ public static boolean isLogClassTransform() { return isBooleanOptionSet(TTL_AGENT_LOG_CLASS_TRANSFORM_KEY); } /** * Get the TTL Agent Log type. *

* Same as {@code getStringOptionValue(TTL_AGENT_LOGGER_KEY, Logger.STDERR)}. * * @see Logger * @see Logger#STDERR * @see Logger#STDOUT * @see TtlAgent * @see #getStringOptionValue(String, String) * @see #TTL_AGENT_LOGGER_KEY */ @NonNull public static String getLoggerType() { return getStringOptionValue(TTL_AGENT_LOGGER_KEY, Logger.STDERR); } // ======== Generic Option Getters ======== /** * Generic Option Getters for {@code boolean type} option. *

* Same as {@code isBooleanOptionSet(key, false)}. * * @see #isBooleanOptionSet(String, boolean) * @see TtlAgent */ public static boolean isBooleanOptionSet(@NonNull String key) { return isBooleanOptionSet(key, false); } /** * Generic Option Getters for {@code boolean type} option. * * @see TtlAgent */ public static boolean isBooleanOptionSet(@NonNull String key, boolean defaultValueIfKeyAbsent) { return TtlAgentHelper.isBooleanOptionSet(kvs, key, defaultValueIfKeyAbsent); } /** * Generic Option Getters for {@code string type} option. *

* usage example: *

* if {@code -Dttl.agent.logger=STDOUT} or * TTL Agent configuration is {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT}, * {@code getOptionValue("ttl.agent.logger")} return {@code STDOUT}. * * @see TtlAgent */ @NonNull public static String getStringOptionValue(@NonNull String key, @NonNull String defaultValue) { return TtlAgentHelper.getStringOptionValue(kvs, key, defaultValue); } /** * Generic Option Getters for {@code string list type} option. *

* TTL configuration use {@code |} to separate items. *

* usage example:
* if {@code -Dfoo.list=v1|v2|v3} or * TTL Agent configuration is {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=foo.list:v1|v2|v3}, * {@code getOptionValue("foo.list")} return {@code [v1, v2, v3]}. * * @see TtlAgent */ @NonNull static List getOptionStringListValues(@NonNull String key) { return TtlAgentHelper.getOptionStringListValues(kvs, key); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlAgent() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlAgentHelper.java ================================================ package com.alibaba.ttl3.agent; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.*; /** * @author Jerry Lee (oldratlee at gmail dot com) */ final class TtlAgentHelper { // ======== Option Getter Methods ======== static boolean isBooleanOptionSet( @Nullable final Map kvs, @NonNull String key, boolean defaultValueIfKeyAbsent ) { return isBooleanOptionSet(kvs, key, defaultValueIfKeyAbsent, true); } static boolean isBooleanOptionSet( @Nullable final Map kvs, @NonNull String key, boolean defaultValueIfKeyAbsent, boolean defaultValueIfValueAbsent ) { final String value; final Properties properties = System.getProperties(); if (properties.containsKey(key)) { value = properties.getProperty(key).trim(); } else { if (kvs == null) return defaultValueIfKeyAbsent; final boolean containsKey = kvs.containsKey(key); if (!containsKey) return defaultValueIfKeyAbsent; value = kvs.get(key).trim(); } // if value is blank if (value.isEmpty()) return defaultValueIfValueAbsent; return !"false".equalsIgnoreCase(value); } @NonNull static String getStringOptionValue( @Nullable final Map kvs, @NonNull String key, @NonNull String defaultValue ) { final String value; final Properties properties = System.getProperties(); if (properties.containsKey(key)) { value = properties.getProperty(key).trim(); } else { if (kvs == null) return defaultValue; final boolean containsKey = kvs.containsKey(key); if (!containsKey) return defaultValue; value = kvs.get(key).trim(); } // if value is blank if (value.isEmpty()) return defaultValue; return value; } @NonNull @SuppressWarnings("unchecked") static List getOptionStringListValues(@Nullable final Map kvs, @NonNull String key) { final String value; final Properties properties = System.getProperties(); if (properties.containsKey(key)) { value = properties.getProperty(key); } else { if (kvs == null) return Collections.EMPTY_LIST; value = kvs.get(key); } return splitListStringToStringList(value); } // ======== Simple Parse Util Methods ======== /** * Split {@code json} like String({@code "k1:v1,k2:v2"}) to KV map({@code "k1"->"v1", "k2"->"v2"}). */ @NonNull static Map splitCommaColonStringToKV(@Nullable final String commaColonString) { final Map ret = new HashMap<>(); if (commaColonString == null || commaColonString.trim().length() == 0) return ret; final String[] splitKvArray = commaColonString.trim().split("\\s*,\\s*"); for (String kvString : splitKvArray) { final String[] kv = kvString.trim().split("\\s*:\\s*"); if (kv.length == 0) continue; if (kv.length == 1) ret.put(kv[0], ""); else ret.put(kv[0], kv[1]); } return ret; } /** * Split String {@code "v1|v2|v3"} to String List({@code [v1, v2, v3]}). */ @NonNull static List splitListStringToStringList(@Nullable String listString) { final List ret = new ArrayList<>(); if (listString == null || listString.trim().length() == 0) return ret; final String[] split = listString.trim().split("\\s*\\|\\s*"); for (String s : split) { if (s.length() == 0) continue; ret.add(s); } return ret; } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlAgentHelper() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlExtensionTransformletManager.java ================================================ package com.alibaba.ttl3.agent; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.ClassInfo; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.CannotCompileException; import javassist.NotFoundException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.*; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.getLocationUrlOfClass; import static java.nio.charset.StandardCharsets.UTF_8; /** * @author Jerry Lee (oldratlee at gmail dot com) */ final class TtlExtensionTransformletManager { private static final Logger logger = Logger.getLogger(TtlExtensionTransformletManager.class); private static final String TTL_AGENT_EXTENSION_TRANSFORMLET_FILE = "META-INF/ttl.agent.transformlets"; public TtlExtensionTransformletManager() { } public String extensionTransformletDoTransform(@NonNull final ClassInfo classInfo) throws NotFoundException, CannotCompileException, IOException { final Map transformlets = classLoader2ExtensionTransformletsIncludeParentCL.get(classInfo.getClassLoader()); if (transformlets == null) return null; for (Map.Entry entry : transformlets.entrySet()) { final String className = entry.getKey(); final TtlTransformlet transformlet = entry.getValue(); transformlet.doTransform(classInfo); if (classInfo.isModified()) { return className; } } return null; } // NOTE: use WeakHashMap as a Set collection, value is always null. private final WeakHashMap collectedClassLoaderHistory = new WeakHashMap<>(512); // Map: ExtensionTransformlet ClassLoader -> ExtensionTransformlet ClassName -> ExtensionTransformlet instance(not include from parent classloader) private final WeakHashMap> classLoader2ExtensionTransformlets = new WeakHashMap<>(512); // Map: ExtensionTransformlet ClassLoader -> ExtensionTransformlet ClassName -> ExtensionTransformlet instance(include from parent classloader) private final WeakHashMap> classLoader2ExtensionTransformletsIncludeParentCL = new WeakHashMap<>(512); public void collectExtensionTransformlet(@NonNull final ClassInfo classInfo) throws IOException { final ClassLoader classLoader = classInfo.getClassLoader(); // classloader may null be if the bootstrap loader, // which classloader must contains NO Ttl Agent Extension Transformlet, so just safe skip if (classLoader == null) return; // this classLoader is collected, so skip collection if (collectedClassLoaderHistory.containsKey(classLoader)) return; collectedClassLoaderHistory.put(classLoader, null); logger.info("[TtlExtensionTransformletCollector] collecting TTL Extension Transformlets from classloader " + classLoader); final LinkedHashSet extensionTransformletClassNames = readExtensionTransformletClassNames(classLoader); final String foundMsgHead = "[TtlExtensionTransformletCollector] found TTL Extension Transformlet class "; final String failLoadMsgHead = "[TtlExtensionTransformletCollector] fail to load TTL Extension Transformlet "; final Map> loadedTransformlet = loadExtensionInstances(classLoader, extensionTransformletClassNames, TtlTransformlet.class, foundMsgHead, failLoadMsgHead); mergeToClassLoader2ExtensionTransformlet(classLoader2ExtensionTransformlets, loadedTransformlet); updateClassLoader2ExtensionTransformletsIncludeParentCL( classLoader2ExtensionTransformlets, classLoader2ExtensionTransformletsIncludeParentCL); } // extension transformlet configuration file URL location string -> URL contained extension transformlet class names private final Map> redExtensionTransformletFileHistory = new HashMap<>(); private LinkedHashSet readExtensionTransformletClassNames(ClassLoader classLoader) throws IOException { final Enumeration extensionFiles = classLoader.getResources(TTL_AGENT_EXTENSION_TRANSFORMLET_FILE); final Pair, Set> pair = readLinesFromExtensionFiles(extensionFiles, redExtensionTransformletFileHistory); final LinkedHashSet extensionTransformletClassNames = pair.first; final Set stringUrls = pair.second; if (!stringUrls.isEmpty()) logger.info("[TtlExtensionTransformletCollector] found TTL Extension Transformlet configuration files from classloader " + classLoader + " : " + stringUrls); return extensionTransformletClassNames; } private static void mergeToClassLoader2ExtensionTransformlet( Map> destination, Map> loadedTransformlets ) { for (Map.Entry> entry : loadedTransformlets.entrySet()) { final ClassLoader classLoader = entry.getKey(); final Set transformlets = entry.getValue(); Map className2Transformlets = destination.computeIfAbsent(classLoader, k -> new HashMap<>()); for (TtlTransformlet t : transformlets) { final String className = t.getClass().getName(); if (className2Transformlets.containsKey(className)) continue; className2Transformlets.put(className, t); logger.info("[TtlExtensionTransformletCollector] add TTL Extension Transformlet " + className + " success"); } } } static void updateClassLoader2ExtensionTransformletsIncludeParentCL( Map> classLoader2ExtensionTransformlets, Map> classLoader2ExtensionTransformletsIncludeParentCL ) { for (Map.Entry> entry : classLoader2ExtensionTransformlets.entrySet()) { final ClassLoader classLoader = entry.getKey(); final Map merged = childClassLoaderFirstMergeTransformlets(classLoader2ExtensionTransformlets, classLoader); classLoader2ExtensionTransformletsIncludeParentCL.put(classLoader, merged); } } static Map childClassLoaderFirstMergeTransformlets( Map> classLoader2Transformlet, ClassLoader classLoader ) { Map ret = new HashMap<>(); final ArrayDeque chain = new ArrayDeque<>(); chain.add(classLoader); while (classLoader.getParent() != null) { classLoader = classLoader.getParent(); chain.addFirst(classLoader); } for (ClassLoader loader : chain) { final Map m = classLoader2Transformlet.get(loader); if (m == null) continue; ret.putAll(m); } return ret; } // ======== Extension load util methods ======== static Map> loadExtensionInstances( ClassLoader classLoader, LinkedHashSet instanceClassNames, Class superType, String foundMsgHead, String failLoadMsgHead ) { Map> ret = new HashMap<>(); for (final String className : instanceClassNames) { try { final Class clazz = classLoader.loadClass(className); if (!superType.isAssignableFrom(clazz)) { final String msg = foundMsgHead + className + " from classloader " + classLoader + " at location " + getLocationUrlOfClass(clazz) + ", but NOT subtype of " + superType.getName() + ", ignored!"; logger.error(msg); continue; } Object instance = clazz.getDeclaredConstructor().newInstance(); final ClassLoader actualClassLoader = instance.getClass().getClassLoader(); Set set = ret.computeIfAbsent(actualClassLoader, k -> new HashSet<>()); set.add(superType.cast(instance)); final String msg = foundMsgHead + className + ", and loaded from classloader " + classLoader + " at location " + getLocationUrlOfClass(clazz); logger.info(msg); } catch (ClassNotFoundException e) { final String msg = failLoadMsgHead + className + " from classloader " + classLoader + ", cause: " + e.toString(); logger.warn(msg, e); } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { final String msg = failLoadMsgHead + className + " from classloader " + classLoader + ", cause: " + e.toString(); logger.error(msg, e); } } return ret; } // return: read lines from URL, url strings @NonNull static Pair, Set> readLinesFromExtensionFiles( /* input */ @NonNull Enumeration extensionFiles, /* input/output, map url string -> content lines */ @NonNull Map> redExtensionFilesHistory ) { final LinkedHashSet mergedLines = new LinkedHashSet<>(); final Set stringUrls = new HashSet<>(); while (extensionFiles.hasMoreElements()) { final URL url = extensionFiles.nextElement(); final String urlString = url.toString(); stringUrls.add(urlString); LinkedHashSet lines; if (redExtensionFilesHistory.containsKey(urlString)) { lines = redExtensionFilesHistory.get(urlString); } else { lines = readLines(url); redExtensionFilesHistory.put(urlString, lines); } mergedLines.addAll(lines); } return new Pair<>(mergedLines, stringUrls); } /** * this method is modified based on {@link ServiceLoader} */ @SuppressWarnings("StatementWithEmptyBody") static LinkedHashSet readLines(URL extensionFile) { InputStream inputStream = null; BufferedReader reader = null; LinkedHashSet names = new LinkedHashSet<>(); try { inputStream = extensionFile.openStream(); reader = new BufferedReader(new InputStreamReader(inputStream, UTF_8)); int lineNum = 1; while ((lineNum = parseLine(extensionFile, reader, lineNum, names)) >= 0) ; } catch (IOException x) { logger.error("Error reading configuration file " + extensionFile, x); } finally { try { if (reader != null) reader.close(); if (inputStream != null) inputStream.close(); } catch (IOException y) { logger.warn("Error closing configuration file " + extensionFile, y); } } return names; } /** * this method is modified based on {@link ServiceLoader} */ private static int parseLine(URL url, BufferedReader reader, int lineNum, LinkedHashSet names) throws IOException { String line = reader.readLine(); if (line == null) { return -1; } // remove comments that start with `#` int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); int n = line.length(); if (n != 0) { if ((line.indexOf(' ') >= 0) || (line.indexOf('\t') >= 0)) { logger.error("Illegal syntax " + line + "in configuration file" + url + ", contains space or tab; ignore this line!"); return lineNum + 1; } int cp = line.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) { logger.error("Illegal extension class name " + line + " in configuration file " + url + "; ignore this line!"); return lineNum + 1; } for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = line.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { logger.error("Illegal extension class name: " + line + " in configuration file " + url + "; ignore this line!"); return lineNum + 1; } } names.add(line); } return lineNum + 1; } static class Pair { T first; U second; public Pair(T first, U second) { this.first = first; this.second = second; } } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/TtlTransformer.java ================================================ package com.alibaba.ttl3.agent; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.ClassInfo; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.isClassUnderPackage; /** * TTL {@link ClassFileTransformer} of Java Agent * * @author Jerry Lee (oldratlee at gmail dot com) * @see ClassFileTransformer * @see The mechanism for instrumentation */ public class TtlTransformer implements ClassFileTransformer { private static final Logger logger = Logger.getLogger(TtlTransformer.class); /** * "null if no transform is performed", * see {@code @return} of {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} */ @SuppressFBWarnings({"EI_EXPOSE_REP"}) // [ERROR] com.alibaba.ttl3.agent.TtlTransformer.transform(ClassLoader, String, Class, ProtectionDomain, byte[]) // may expose internal representation by returning TtlTransformer.NO_TRANSFORM // the value is null, so there is NO "EI_EXPOSE_REP" problem actually. private static final byte[] NO_TRANSFORM = null; private final TtlExtensionTransformletManager extensionTransformletManager; private final List transformletList = new ArrayList<>(); private final boolean logClassTransform; TtlTransformer(List transformletList, boolean logClassTransform) { extensionTransformletManager = new TtlExtensionTransformletManager(); this.logClassTransform = logClassTransform; for (TtlTransformlet ttlTransformlet : transformletList) { this.transformletList.add(ttlTransformlet); logger.info("[TtlTransformer] add Transformlet " + ttlTransformlet.getClass().getName()); } } /** * info about class loader: may be null if the bootstrap loader. *

* more info see {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} */ @Override public final byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class classBeingRedefined, final ProtectionDomain protectionDomain, @NonNull final byte[] classFileBuffer) { try { // Lambda has no class file, no need to transform, just return. if (classFile == null) return NO_TRANSFORM; final ClassInfo classInfo = new ClassInfo(classFile, classFileBuffer, loader); if (isClassUnderPackage(classInfo.getClassName(), "com.alibaba.ttl")) return NO_TRANSFORM; if (isClassUnderPackage(classInfo.getClassName(), "java.lang")) return NO_TRANSFORM; if (logClassTransform) logger.info("[TtlTransformer] transforming " + classInfo.getClassName() + " from classloader " + classInfo.getClassLoader() + " at location " + classInfo.getLocationUrl()); extensionTransformletManager.collectExtensionTransformlet(classInfo); for (TtlTransformlet transformlet : transformletList) { transformlet.doTransform(classInfo); if (classInfo.isModified()) { logger.info("[TtlTransformer] " + transformlet.getClass().getName() + " transformed " + classInfo.getClassName() + " from classloader " + classInfo.getClassLoader() + " at location " + classInfo.getLocationUrl()); return classInfo.getCtClass().toBytecode(); } } final String transformlet = extensionTransformletManager.extensionTransformletDoTransform(classInfo); if (classInfo.isModified()) { logger.info("[TtlTransformer] " + transformlet + " transformed " + classInfo.getClassName() + " from classloader " + classInfo.getClassLoader() + " at location " + classInfo.getLocationUrl()); return classInfo.getCtClass().toBytecode(); } } catch (Throwable t) { String msg = "[TtlTransformer] fail to transform class " + classFile + ", cause: " + t.toString(); logger.error(msg, t); throw new IllegalStateException(msg, t); } return NO_TRANSFORM; } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/logging/Logger.java ================================================ package com.alibaba.ttl3.agent.logging; import com.alibaba.ttl3.agent.TtlAgent; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Level; /** * Logger adaptor for ttl TTL agent/transformlet. Only use for TTL agent/transformlet! * * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlTransformlet * @see TtlAgent */ public abstract class Logger { public static final String STDOUT = "STDOUT"; public static final String STDERR = "STDERR"; private static volatile int loggerImplType = -1; public static void setLoggerImplType(String type) { if (loggerImplType != -1) { throw new IllegalStateException("TTL logger implementation type is already set! type = " + loggerImplType); } if (STDERR.equalsIgnoreCase(type)) loggerImplType = 0; else if (STDOUT.equalsIgnoreCase(type)) loggerImplType = 1; else loggerImplType = 0; } /** * Only for test code */ public static void setLoggerImplTypeIfNotSetYet(String type) { if (loggerImplType == -1) setLoggerImplType(type); } public static Logger getLogger(Class clazz) { if (loggerImplType == -1) throw new IllegalStateException("TTL logger implementation type is NOT set!"); switch (loggerImplType) { case 1: return new StdOutLogger(clazz); default: return new StdErrorLogger(clazz); } } final Class logClass; private Logger(Class logClass) { this.logClass = logClass; } public void info(String msg) { log(Level.INFO, msg, null); } public void warn(String msg) { log(Level.WARNING, msg, null); } public void warn(String msg, Throwable thrown) { log(Level.WARNING, msg, thrown); } public void error(String msg) { log(Level.SEVERE, msg, null); } public void error(String msg, Throwable thrown) { log(Level.SEVERE, msg, thrown); } protected abstract void log(Level level, String msg, Throwable thrown); private static class StdErrorLogger extends Logger { StdErrorLogger(Class clazz) { super(clazz); } @Override public void log(Level level, String msg, Throwable thrown) { if (level == Level.SEVERE) { final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); System.err.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); if (thrown != null) thrown.printStackTrace(); } } } private static class StdOutLogger extends Logger { StdOutLogger(Class clazz) { super(clazz); } @Override public void log(Level level, String msg, Throwable thrown) { final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); System.out.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); if (thrown != null) thrown.printStackTrace(System.out); } } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/logging/package-info.java ================================================ /** * TTL Agent Logger. Only use for TTL agent/transformlet. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.agent.logging.Logger */ package com.alibaba.ttl3.agent.logging; ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/package-info.java ================================================ /** * TTL Agent. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.agent.TtlAgent * @see The mechanism for instrumentation */ package com.alibaba.ttl3.agent; ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/ClassInfo.java ================================================ package com.alibaba.ttl3.agent.transformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javassist.ClassPool; import javassist.CtClass; import javassist.LoaderClassPath; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URL; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.getLocationUrlOfClass; /** * Class Info for {@link TtlTransformlet}. * * Caution:
* Do NOT load {@link Class} which is transforming, or the transform will lose effectiveness. * * @author Jerry Lee (oldratlee at gmail dot com) */ public class ClassInfo { private final String transformerClassFile; private final String className; private final byte[] classFileBuffer; private final ClassLoader loader; // SuppressFBWarnings for classFileBuffer/loader parameter: // [ERROR] new com.alibaba.ttl3.agent.transformlet.ClassInfo(String, byte[], ClassLoader) // may expose internal representation by storing an externally mutable object // into ClassInfo.classFileBuffer/loader public ClassInfo(@NonNull String transformerClassFile, @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP2"}) byte[] classFileBuffer, @Nullable @SuppressFBWarnings({"EI_EXPOSE_REP2"}) ClassLoader loader) { this.transformerClassFile = transformerClassFile; this.className = toClassName(transformerClassFile); this.classFileBuffer = classFileBuffer; this.loader = loader; } @NonNull public String getClassName() { return className; } private CtClass ctClass; public URL getLocationUrl() throws IOException { return getLocationUrlOfClass(getCtClass()); } @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP"}) // [ERROR] Medium: com.alibaba.ttl3.agent.transformlet.ClassInfo.getCtClass() // may expose internal representation // by returning ClassInfo.ctClass [com.alibaba.ttl3.agent.transformlet.ClassInfo] public CtClass getCtClass() throws IOException { if (ctClass != null) return ctClass; final ClassPool classPool = new ClassPool(true); if (loader == null) { classPool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader())); } else { classPool.appendClassPath(new LoaderClassPath(loader)); } final CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classFileBuffer), false); clazz.defrost(); this.ctClass = clazz; return clazz; } private boolean modified = false; public boolean isModified() { return modified; } public void setModified() { this.modified = true; } @SuppressFBWarnings({"EI_EXPOSE_REP"}) // [ERROR] Medium: com.alibaba.ttl3.agent.transformlet.ClassInfo.getClassLoader() // may expose internal representation // by returning ClassInfo.loader [com.alibaba.ttl3.agent.transformlet.ClassInfo] public ClassLoader getClassLoader() { return loader; } private static String toClassName(@NonNull final String classFile) { return classFile.replace('/', '.'); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/TtlTransformlet.java ================================================ package com.alibaba.ttl3.agent.transformlet; import com.alibaba.ttl3.agent.TtlTransformer; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.CannotCompileException; import javassist.NotFoundException; import java.io.IOException; /** * TTL {@code Transformlet}. * * @author Jerry Lee (oldratlee at gmail dot com) */ public interface TtlTransformlet { /** * info about class loader: may be null if the bootstrap loader. *

* more info see {@link java.lang.instrument.ClassFileTransformer#transform(ClassLoader, String, Class, java.security.ProtectionDomain, byte[])} * * @see TtlTransformer#transform(ClassLoader, String, Class, java.security.ProtectionDomain, byte[]) * @see java.lang.instrument.ClassFileTransformer#transform */ void doTransform(@NonNull ClassInfo classInfo) throws CannotCompileException, NotFoundException, IOException; } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/helper/AbstractExecutorTtlTransformlet.java ================================================ package com.alibaba.ttl3.agent.transformlet.helper; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.ClassInfo; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.agent.transformlet.internal.PriorityBlockingQueueTtlTransformlet; import com.alibaba.ttl3.spi.TtlAttachmentsDelegate; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javassist.*; import java.io.IOException; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.isClassAtPackageJavaUtil; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; /** * Abstract {@link TtlTransformlet} for {@link java.util.concurrent.Executor} and its subclass. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.concurrent.Executor * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.Executors * @see PriorityBlockingQueueTtlTransformlet */ public abstract class AbstractExecutorTtlTransformlet implements TtlTransformlet { protected static final String RUNNABLE_CLASS_NAME = "java.lang.Runnable"; protected static final String CALLABLE_CLASS_NAME = "java.util.concurrent.Callable"; protected static final String TTL_RUNNABLE_CLASS_NAME = "com.alibaba.ttl3.TtlRunnable"; protected static final String TTL_CALLABLE_CLASS_NAME = "com.alibaba.ttl3.TtlCallable"; protected static final String THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ThreadFactory"; protected static final String THREAD_POOL_EXECUTOR_CLASS_NAME = "java.util.concurrent.ThreadPoolExecutor"; protected final Logger logger = Logger.getLogger(getClass()); protected final Set executorClassNames; protected final boolean disableInheritableForThreadPool; private final Map paramTypeNameToDecorateMethodClass = new HashMap<>(); /** * @param executorClassNames the executor class names to be transformed */ public AbstractExecutorTtlTransformlet(Set executorClassNames, boolean disableInheritableForThreadPool) { this.executorClassNames = Collections.unmodifiableSet(executorClassNames); this.disableInheritableForThreadPool = disableInheritableForThreadPool; paramTypeNameToDecorateMethodClass.put(RUNNABLE_CLASS_NAME, TTL_RUNNABLE_CLASS_NAME); paramTypeNameToDecorateMethodClass.put(CALLABLE_CLASS_NAME, TTL_CALLABLE_CLASS_NAME); } @Override public final void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { // work-around ClassCircularityError: // https://github.com/alibaba/transmittable-thread-local/issues/278 // https://github.com/alibaba/transmittable-thread-local/issues/234 if (isClassAtPackageJavaUtil(classInfo.getClassName())) return; final CtClass clazz = classInfo.getCtClass(); if (executorClassNames.contains(classInfo.getClassName())) { for (CtMethod method : clazz.getDeclaredMethods()) { updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); } if (disableInheritableForThreadPool) updateConstructorDisableInheritable(clazz); classInfo.setModified(); } else { if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || clazz.isAnnotation()) { return; } if (!clazz.subclassOf(clazz.getClassPool().get(THREAD_POOL_EXECUTOR_CLASS_NAME))) return; logger.info("Transforming class " + classInfo.getClassName()); final boolean modified = updateBeforeAndAfterExecuteMethodOfExecutorSubclass(clazz); if (modified) classInfo.setModified(); } } /** * @see TtlTransformletHelper#doAutoWrap(Runnable) * @see TtlTransformletHelper#doAutoWrap(Callable) */ @SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE") // [ERROR] Format string should use %n rather than \n private void updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(@NonNull final CtMethod method) throws NotFoundException, CannotCompileException { final int modifiers = method.getModifiers(); if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) return; CtClass[] parameterTypes = method.getParameterTypes(); StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (paramTypeNameToDecorateMethodClass.containsKey(paramTypeName)) { String code = String.format( // auto decorate to TTL wrapper "$%d = helper.transformlet.com.alibaba.ttl3.agent.TtlTransformletHelper.doAutoWrap($% 0) { logger.info("insert code before method " + signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ":\n" + insertCode); method.insertBefore(insertCode.toString()); } } /** * @see com.alibaba.ttl3.executor.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) */ private void updateConstructorDisableInheritable(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException { for (CtConstructor constructor : clazz.getDeclaredConstructors()) { final CtClass[] parameterTypes = constructor.getParameterTypes(); final StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (THREAD_FACTORY_CLASS_NAME.equals(paramTypeName)) { String code = String.format("$%d = com.alibaba.ttl3.executor.TtlExecutors.getDisableInheritableThreadFactory($% 0) { logger.info("insert code before constructor " + signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + insertCode); constructor.insertBefore(insertCode.toString()); } } } /** * @see TtlAttachmentsDelegate#unwrapIfIsAutoWrapper(Object) */ private boolean updateBeforeAndAfterExecuteMethodOfExecutorSubclass(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException { final CtClass runnableClass = clazz.getClassPool().get(RUNNABLE_CLASS_NAME); final CtClass threadClass = clazz.getClassPool().get("java.lang.Thread"); final CtClass throwableClass = clazz.getClassPool().get("java.lang.Throwable"); boolean modified = false; try { final CtMethod beforeExecute = clazz.getDeclaredMethod("beforeExecute", new CtClass[]{threadClass, runnableClass}); // unwrap runnable if IsAutoWrapper String code = "$2 = com.alibaba.ttl3.spi.TtlAttachmentsDelegate.unwrapIfIsAutoWrapper($2);"; logger.info("insert code before method " + signatureOfMethod(beforeExecute) + " of class " + beforeExecute.getDeclaringClass().getName() + ": " + code); beforeExecute.insertBefore(code); modified = true; } catch (NotFoundException e) { // clazz does not override beforeExecute method, do nothing. } try { final CtMethod afterExecute = clazz.getDeclaredMethod("afterExecute", new CtClass[]{runnableClass, throwableClass}); // unwrap runnable if IsAutoWrapper String code = "$1 = com.alibaba.ttl3.spi.TtlAttachmentsDelegate.unwrapIfIsAutoWrapper($1);"; logger.info("insert code before method " + signatureOfMethod(afterExecute) + " of class " + afterExecute.getDeclaringClass().getName() + ": " + code); afterExecute.insertBefore(code); modified = true; } catch (NotFoundException e) { // clazz does not override afterExecute method, do nothing. } return modified; } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/helper/TtlTransformletHelper.java ================================================ package com.alibaba.ttl3.agent.transformlet.helper; import com.alibaba.ttl3.TtlCallable; import com.alibaba.ttl3.TtlRunnable; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.spi.TtlEnhanced; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javassist.*; import java.lang.reflect.Modifier; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.concurrent.Callable; import static com.alibaba.ttl3.spi.TtlAttachmentsDelegate.setAutoWrapperAttachment; import static com.alibaba.ttl3.transmitter.Transmitter.capture; /** * Helper methods for {@link TtlTransformlet} implementation. * * @author Jerry Lee (oldratlee at gmail dot com) */ public final class TtlTransformletHelper { private static final Logger logger = Logger.getLogger(TtlTransformletHelper.class); // ======== Javassist/Class Helper ======== /** * Output string like {@code public ScheduledFuture scheduleAtFixedRate(Runnable, long, long, TimeUnit)} * for {@link java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate}. * * @param method method object * @return method signature string */ @NonNull public static String signatureOfMethod(@NonNull final CtBehavior method) throws NotFoundException { final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(Modifier.toString(method.getModifiers())); if (method instanceof CtMethod) { final String returnType = ((CtMethod) method).getReturnType().getSimpleName(); stringBuilder.append(" ").append(returnType); } stringBuilder.append(" ").append(method.getName()).append("("); final CtClass[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { CtClass parameterType = parameterTypes[i]; if (i != 0) stringBuilder.append(", "); stringBuilder.append(parameterType.getSimpleName()); } stringBuilder.append(")"); return stringBuilder.toString(); } public static URL getLocationUrlOfClass(CtClass clazz) { try { // proxy classes is dynamic, no class file if (clazz.getName().startsWith("com.sun.proxy.")) return null; return clazz.getURL(); } catch (Exception e) { logger.warn("Fail to getLocationUrlOfClass " + clazz.getName() + ", cause: " + e.toString()); return null; } } public static String getLocationFileOfClass(CtClass clazz) { final URL location = getLocationUrlOfClass(clazz); if (location == null) return null; return location.getFile(); } public static URL getLocationUrlOfClass(Class clazz) { try { // proxy classes is dynamic, no class file if (clazz.getName().startsWith("com.sun.proxy.")) return null; final ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (protectionDomain == null) return null; final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource == null) return null; return codeSource.getLocation(); } catch (Exception e) { logger.warn("Fail to getLocationUrlOfClass " + clazz.getName() + ", cause: " + e.toString()); return null; } } public static String getLocationFileOfClass(Class clazz) { final URL location = getLocationUrlOfClass(clazz); if (location == null) return null; return location.getFile(); } // ======== Method Transform Helper ======== @NonNull public static String renamedMethodNameByTtl(@NonNull CtMethod method) { return "original$" + method.getName() + "$method$renamed$by$ttl"; } public static String addTryFinallyToMethod(@NonNull CtMethod method, @NonNull String beforeCode, @NonNull String finallyCode) throws CannotCompileException, NotFoundException { return addTryFinallyToMethod(method, renamedMethodNameByTtl(method), beforeCode, finallyCode); } /** * Add {@code try-finally} logic to method. * * @return the body code of method rewritten */ public static String addTryFinallyToMethod(@NonNull CtMethod method, @NonNull String nameForOriginalMethod, @NonNull String beforeCode, @NonNull String finallyCode) throws CannotCompileException, NotFoundException { final CtClass clazz = method.getDeclaringClass(); final CtMethod newMethod = CtNewMethod.copy(method, clazz, null); // rename original method, and set to private method(avoid reflect out renamed method unexpectedly) newMethod.setName(nameForOriginalMethod); newMethod.setModifiers(newMethod.getModifiers() & ~Modifier.PUBLIC /* remove public */ & ~Modifier.PROTECTED /* remove protected */ | Modifier.PRIVATE /* add private */); clazz.addMethod(newMethod); final String returnOp; if (method.getReturnType() == CtClass.voidType) { returnOp = ""; } else { returnOp = "return "; } // set new method implementation final String code = "{\n" + beforeCode + "\n" + "try {\n" + " " + returnOp + nameForOriginalMethod + "($$);\n" + "} finally {\n" + " " + finallyCode + "\n" + "} }"; method.setBody(code); return code; } // ======== CRR Helper ======== @Nullable public static Object doCaptureIfNotTtlEnhanced(@Nullable Object obj) { if (obj instanceof TtlEnhanced) return null; else return capture(); } // FIXME hard-coded for type Runnable, not generic! @Nullable public static Runnable doAutoWrap(@Nullable final Runnable runnable) { if (runnable == null) return null; final TtlRunnable ret = TtlRunnable.get(runnable, false, true); // have been auto wrapped? if (ret != runnable) setAutoWrapperAttachment(ret); return ret; } // FIXME hard-coded for type Callable, not generic! @Nullable public static Callable doAutoWrap(@Nullable final Callable callable) { if (callable == null) return null; final TtlCallable ret = TtlCallable.get(callable, false, true); // have been auto wrapped? if (ret != callable) setAutoWrapperAttachment(ret); return ret; } // ======== class/package info Helper ======== @NonNull public static String getPackageName(@NonNull String className) { final int idx = className.lastIndexOf('.'); if (-1 == idx) return ""; return className.substring(0, idx); } public static boolean isClassAtPackage(@NonNull String className, @NonNull String packageName) { return packageName.equals(getPackageName(className)); } public static boolean isClassUnderPackage(@NonNull String className, @NonNull String packageName) { String packageOfClass = getPackageName(className); return packageOfClass.equals(packageName) || packageOfClass.startsWith(packageName + "."); } public static boolean isClassAtPackageJavaUtil(@NonNull String className) { return isClassAtPackage(className, "java.util"); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlTransformletHelper() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/helper/package-info.java ================================================ /** * Helper API for TTL Agent extension {@code Transformlet} development. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.agent.transformlet.TtlTransformlet */ package com.alibaba.ttl3.agent.transformlet.helper; ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/ForkJoinTtlTransformlet.java ================================================ package com.alibaba.ttl3.agent.transformlet.internal; import com.alibaba.ttl3.agent.TtlAgent; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.ClassInfo; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper; import com.alibaba.ttl3.spi.TtlEnhanced; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.*; import java.io.IOException; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.*; /** * {@link TtlTransformlet} for {@link java.util.concurrent.ForkJoinTask}. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.concurrent.ForkJoinPool * @see java.util.concurrent.ForkJoinTask */ public final class ForkJoinTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(ForkJoinTtlTransformlet.class); private static final String FORK_JOIN_TASK_CLASS_NAME = "java.util.concurrent.ForkJoinTask"; private static final String FORK_JOIN_POOL_CLASS_NAME = "java.util.concurrent.ForkJoinPool"; private static final String FORK_JOIN_WORKER_THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory"; private final boolean disableInheritableForThreadPool; public ForkJoinTtlTransformlet() { this.disableInheritableForThreadPool = TtlAgent.isDisableInheritableForThreadPool(); } @Override public void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { if (FORK_JOIN_TASK_CLASS_NAME.equals(classInfo.getClassName())) { updateForkJoinTaskClass(classInfo.getCtClass()); classInfo.setModified(); } else if (disableInheritableForThreadPool && FORK_JOIN_POOL_CLASS_NAME.equals(classInfo.getClassName())) { updateConstructorDisableInheritable(classInfo.getCtClass()); classInfo.setModified(); } } /** * @see TtlTransformletHelper#doCaptureIfNotTtlEnhanced(Object) */ private void updateForkJoinTaskClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { final String className = clazz.getName(); // add new field final String capturedFieldName = "captured$field$added$by$ttl"; final CtField capturedField = CtField.make("private final Object " + capturedFieldName + ";", clazz); clazz.addField(capturedField, "com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.doCaptureIfNotTtlEnhanced(this);"); logger.info("add new field " + capturedFieldName + " to class " + className); final CtMethod doExecMethod = clazz.getDeclaredMethod("doExec", new CtClass[0]); final String doExec_renamed_method_name = renamedMethodNameByTtl(doExecMethod); final String beforeCode = "if (this instanceof " + TtlEnhanced.class.getName() + ") {\n" + // if the class is already TTL enhanced(eg: com.alibaba.ttl3.TtlRecursiveTask) " return " + doExec_renamed_method_name + "($$);\n" + // return directly/do nothing "}\n" + "Object backup = com.alibaba.ttl3.transmitter.Transmitter.replay(" + capturedFieldName + ");"; final String finallyCode = "com.alibaba.ttl3.transmitter.Transmitter.restore(backup);"; final String code = addTryFinallyToMethod(doExecMethod, doExec_renamed_method_name, beforeCode, finallyCode); logger.info("insert code around method " + signatureOfMethod(doExecMethod) + " of class " + clazz.getName() + ": " + code); } private void updateConstructorDisableInheritable(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException { for (CtConstructor constructor : clazz.getDeclaredConstructors()) { final CtClass[] parameterTypes = constructor.getParameterTypes(); final StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (FORK_JOIN_WORKER_THREAD_FACTORY_CLASS_NAME.equals(paramTypeName)) { String code = String.format("$%d = com.alibaba.ttl3.executor.TtlForkJoinPoolHelper.getDisableInheritableForkJoinWorkerThreadFactory($% 0) { logger.info("insert code before method " + signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + insertCode); constructor.insertBefore(insertCode.toString()); } } } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/JdkExecutorTtlTransformlet.java ================================================ package com.alibaba.ttl3.agent.transformlet.internal; import com.alibaba.ttl3.agent.TtlAgent; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.agent.transformlet.helper.AbstractExecutorTtlTransformlet; import java.util.HashSet; import java.util.Set; /** * {@link TtlTransformlet} for {@link java.util.concurrent.ThreadPoolExecutor} * and {@link java.util.concurrent.ScheduledThreadPoolExecutor}. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor */ public final class JdkExecutorTtlTransformlet extends AbstractExecutorTtlTransformlet implements TtlTransformlet { private static Set getExecutorClassNames() { Set executorClassNames = new HashSet<>(); executorClassNames.add(THREAD_POOL_EXECUTOR_CLASS_NAME); executorClassNames.add("java.util.concurrent.ScheduledThreadPoolExecutor"); return executorClassNames; } public JdkExecutorTtlTransformlet() { super(getExecutorClassNames(), TtlAgent.isDisableInheritableForThreadPool()); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/PriorityBlockingQueueTtlTransformlet.java ================================================ package com.alibaba.ttl3.agent.transformlet.internal; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.ClassInfo; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.executor.TtlExecutors; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtConstructor; import javassist.NotFoundException; import java.io.IOException; import java.util.Comparator; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; /** * TTL {@link TtlTransformlet} for {@link java.util.concurrent.PriorityBlockingQueue PriorityBlockingQueue}. *

* Avoid {@code ClassCastException(TtlRunnable cannot be cast to Comparable)} problem * for combination usage: *

    *
  • use {@link java.util.concurrent.PriorityBlockingQueue PriorityBlockingQueue} for {@link java.util.concurrent.ThreadPoolExecutor ThreadPoolExecutor}
  • *
  • use {@code TTL Agent} {@link JdkExecutorTtlTransformlet}
  • *
* More info see issue #330 * * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) * @see TtlExecutors#getTtlRunnableUnwrapComparatorForComparableRunnable() * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue) * @see java.util.concurrent.PriorityBlockingQueue * @see java.util.concurrent.PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator) * @see java.util.PriorityQueue * @see java.util.PriorityQueue#PriorityQueue(int, Comparator) * @see JdkExecutorTtlTransformlet */ public class PriorityBlockingQueueTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(PriorityBlockingQueueTtlTransformlet.class); private static final String PRIORITY_BLOCKING_QUEUE_CLASS_NAME = "java.util.concurrent.PriorityBlockingQueue"; private static final String PRIORITY_QUEUE_CLASS_NAME = "java.util.PriorityQueue"; private static final String COMPARATOR_FIELD_NAME = "comparator"; @Override public void doTransform(@NonNull ClassInfo classInfo) throws IOException, CannotCompileException, NotFoundException { final String className = classInfo.getClassName(); if (PRIORITY_BLOCKING_QUEUE_CLASS_NAME.equals(className)) { updatePriorityBlockingQueueClass(classInfo.getCtClass()); classInfo.setModified(); } if (PRIORITY_QUEUE_CLASS_NAME.equals(className)) { updateBlockingQueueClass(classInfo.getCtClass()); classInfo.setModified(); } } private void updatePriorityBlockingQueueClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { if (!haveComparatorField(clazz)) { // In Java 6, PriorityBlockingQueue implementation do not have field comparator, // need transform more fundamental class PriorityQueue logger.info(PRIORITY_BLOCKING_QUEUE_CLASS_NAME + " do not have field " + COMPARATOR_FIELD_NAME + ", transform " + PRIORITY_QUEUE_CLASS_NAME + " instead."); return; } modifyConstructors(clazz); } private void updateBlockingQueueClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { final CtClass classPriorityBlockingQueue = clazz.getClassPool().getCtClass(PRIORITY_BLOCKING_QUEUE_CLASS_NAME); if (haveComparatorField(classPriorityBlockingQueue)) return; logger.info(PRIORITY_BLOCKING_QUEUE_CLASS_NAME + " do not have field " + COMPARATOR_FIELD_NAME + ", so need transform " + PRIORITY_QUEUE_CLASS_NAME); modifyConstructors(clazz); } private static boolean haveComparatorField(CtClass clazz) { try { clazz.getDeclaredField(COMPARATOR_FIELD_NAME); return true; } catch (NotFoundException e) { return false; } } /** * wrap comparator field in constructors */ private static final String CODE = "this." + COMPARATOR_FIELD_NAME + " = " + PriorityBlockingQueueTtlTransformlet.class.getName() + ".overwriteComparatorField$by$ttl(this." + COMPARATOR_FIELD_NAME + ");"; /** * @see #overwriteComparatorField$by$ttl(Comparator) */ private static void modifyConstructors(@NonNull CtClass clazz) throws NotFoundException, CannotCompileException { for (CtConstructor constructor : clazz.getDeclaredConstructors()) { logger.info("insert code after constructor " + signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + CODE); constructor.insertAfter(CODE); } } /** * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) */ public static Comparator overwriteComparatorField$by$ttl(Comparator comparator) { if (comparator == null) return TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable(); return TtlExecutors.getTtlRunnableUnwrapComparator(comparator); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/TimerTaskTtlTransformlet.java ================================================ package com.alibaba.ttl3.agent.transformlet.internal; import com.alibaba.ttl3.agent.logging.Logger; import com.alibaba.ttl3.agent.transformlet.ClassInfo; import com.alibaba.ttl3.agent.transformlet.TtlTransformlet; import com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.*; import java.io.IOException; import static com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.*; /** * {@link TtlTransformlet} for {@link java.util.TimerTask}. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.TimerTask * @see java.util.Timer */ public final class TimerTaskTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(TimerTaskTtlTransformlet.class); private static final String TIMER_TASK_CLASS_NAME = "java.util.TimerTask"; private static final String RUN_METHOD_NAME = "run"; @Override public void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { // work-around ClassCircularityError: if (isClassAtPackageJavaUtil(classInfo.getClassName())) return; // TimerTask class is checked by above logic. // // if (TIMER_TASK_CLASS_NAME.equals(classInfo.getClassName())) return; // No need transform TimerTask class final CtClass clazz = classInfo.getCtClass(); if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || clazz.isAnnotation()) { return; } // class contains method `void run()` ? try { final CtMethod runMethod = clazz.getDeclaredMethod(RUN_METHOD_NAME, new CtClass[0]); if (!CtClass.voidType.equals(runMethod.getReturnType())) return; } catch (NotFoundException e) { return; } if (!clazz.subclassOf(clazz.getClassPool().get(TIMER_TASK_CLASS_NAME))) return; logger.info("Transforming class " + classInfo.getClassName()); updateTimerTaskClass(clazz); classInfo.setModified(); } /** * @see TtlTransformletHelper#doCaptureIfNotTtlEnhanced(Object) */ private void updateTimerTaskClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { final String className = clazz.getName(); // add new field final String capturedFieldName = "captured$field$added$by$ttl"; final CtField capturedField = CtField.make("private final Object " + capturedFieldName + ";", clazz); clazz.addField(capturedField, "com.alibaba.ttl3.agent.transformlet.helper.TtlTransformletHelper.doCaptureIfNotTtlEnhanced(this);"); logger.info("add new field " + capturedFieldName + " to class " + className); final CtMethod runMethod = clazz.getDeclaredMethod(RUN_METHOD_NAME, new CtClass[0]); final String beforeCode = "Object backup = com.alibaba.ttl3.transmitter.Transmitter.replay(" + capturedFieldName + ");"; final String finallyCode = "com.alibaba.ttl3.transmitter.Transmitter.restore(backup);"; final String code = addTryFinallyToMethod(runMethod, beforeCode, finallyCode); logger.info("insert code around method " + signatureOfMethod(runMethod) + " of class " + clazz.getName() + ": " + code); } } ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/internal/package-info.java ================================================ /** * TTL built-in {@code Transformlet} implementations. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.agent.transformlet.internal.JdkExecutorTtlTransformlet * @see com.alibaba.ttl3.agent.transformlet.internal.ForkJoinTtlTransformlet * @see com.alibaba.ttl3.agent.transformlet.internal.TimerTaskTtlTransformlet * @see com.alibaba.ttl3.agent.transformlet.TtlTransformlet */ package com.alibaba.ttl3.agent.transformlet.internal; ================================================ FILE: ttl-agent/src/main/java/com/alibaba/ttl3/agent/transformlet/package-info.java ================================================ /** * TTL {@code Transformlet} API for TTL Agent extension {@code Transformlet} development. *

* TTL built-in {@code Transformlet} implementations is in the package {@link com.alibaba.ttl3.agent.transformlet.internal}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.agent.transformlet.TtlTransformlet */ package com.alibaba.ttl3.agent.transformlet; ================================================ FILE: ttl-bom/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl-bom 3.x-SNAPSHOT pom ${project.artifactId} ${project.artifactId} https://github.com/alibaba/transmittable-thread-local Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions Alibaba https://www.alibaba.com Jerry Lee oldratlee oldratlee(AT)gmail(DOT)com Developer +8 https://github.com/oldratlee Alibaba https://www.alibaba.com true com.alibaba.ttl3 ttl-core ${project.version} com.alibaba.ttl3 ttl-agent ${project.version} com.alibaba.ttl3 ttl-kotlin ${project.version} com.alibaba transmittable-thread-local ${project.version} ossrh https://oss.sonatype.org/content/repositories/snapshots maven-source-plugin 3.3.1 maven-javadoc-plugin 3.8.0 maven-deploy-plugin 3.1.3 org.jacoco jacoco-maven-plugin 0.8.12 gen-sign performRelease true maven-gpg-plugin 3.2.7 sign-artifacts verify sign deploy-settings performRelease true org.sonatype.plugins nexus-staging-maven-plugin 1.7.0 true ossrh https://oss.sonatype.org/ true ================================================ FILE: ttl-core/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT ../pom.xml ttl-core jar TransmittableThreadLocal(TTL) 📌 The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components. https://github.com/alibaba/transmittable-thread-local 2022 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions Alibaba https://www.alibaba.com Jerry Lee oldratlee oldratlee(AT)gmail(DOT)com Developer +8 https://github.com/oldratlee Alibaba https://www.alibaba.com Yang Fang driventokill snoop(DOT)fy(AT)gmail(DOT)com Developer +8 https://github.com/driventokill Alibaba https://www.alibaba.com wuwen wuwen5 wuwen.55(AT)aliyun(DOT)com Developer +8 https://github.com/wuwen5 ofpay https://www.ofpay.com David Dai LNAmp 351450944(AT)qq(DOT)com Developer +8 https://github.com/LNAmp Alibaba https://www.alibaba.com io.reactivex.rxjava2 rxjava test io.reactivex.rxjava2 rxkotlin test ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/TransmitCallback.java ================================================ package com.alibaba.crr; /** * The callback of {@link Transmittable} process. * * @author Jerry Lee (oldratlee at gmail dot com) * @see Transmittable */ public interface TransmitCallback { /** * @see Transmittable#replay(Object) */ default void beforeReplay() { } /** * @see Transmittable#replay(Object) */ default void afterReplay() { } /** * @see Transmittable#restore(Object) */ default void beforeRestore() { } /** * @see Transmittable#restore(Object) */ default void afterRestore() { } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/Transmittable.java ================================================ package com.alibaba.crr; import edu.umd.cs.findbugs.annotations.NonNull; /** * Transmittance process is represented by methods {@link #capture()} => * {@link #replay(Object)} => {@link #restore(Object)} (aka {@code CRR} operations). * * @param the capture data type of transmittance * @param the backup data type of transmittance * @author Jerry Lee (oldratlee at gmail dot com) */ public interface Transmittable { /** * Capture. *

* NOTE:
* do NOT return {@code null}. * * @return the capture data of transmittance */ @NonNull C capture(); /** * Replay. *

* NOTE:
* do NOT return {@code null}. * * @param captured the capture data of transmittance, the return value of method {@link #capture()} * @return the backup data of transmittance */ @NonNull B replay(@NonNull C captured); /** * Clear. *

* NOTE:
* do NOT return {@code null}. *

* Semantically, the code {@code `B backup = clear();`} is same as {@code `B backup = replay(EMPTY_CAPTURE);`}. *

* The reason for providing this method is: *

    *
  1. lead to more readable code
  2. *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. *
* * @return the backup data of transmittance */ @NonNull B clear(); /** * Restore. * * @param backup the backup data of transmittance, the return value of methods {@link #replay(Object)} or {@link #clear()} * @see #replay(Object) * @see #clear() */ void restore(@NonNull B backup); } ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/composite/Backup.java ================================================ package com.alibaba.crr.composite; import org.jetbrains.annotations.ApiStatus; /** * The composite backup data type of {@link CompositeTransmittable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see CompositeTransmittable */ @ApiStatus.NonExtendable public interface Backup { } ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/composite/Capture.java ================================================ package com.alibaba.crr.composite; import org.jetbrains.annotations.ApiStatus; /** * The composite capture data type of {@link CompositeTransmittable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see CompositeTransmittable */ @ApiStatus.NonExtendable public interface Capture { } ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/composite/CompositeTransmitCallback.java ================================================ package com.alibaba.crr.composite; import com.alibaba.crr.TransmitCallback; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; import static com.alibaba.ttl3.internal.util.Utils.propagateIfFatal; /** * Composite TransmitCallback. * * @author Jerry Lee (oldratlee at gmail dot com) * @see TransmitCallback */ public final class CompositeTransmitCallback { private static final Logger logger = Logger.getLogger(CompositeTransmitCallback.class.getName()); private final Set registeredTransmitCallbackSet = new CopyOnWriteArraySet<>(); Object beforeReplay() { Set callbacks = new HashSet<>(registeredTransmitCallbackSet); for (TransmitCallback cb : callbacks) { try { cb.beforeReplay(); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when beforeReplay for transmittableCallback " + cb + "(class " + cb.getClass().getName() + "), just ignored; cause: " + t, t); } } } return callbacks; } Object afterReplay(Object data) { @SuppressWarnings("unchecked") Set callbacks = (Set) data; for (TransmitCallback cb : callbacks) { try { cb.afterReplay(); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when afterReplay for transmittableCallback " + cb + "(class " + cb.getClass().getName() + "), just ignored; cause: " + t, t); } } } return data; } Object beforeRestore(Object data) { @SuppressWarnings("unchecked") Set callbacks = (Set) data; for (TransmitCallback cb : callbacks) { try { cb.beforeRestore(); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when beforeRestore for transmittableCallback " + cb + "(class " + cb.getClass().getName() + "), just ignored; cause: " + t, t); } } } return data; } void afterRestore(Object data) { @SuppressWarnings("unchecked") Set callbacks = (Set) data; for (TransmitCallback cb : callbacks) { try { cb.afterRestore(); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when afterRestore for transmittableCallback " + cb + "(class " + cb.getClass().getName() + "), just ignored; cause: " + t, t); } } } } /** * Register the {@link TransmitCallback}. * * @return true if the input callback is not registered * @see #unregisterCallback(TransmitCallback) */ public boolean registerCallback(@NonNull TransmitCallback callback) { return registeredTransmitCallbackSet.add(callback); } /** * Unregister the {@link TransmitCallback}. * * @return true if the input callback is registered * @see #registerCallback(TransmitCallback) */ public boolean unregisterCallback(@NonNull TransmitCallback callback) { return registeredTransmitCallbackSet.remove(callback); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/composite/CompositeTransmittable.java ================================================ package com.alibaba.crr.composite; import com.alibaba.crr.Transmittable; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Level; import java.util.logging.Logger; import static com.alibaba.ttl3.internal.util.Utils.newHashMap; import static com.alibaba.ttl3.internal.util.Utils.propagateIfFatal; /** * {@link CompositeTransmittable} transmit all {@link Transmittable} * registered by {@link #registerTransmittable(Transmittable)}. *

* Transmittance is completed by methods {@link #capture()} => * {@link #replay(Capture)} => {@link #restore(Backup)} (aka {@code CRR} operations). *

* CAUTION:
* This implementation just ignore all exception thrown by * {@code CRR} operations of registered {@link Transmittable}. * * @author Jerry Lee (oldratlee at gmail dot com) */ public final class CompositeTransmittable implements Transmittable { private static final Logger logger = Logger.getLogger(CompositeTransmittable.class.getName()); private final Set> registeredTransmittableSet = new CopyOnWriteArraySet<>(); private final CompositeTransmitCallback callback; public CompositeTransmittable(CompositeTransmitCallback callback) { this.callback = callback; } /** * Capture all {@link Transmittable}. * * @return the captured values */ @NonNull public Capture capture() { final HashMap, Object> transmit2Value = newHashMap(registeredTransmittableSet.size()); for (Transmittable transmittable : registeredTransmittableSet) { try { transmit2Value.put(transmittable, transmittable.capture()); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when capture for transmittable " + transmittable + "(class " + transmittable.getClass().getName() + "), just ignored; cause: " + t, t); } } } return new Snapshot(transmit2Value, null); } /** * Replay the captured values from {@link #capture()}, * and return the backup values before replay. * * @param captured captured values {@link #capture()} * @return the backup values before replay * @see #capture() */ @NonNull public Backup replay(@NonNull Capture captured) { final Object data = callback.beforeReplay(); final Snapshot capturedSnapshot = (Snapshot) captured; final HashMap, Object> transmit2Value = newHashMap(capturedSnapshot.transmit2Value.size()); for (Map.Entry, Object> entry : capturedSnapshot.transmit2Value.entrySet()) { Transmittable transmittable = entry.getKey(); try { Object transmitCaptured = entry.getValue(); transmit2Value.put(transmittable, transmittable.replay(transmitCaptured)); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when replay for transmittable " + transmittable + "(class " + transmittable.getClass().getName() + "), just ignored; cause: " + t, t); } } } final Object afterData = callback.afterReplay(data); return new Snapshot(transmit2Value, afterData); } /** * Clear all values, and return the backup values before clear. *

* Semantically, the code {@code `Object backup = clear();`} is same as {@code `Object backup = replay(EMPTY_CAPTURE);`}. *

* The reason for providing this method is: * *

    *
  1. lead to more readable code
  2. *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. *
* * @return the backup values before clear * @see #replay(Capture) */ @NonNull public Backup clear() { final Object data = callback.beforeReplay(); final HashMap, Object> transmit2Value = newHashMap(registeredTransmittableSet.size()); for (Transmittable transmittable : registeredTransmittableSet) { try { transmit2Value.put(transmittable, transmittable.clear()); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when clear for transmittable " + transmittable + "(class " + transmittable.getClass().getName() + "), just ignored; cause: " + t, t); } } } final Object afterData = callback.afterReplay(data); return new Snapshot(transmit2Value, afterData); } /** * Restore the backup values from {@link #replay(Capture)}/{@link #clear()}. * * @param backup the backup values from {@link #replay(Capture)}/{@link #clear()} * @see #replay(Capture) * @see #clear() */ public void restore(@NonNull Backup backup) { final Snapshot snapshot = (Snapshot) backup; final Object data = callback.beforeRestore(snapshot.data); for (Map.Entry, Object> entry : snapshot.transmit2Value.entrySet()) { Transmittable transmittable = entry.getKey(); try { Object transmitBackup = entry.getValue(); transmittable.restore(transmitBackup); } catch (Throwable t) { propagateIfFatal(t); if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when restore for transmittable " + transmittable + "(class " + transmittable.getClass().getName() + "), just ignored; cause: " + t, t); } } } callback.afterRestore(data); } private static class Snapshot implements Capture, Backup { final HashMap, Object> transmit2Value; final Object data; Snapshot(HashMap, Object> transmit2Value, Object data) { this.transmit2Value = transmit2Value; this.data = data; } } /** * Register the Transmittable. * * @param the Transmittable capture data type * @param the Transmittable backup data type * @return true if the input Transmittable is not registered * @see #unregisterTransmittable(Transmittable) */ @SuppressWarnings("unchecked") public boolean registerTransmittable(@NonNull Transmittable transmittable) { return registeredTransmittableSet.add((Transmittable) transmittable); } /** * Unregister the Transmittable. * * @param the Transmittable capture data type * @param the Transmittable backup data type * @return true if the input transmittable is registered * @see #registerTransmittable(Transmittable) */ @SuppressWarnings("unchecked") public boolean unregisterTransmittable(@NonNull Transmittable transmittable) { return registeredTransmittableSet.remove((Transmittable) transmittable); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/composite/package-info.java ================================================ /** * Provide composite transmittance implementation * {@link com.alibaba.crr.composite.CompositeTransmittable} * for {@link com.alibaba.crr.Transmittable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.crr.composite.CompositeTransmittable * @see com.alibaba.crr.Transmittable */ package com.alibaba.crr.composite; ================================================ FILE: ttl-core/src/main/java/com/alibaba/crr/package-info.java ================================================ /** * The base independent abstraction of {@code CRR} * transmittance({@code interface} {@link com.alibaba.crr.Transmittable}), * and provide a common related component implementation * {@link com.alibaba.crr.composite.CompositeTransmittable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.crr.Transmittable * @see com.alibaba.crr.composite.CompositeTransmittable */ package com.alibaba.crr; ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TransmittableThreadLocal.java ================================================ package com.alibaba.ttl3; import com.alibaba.ttl3.executor.TtlExecutors; import com.alibaba.ttl3.transmitter.Transmittee; import com.alibaba.ttl3.transmitter.TransmitteeRegistry; import edu.umd.cs.findbugs.annotations.NonNull; import javax.annotation.ParametersAreNonnullByDefault; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.WeakHashMap; import java.util.function.Supplier; import java.util.function.UnaryOperator; import static com.alibaba.ttl3.internal.util.Utils.newHashMap; /** * {@link TransmittableThreadLocal}({@code TTL}) can transmit the value from the thread of submitting task * to the thread of executing task even using thread pooling components. *

* Note:
* {@link TransmittableThreadLocal} extends {@link InheritableThreadLocal}, * so {@link TransmittableThreadLocal} first is a {@link InheritableThreadLocal}.
* If the inheritable ability from {@link InheritableThreadLocal} has potential leaking problem, * you can disable the inheritable ability: *

* ❶ For thread pooling components({@link java.util.concurrent.ThreadPoolExecutor}, * {@link java.util.concurrent.ForkJoinPool}), Inheritable feature should never happen, * since threads in thread pooling components is pre-created and pooled, these threads is neutral to biz logic/data. *
* Disable inheritable for thread pooling components by wrapping thread factories using methods * {@link TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) getDisableInheritableThreadFactory} / * {@link TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() getDefaultDisableInheritableForkJoinWorkerThreadFactory}. *
* Or you can turn on "disable inheritable for thread pool" by {@code TTL Java Agent} * to wrap thread factories for thread pooling components automatically and transparently. *

* ❷ In other cases, disable inheritable by overriding method {@link #childValue(Object)}. *
* Whether the value should be inheritable or not can be controlled by the data owner, * disable it carefully when data owner have a clear idea. *

{@code
 * TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>() {
 *     protected String childValue(String parentValue) {
 *         return initialValue();
 *     }
 * }}
*

* More discussion about "disable the inheritable ability" * see * issue #100: disable Inheritable when it's not necessary and buggy. * * @author Jerry Lee (oldratlee at gmail dot com) * @author Yang Fang (snoop dot fy at gmail dot com) * @see user guide docs and code repo of TransmittableThreadLocal(TTL) * @see TtlRunnable * @see TtlCallable * @see TtlExecutors * @see TtlExecutors#getTtlExecutor(java.util.concurrent.Executor) * @see TtlExecutors#getTtlExecutorService(java.util.concurrent.ExecutorService) * @see TtlExecutors#getTtlScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) */ public class TransmittableThreadLocal extends InheritableThreadLocal { private final boolean disableIgnoreNullValueSemantics; /** * Default constructor. Create a {@link TransmittableThreadLocal} instance with "Ignore-Null-Value Semantics". *

* About "Ignore-Null-Value Semantics": * *

    *
  1. If value is {@code null}(check by {@link #get()} method), do NOT transmit this {@code ThreadLocal}.
  2. *
  3. If set {@code null} value, also remove value(invoke {@link #remove()} method).
  4. *
*

* This is a pragmatic design decision: *

    *
  1. use explicit value type rather than {@code null} value to express biz intent.
  2. *
  3. safer and more robust code(avoid {@code NPE} risk).
  4. *
*

* So it's strongly not recommended to use {@code null} value. *

* But the behavior of "Ignore-Null-Value Semantics" is NOT compatible with * {@link ThreadLocal} and {@link InheritableThreadLocal}, * you can disable this behavior/semantics via using constructor {@link #TransmittableThreadLocal(boolean)} * and setting parameter {@code disableIgnoreNullValueSemantics} to {@code true}. *

* More discussion about "Ignore-Null-Value Semantics" see * Issue #157. * * @see #TransmittableThreadLocal(boolean) */ public TransmittableThreadLocal() { this(false); } /** * Constructor, create a {@link TransmittableThreadLocal} instance * with parameter {@code disableIgnoreNullValueSemantics} to control "Ignore-Null-Value Semantics". * * @param disableIgnoreNullValueSemantics disable "Ignore-Null-Value Semantics" * @see #TransmittableThreadLocal() */ public TransmittableThreadLocal(boolean disableIgnoreNullValueSemantics) { this.disableIgnoreNullValueSemantics = disableIgnoreNullValueSemantics; } /** * Creates a transmittable thread local variable. * The initial value({@link #initialValue()}) of the variable is * determined by invoking the {@link #get()} method on the {@code Supplier}. * * @param the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @return a new transmittable thread local variable * @throws NullPointerException if the specified supplier is null * @see #withInitialAndGenerator(Supplier, UnaryOperator) */ @NonNull @SuppressWarnings("ConstantConditions") public static TransmittableThreadLocal withInitial(@NonNull Supplier supplier) { if (supplier == null) throw new NullPointerException("supplier is null"); return new SuppliedTransmittableThreadLocal<>(supplier, null, null); } /** * Creates a transmittable thread local variable. * The initial value({@link #initialValue()}) of the variable is * determined by invoking the {@link #get()} method on the {@code Supplier}; * and the child value({@link #childValue(Object)}) and the transmittee value({@link #transmitteeValue(Object)}) of the variable is * determined by invoking the {@link UnaryOperator#apply(Object)} method on the {@code UnaryOperator}. * * @param the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @param generatorForChildValueAndTransmitteeValue the value generator to be used to determine the child value and the transmittee value * @return a new transmittable thread local variable * @throws NullPointerException if the specified supplier or value generator is null * @see #withInitial(Supplier) */ @NonNull @ParametersAreNonnullByDefault @SuppressWarnings("ConstantConditions") public static TransmittableThreadLocal withInitialAndGenerator(Supplier supplier, UnaryOperator generatorForChildValueAndTransmitteeValue) { if (supplier == null) throw new NullPointerException("supplier is null"); if (generatorForChildValueAndTransmitteeValue == null) throw new NullPointerException("value generator is null"); return new SuppliedTransmittableThreadLocal<>(supplier, generatorForChildValueAndTransmitteeValue, generatorForChildValueAndTransmitteeValue); } /** * Creates a transmittable thread local variable. * The initial value({@link #initialValue()}) of the variable is * determined by invoking the {@link #get()} method on the {@code Supplier}; * and the child value({@link #childValue(Object)})}) and the transmittee value({@link #transmitteeValue(Object)}) of the variable is * determined by invoking the {@link UnaryOperator#apply(Object)} method on the {@code UnaryOperator}. *

* NOTE:
* Recommend use {@link #withInitialAndGenerator(Supplier, UnaryOperator)} instead of this method. * In most cases, the logic of determining the child value({@link #childValue(Object)}) * and the transmittee value({@link #transmitteeValue(Object)}) should be the same. * * @param the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @param generatorForChildValue the value generator to be used to determine the child value * @param generatorForTransmitteeValue the value generator to be used to determine the transmittee value * @return a new transmittable thread local variable * @throws NullPointerException if the specified supplier or value generator is null * @see #withInitial(Supplier) * @see #withInitialAndGenerator(Supplier, UnaryOperator) */ @NonNull @ParametersAreNonnullByDefault @SuppressWarnings("ConstantConditions") public static TransmittableThreadLocal withInitialAndGenerator(Supplier supplier, UnaryOperator generatorForChildValue, UnaryOperator generatorForTransmitteeValue) { if (supplier == null) throw new NullPointerException("supplier is null"); if (generatorForChildValue == null) throw new NullPointerException("value generator for child value is null"); if (generatorForTransmitteeValue == null) throw new NullPointerException("value generator for transmittee value is null"); return new SuppliedTransmittableThreadLocal<>(supplier, generatorForChildValue, generatorForTransmitteeValue); } /** * An extension of ThreadLocal that obtains its initial value from the specified {@code Supplier} * and obtains its child value and transmittee value from the specified generator. */ private static final class SuppliedTransmittableThreadLocal extends TransmittableThreadLocal { private final Supplier supplier; private final UnaryOperator generatorForChildValue; private final UnaryOperator generatorForTransmitteeValue; SuppliedTransmittableThreadLocal(Supplier supplier, UnaryOperator generatorForChildValue, UnaryOperator generatorForTransmitteeValue) { if (supplier == null) throw new NullPointerException("supplier is null"); this.supplier = supplier; this.generatorForChildValue = generatorForChildValue; this.generatorForTransmitteeValue = generatorForTransmitteeValue; } @Override protected T initialValue() { return supplier.get(); } @Override protected T childValue(T parentValue) { if (generatorForChildValue != null) return generatorForChildValue.apply(parentValue); else return super.childValue(parentValue); } @Override public T transmitteeValue(T parentValue) { if (generatorForTransmitteeValue != null) return generatorForTransmitteeValue.apply(parentValue); else return super.transmitteeValue(parentValue); } } /** * Computes the child's initial value for this transmittable thread-local * variable as a function of the parent's value at the time the child * thread is created. This method is called from within the parent * thread before the child is started. *

* Note:
* This method is overridden, and merely call {@link #transmitteeValue(Object)}. * In most cases, the logic of determining the child value({@link #childValue(Object)}) * and the transmittee value({@link #transmitteeValue(Object)}) should be the same, * so it's NOT recommended to override this method in subclass. * * @param parentValue the parent thread's value * @return the child thread's initial value */ @Override protected T childValue(T parentValue) { return transmitteeValue(parentValue); } /** * Computes the value for this transmittable thread-local variable * as a function of the source thread's value at the time the task * Object is created. *

* This method is called from {@link TtlRunnable} or * {@link TtlCallable} when it create, before the task is started. *

* Note:
* This method merely returns reference of its source thread value(the shadow copy), * and should be overridden if a different behavior is desired. * It's recommended to override this method in subclass. */ protected T transmitteeValue(T parentValue) { return parentValue; } /** * {@inheritDoc} */ @Override public final T get() { T value = super.get(); if (disableIgnoreNullValueSemantics || value != null) addThisToHolder(); return value; } /** * {@inheritDoc} */ @Override public final void set(T value) { if (!disableIgnoreNullValueSemantics && value == null) { // may set null to remove value remove(); } else { super.set(value); addThisToHolder(); } } /** * {@inheritDoc} */ @Override public final void remove() { removeThisFromHolder(); super.remove(); } private void superRemove() { super.remove(); } private T getTransmitteeValue() { return transmitteeValue(get()); } // Note about the holder: // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*). // 2. The type of value in the holder is WeakHashMap, ?>. // 2.1 but the WeakHashMap is used as a *Set*: // the value of WeakHashMap is *always* null, and never used. // 2.2 WeakHashMap support *null* value. private static final InheritableThreadLocal, ?>> holder = new InheritableThreadLocal, ?>>() { @Override protected WeakHashMap, ?> initialValue() { return new WeakHashMap<>(); } @Override protected WeakHashMap, ?> childValue(WeakHashMap, ?> parentValue) { return new WeakHashMap<>(parentValue); } }; @SuppressWarnings("unchecked") private void addThisToHolder() { if (!holder.get().containsKey(this)) { holder.get().put((TransmittableThreadLocal) this, null); // WeakHashMap supports null value. } } private void removeThisFromHolder() { holder.get().remove(this); } private static class TtlTransmittee implements Transmittee, Object>, HashMap, Object>> { @NonNull @Override public HashMap, Object> capture() { final HashMap, Object> ttl2Value = newHashMap(holder.get().size()); for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { ttl2Value.put(threadLocal, threadLocal.getTransmitteeValue()); } return ttl2Value; } @NonNull @Override public HashMap, Object> replay(@NonNull HashMap, Object> captured) { final HashMap, Object> backup = newHashMap(holder.get().size()); for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal threadLocal = iterator.next(); // backup backup.put(threadLocal, threadLocal.get()); // clear the TTL values that is not in captured // avoid the extra TTL values after replay when run task if (!captured.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // set TTL values to captured setTtlValuesTo(captured); return backup; } @NonNull @Override public HashMap, Object> clear() { return replay(newHashMap(0)); } @Override public void restore(@NonNull HashMap, Object> backup) { for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal threadLocal = iterator.next(); // clear the TTL values that is not in backup // avoid the extra TTL values after restore if (!backup.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // restore TTL values setTtlValuesTo(backup); } private static void setTtlValuesTo(@NonNull HashMap, Object> ttlValues) { for (Map.Entry, Object> entry : ttlValues.entrySet()) { TransmittableThreadLocal threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } } private static final TtlTransmittee ttlTransmittee = new TtlTransmittee(); static { TransmitteeRegistry.registerTransmittee(ttlTransmittee); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TtlCallable.java ================================================ package com.alibaba.ttl3; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.ttl3.spi.TtlAttachments; import com.alibaba.ttl3.spi.TtlAttachmentsDelegate; import com.alibaba.ttl3.spi.TtlEnhanced; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl3.transmitter.Transmitter.*; /** * {@link TtlCallable} decorate {@link Callable} to get {@link TransmittableThreadLocal} value * and transmit it to the time of {@link Callable} execution, needed when use {@link Callable} to thread pool. *

* Use factory method {@link #get(Callable)} to get decorated instance. *

* Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.executor.TtlExecutors * @see TtlWrappers * @see java.util.concurrent.Executor * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.Executors * @see java.util.concurrent.CompletionService * @see java.util.concurrent.ExecutorCompletionService */ public final class TtlCallable implements Callable, TtlWrapper>, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Callable callable; private final boolean releaseTtlValueReferenceAfterCall; private TtlCallable(@NonNull Callable callable, boolean releaseTtlValueReferenceAfterCall) { this.capturedRef = new AtomicReference<>(capture()); this.callable = callable; this.releaseTtlValueReferenceAfterCall = releaseTtlValueReferenceAfterCall; } /** * wrap method {@link Callable#call()}. */ @Override @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public V call() throws Exception { final Capture captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after call!"); } final Backup backup = replay(captured); try { return callable.call(); } finally { restore(backup); } } /** * return the original/underneath {@link Callable}. */ @NonNull public Callable getCallable() { return unwrap(); } /** * unwrap to the original/underneath {@link Callable}. * * @see TtlWrappers#unwrap(Object) */ @NonNull @Override public Callable unwrap() { return callable; } @Override public String toString() { return this.getClass().getName() + " - " + callable.toString(); } /** * Factory method, wrap input {@link Callable} to {@link TtlCallable}. *

* This method is idempotent. * * @param callable input {@link Callable} * @return Wrapped {@link Callable} */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TtlCallable get(@Nullable Callable callable) { return get(callable, false, false); } /** * Factory method, wrap input {@link Callable} to {@link TtlCallable}. *

* This method is idempotent. * * @param callable input {@link Callable} * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Callable} */ @Nullable @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) public static TtlCallable get(@Nullable Callable callable, boolean releaseTtlValueReferenceAfterCall) { return get(callable, releaseTtlValueReferenceAfterCall, false); } /** * Factory method, wrap input {@link Callable} to {@link TtlCallable}. *

* This method is idempotent. * * @param callable input {@link Callable} * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Callable} */ @Nullable @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) public static TtlCallable get(@Nullable Callable callable, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { if (callable == null) return null; if (callable instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlCallable) callable; else throw new IllegalStateException("Already TtlCallable!"); } return new TtlCallable<>(callable, releaseTtlValueReferenceAfterCall); } /** * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. * * @param tasks task to be wrapped * @return Wrapped {@link Callable} */ @NonNull public static List> gets(@Nullable Collection> tasks) { return gets(tasks, false, false); } /** * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. * * @param tasks task to be wrapped * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Callable} */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterCall) { return gets(tasks, releaseTtlValueReferenceAfterCall, false); } /** * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. * * @param tasks task to be wrapped * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Callable} */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { if (tasks == null) return Collections.emptyList(); List> copy = new ArrayList<>(); for (Callable task : tasks) { copy.add(TtlCallable.get(task, releaseTtlValueReferenceAfterCall, idempotent)); } return copy; } /** * Unwrap {@link TtlCallable} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code Callable} parameter is {@code null}, return {@code null}; * if input {@code Callable} parameter is not a {@link TtlCallable} just return input {@code Callable}. *

* so {@code TtlCallable.unwrap(TtlCallable.get(callable))} will always return the same input {@code callable} object. * * @see #get(Callable) * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Callable unwrap(@Nullable Callable callable) { if (!(callable instanceof TtlCallable)) return callable; else return ((TtlCallable) callable).getCallable(); } /** * Unwrap {@link TtlCallable} to the original/underneath one. *

* Invoke {@link #unwrap(Callable)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code Callable} collection parameter is {@code null}, return an empty list. * * @see #gets(Collection) * @see #unwrap(Callable) */ @NonNull public static List> unwraps(@Nullable Collection> tasks) { if (tasks == null) return Collections.emptyList(); List> copy = new ArrayList<>(); for (Callable task : tasks) { if (!(task instanceof TtlCallable)) copy.add(task); else copy.add(((TtlCallable) task).getCallable()); } return copy; } private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); /** * see {@link TtlAttachments#setTtlAttachment(String, Object)} */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveAction.java ================================================ package com.alibaba.ttl3; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.ttl3.spi.TtlEnhanced; import java.util.concurrent.ForkJoinTask; import static com.alibaba.ttl3.transmitter.Transmitter.*; /** * A recursive resultless {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. *

* Recommend to use {@code TTL Java Agent}; * Specially for {@code Java 8} {@link java.util.stream.Stream} and {@link java.util.concurrent.CompletableFuture}, * these async task are executed by {@link java.util.concurrent.ForkJoinPool} via {@link ForkJoinTask} at the bottom. * * @author LNAmp * @see java.util.concurrent.RecursiveAction * @see com.alibaba.ttl3.executor.TtlExecutors */ public abstract class TtlRecursiveAction extends ForkJoinTask implements TtlEnhanced { private static final long serialVersionUID = -5753568484583412377L; private final Capture captured = capture(); protected TtlRecursiveAction() { } /** * The main computation performed by this task. */ protected abstract void compute(); /** * see {@link ForkJoinTask#getRawResult()} */ public final Void getRawResult() { return null; } /** * see {@link ForkJoinTask#setRawResult(Object)} */ protected final void setRawResult(Void mustBeNull) { } /** * Implements execution conventions for RecursiveActions. */ protected final boolean exec() { final Backup backup = replay(captured); try { compute(); return true; } finally { restore(backup); } } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TtlRecursiveTask.java ================================================ package com.alibaba.ttl3; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.ttl3.spi.TtlEnhanced; import java.util.concurrent.ForkJoinTask; import static com.alibaba.ttl3.transmitter.Transmitter.*; /** * A recursive result-bearing {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. *

* Recommend to use {@code TTL Java Agent}; * Specially for {@code Java 8} {@link java.util.stream.Stream} and {@link java.util.concurrent.CompletableFuture}, * these async task are executed by {@link java.util.concurrent.ForkJoinPool} via {@link ForkJoinTask} at the bottom. * * @author LNAmp * @see java.util.concurrent.RecursiveTask * @see com.alibaba.ttl3.executor.TtlExecutors */ public abstract class TtlRecursiveTask extends ForkJoinTask implements TtlEnhanced { private static final long serialVersionUID = 1814679366926362436L; private final Capture captured = capture(); protected TtlRecursiveTask() { } /** * The result of the computation. */ V result; /** * The main computation performed by this task. * * @return the result of the computation */ protected abstract V compute(); /** * see {@link ForkJoinTask#getRawResult()} */ public final V getRawResult() { return result; } /** * see {@link ForkJoinTask#setRawResult(Object)} */ protected final void setRawResult(V value) { result = value; } /** * Implements execution conventions for RecursiveTask. */ protected final boolean exec() { final Backup backup = replay(captured); try { result = compute(); return true; } finally { restore(backup); } } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TtlRunnable.java ================================================ package com.alibaba.ttl3; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.ttl3.spi.TtlAttachments; import com.alibaba.ttl3.spi.TtlAttachmentsDelegate; import com.alibaba.ttl3.spi.TtlEnhanced; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl3.transmitter.Transmitter.*; /** * {@link TtlRunnable} decorate {@link Runnable} to get {@link TransmittableThreadLocal} value * and transmit it to the time of {@link Runnable} execution, needed when use {@link Runnable} to thread pool. *

* Use factory methods {@link #get} / {@link #gets} to create instance. *

* Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.executor.TtlExecutors * @see TtlWrappers * @see java.util.concurrent.Executor * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.Executors */ public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Runnable runnable; private final boolean releaseTtlValueReferenceAfterRun; private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<>(capture()); this.runnable = runnable; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link Runnable#run()}. */ @Override public void run() { final Capture captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Backup backup = replay(captured); try { runnable.run(); } finally { restore(backup); } } /** * return original/unwrapped {@link Runnable}. */ @NonNull public Runnable getRunnable() { return unwrap(); } /** * unwrap to original/unwrapped {@link Runnable}. * * @see TtlWrappers#unwrap(Object) */ @NonNull @Override public Runnable unwrap() { return runnable; } @Override public String toString() { return this.getClass().getName() + " - " + runnable.toString(); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TtlRunnable get(@Nullable Runnable runnable) { return get(runnable, false, false); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @Nullable @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { return get(runnable, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. */ @Nullable @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (runnable == null) return null; if (runnable instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlRunnable) runnable; else throw new IllegalStateException("Already TtlRunnable!"); } return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @NonNull public static List gets(@Nullable Collection tasks) { return gets(tasks, false, false); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @NonNull public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun) { return gets(tasks, releaseTtlValueReferenceAfterRun, false); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. */ @NonNull public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (tasks == null) return Collections.emptyList(); List copy = new ArrayList<>(); for (Runnable task : tasks) { copy.add(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent)); } return copy; } /** * Unwrap {@link TtlRunnable} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code Runnable} parameter is {@code null}, return {@code null}; * if input {@code Runnable} parameter is not a {@link TtlRunnable} just return input {@code Runnable}. *

* so {@code TtlRunnable.unwrap(TtlRunnable.get(runnable))} will always return the same input {@code runnable} object. * * @see #get(Runnable) * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Runnable unwrap(@Nullable Runnable runnable) { if (!(runnable instanceof TtlRunnable)) return runnable; else return ((TtlRunnable) runnable).getRunnable(); } /** * Unwrap {@link TtlRunnable} to the original/underneath one for collection. *

* Invoke {@link #unwrap(Runnable)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code Runnable} parameter collection is {@code null}, return an empty list. * * @see #gets(Collection) * @see #unwrap(Runnable) */ @NonNull public static List unwraps(@Nullable Collection tasks) { if (tasks == null) return Collections.emptyList(); List copy = new ArrayList<>(); for (Runnable task : tasks) { if (!(task instanceof TtlRunnable)) copy.add(task); else copy.add(((TtlRunnable) task).getRunnable()); } return copy; } private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); /** * see {@link TtlAttachments#setTtlAttachment(String, Object)} */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TtlTimerTask.java ================================================ package com.alibaba.ttl3; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.ttl3.spi.TtlEnhanced; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import org.jetbrains.annotations.Contract; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl3.transmitter.Transmitter.*; /** * {@link TtlTimerTask} decorate {@link TimerTask} to get {@link TransmittableThreadLocal} value * and transmit it to the time of {@link TtlTimerTask} execution, needed when use {@link TtlTimerTask} to {@link TimerTask}. *

* Use factory method {@link #get(TimerTask)} to create instance. *

* CAUTION:
* The {@link TtlTimerTask} make the method {@link TimerTask#scheduledExecutionTime()} in * the origin {@link TimerTask} lose effectiveness! Use {@code TTL Java Agent} instead. * * @author Jerry Lee (oldratlee at gmail dot com) * @see Timer * @see TimerTask * @see * Alibaba Java Coding Guidelines - Concurrency - * Item 10: [Mandatory] Run multiple TimeTask by using ScheduledExecutorService * rather than Timer because Timer will kill all running threads * in case of failing to catch exceptions. * @deprecated Use {@link TtlRunnable}, {@link java.util.concurrent.ScheduledExecutorService} * instead of {@link Timer}, {@link TimerTask}. */ @Deprecated public final class TtlTimerTask extends TimerTask implements TtlWrapper, TtlEnhanced { private final AtomicReference capturedRef; private final TimerTask timerTask; private final boolean releaseTtlValueReferenceAfterRun; private TtlTimerTask(@NonNull TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<>(capture()); this.timerTask = timerTask; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link TimerTask#run()}. */ @Override public void run() { final Capture captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Backup backup = replay(captured); try { timerTask.run(); } finally { restore(backup); } } @Override public boolean cancel() { timerTask.cancel(); return super.cancel(); } /** * return original/unwrapped {@link TimerTask}. */ @NonNull public TimerTask getTimerTask() { return unwrap(); } /** * unwrap to original/unwrapped {@link TimerTask}. * * @see TtlWrappers#unwrap(Object) */ @NonNull @Override public TimerTask unwrap() { return timerTask; } @Override public String toString() { return this.getClass().getName() + " - " + timerTask.toString(); } /** * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. *

* This method is idempotent. * * @param timerTask input {@link TimerTask} * @return Wrapped {@link TimerTask} */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TtlTimerTask get(@Nullable TimerTask timerTask) { return get(timerTask, false, false); } /** * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. *

* This method is idempotent. * * @param timerTask input {@link TimerTask} * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlTimerTask} is referred. * @return Wrapped {@link TimerTask} */ @Nullable @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) public static TtlTimerTask get(@Nullable TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { return get(timerTask, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. *

* This method is idempotent. * * @param timerTask input {@link TimerTask} * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlTimerTask} is referred. * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link TimerTask} */ @Nullable @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) public static TtlTimerTask get(@Nullable TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (timerTask == null) return null; if (timerTask instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlTimerTask) timerTask; else throw new IllegalStateException("Already TtlTimerTask!"); } return new TtlTimerTask(timerTask, releaseTtlValueReferenceAfterRun); } /** * Unwrap {@link TtlTimerTask} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code TimerTask} parameter is {@code null}, return {@code null}; * if input {@code TimerTask} parameter is not a {@link TtlTimerTask} just return input {@code TimerTask}. * * @see #get(TimerTask) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TimerTask unwrap(@Nullable TimerTask timerTask) { if (!(timerTask instanceof TtlTimerTask)) return timerTask; else return ((TtlTimerTask) timerTask).getTimerTask(); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/TtlWrappers.java ================================================ package com.alibaba.ttl3; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.ttl3.spi.TtlEnhanced; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.function.*; import static com.alibaba.ttl3.transmitter.Transmitter.*; /** * Util methods for TTL Wrapper. * *

    *
  • wrap common {@code Functional Interface}.
    * if missing your desired wrapper util method, * you need implement your own util method alike. *
  • *
  • unwrap TTL Wrapper and check whether it is TTL Wrapper.
  • *
*

* Note: *

    *
  • all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}.
  • *
  • all wrap method skip wrapping (aka. just return input parameter), when input parameter is already wrapped.
  • *
  • the wrap methods for {@link Runnable} and {@link java.util.concurrent.Callable Callable} are provided * by {@link TtlRunnable#get(Runnable)} and {@link TtlCallable#get(java.util.concurrent.Callable) TtlCallable.get(Callable)}.
  • *
  • the wrap methods for {@link java.util.concurrent.Executor Executor} are provided * by {@link com.alibaba.ttl3.executor.TtlExecutors TtlExecutors}.
  • *
* * @author Jerry Lee (oldratlee at gmail dot com) * @author huangfei1101 (fei.hf at alibaba-inc dot com) * @see TtlRunnable * @see TtlCallable * @see TtlWrapper * @see com.alibaba.ttl3.executor.TtlExecutors TtlExecutors */ public final class TtlWrappers { /** * wrap {@link Supplier} to TTL wrapper. * * @param supplier input {@link Supplier} * @return Wrapped {@link Supplier} * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Supplier wrapSupplier(@Nullable Supplier supplier) { if (supplier == null) return null; else if (supplier instanceof TtlEnhanced) return supplier; else return new TtlSupplier<>(supplier); } /** * wrap {@link Consumer} to TTL wrapper. * * @param consumer input {@link Consumer} * @return Wrapped {@link Consumer} * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Consumer wrapConsumer(@Nullable Consumer consumer) { if (consumer == null) return null; else if (consumer instanceof TtlEnhanced) return consumer; else return new TtlConsumer<>(consumer); } /** * wrap {@link BiConsumer} to TTL wrapper. * * @param consumer input {@link BiConsumer} * @return Wrapped {@link BiConsumer} * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static BiConsumer wrapBiConsumer(@Nullable BiConsumer consumer) { if (consumer == null) return null; else if (consumer instanceof TtlEnhanced) return consumer; else return new TtlBiConsumer<>(consumer); } /** * wrap {@link Function} to TTL wrapper. * * @param fn input {@link Function} * @return Wrapped {@link Function} * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Function wrapFunction(@Nullable Function fn) { if (fn == null) return null; else if (fn instanceof TtlEnhanced) return fn; else return new TtlFunction<>(fn); } /** * wrap {@link BiFunction} to TTL wrapper. * * @param fn input {@link BiFunction} * @return Wrapped {@link BiFunction} * @see TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static BiFunction wrapBiFunction(@Nullable BiFunction fn) { if (fn == null) return null; else if (fn instanceof TtlEnhanced) return fn; else return new TtlBiFunction<>(fn); } /** * Generic unwrap method, unwrap {@link TtlWrapper} to the original/underneath one. *

* this method is {@code null}-safe, when input parameter is {@code null}, return {@code null}; * if input parameter is not a {@link TtlWrapper} just return input. * * @see TtlWrappers#wrapSupplier(Supplier) * @see TtlWrappers#wrapConsumer(Consumer) * @see TtlWrappers#wrapBiConsumer(BiConsumer) * @see TtlWrappers#wrapFunction(Function) * @see TtlWrappers#wrapBiFunction(BiFunction) * @see TtlRunnable#get(Runnable) * @see TtlCallable#get(java.util.concurrent.Callable) TtlCallable.get(Callable) * @see com.alibaba.ttl3.executor.TtlExecutors#getTtlExecutor(java.util.concurrent.Executor) TtlExecutors.getTtlExecutor(Executor) * @see com.alibaba.ttl3.executor.TtlExecutors#getTtlExecutorService(java.util.concurrent.ExecutorService) TtlExecutors.getTtlExecutorService(ExecutorService) * @see com.alibaba.ttl3.executor.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) TtlExecutors.getDisableInheritableThreadFactory(ThreadFactory) * @see com.alibaba.ttl3.executor.TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) TtlExecutors.getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see TtlRunnable#unwrap(Runnable) * @see TtlCallable#unwrap(java.util.concurrent.Callable) TtlCallable.unwrap(Callable) * @see com.alibaba.ttl3.executor.TtlExecutors#unwrapTtlExecutor(java.util.concurrent.Executor) TtlExecutors.unwrapTtlExecutor(Executor) * @see com.alibaba.ttl3.executor.TtlExecutors#unwrapDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) TtlExecutors.unwrapDisableInheritableThreadFactory(ThreadFactory) * @see com.alibaba.ttl3.executor.TtlExecutors#unwrapDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) TtlExecutors.unwrapDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see com.alibaba.ttl3.executor.TtlExecutors#unwrapTtlRunnableUnwrapComparator(java.util.Comparator) TtlExecutors.unwrapTtlRunnableUnwrapComparator(Comparator) * @see #isWrapper(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) @SuppressWarnings("unchecked") public static T unwrap(@Nullable T obj) { if (!isWrapper(obj)) return obj; else return ((TtlWrapper) obj).unwrap(); } /** * check the input object is a {@code TtlWrapper} or not. * * @see #unwrap(Object) */ public static boolean isWrapper(@Nullable T obj) { return obj instanceof TtlWrapper; } /////////////////////////////////////////////////////////////////////////// // private inner classes /////////////////////////////////////////////////////////////////////////// private static class TtlSupplier implements Supplier, TtlWrapper>, TtlEnhanced { final Supplier supplier; final Capture captured; TtlSupplier(@NonNull Supplier supplier) { this.supplier = supplier; this.captured = capture(); } @Override public T get() { final Backup backup = replay(captured); try { return supplier.get(); } finally { restore(backup); } } @NonNull @Override public Supplier unwrap() { return supplier; } @Override public String toString() { return this.getClass().getName() + " - " + supplier.toString(); } } private static class TtlConsumer implements Consumer, TtlWrapper>, TtlEnhanced { final Consumer consumer; final Capture captured; TtlConsumer(@NonNull Consumer consumer) { this.consumer = consumer; this.captured = capture(); } @Override public void accept(T t) { final Backup backup = replay(captured); try { consumer.accept(t); } finally { restore(backup); } } @NonNull @Override public Consumer unwrap() { return consumer; } @Override public String toString() { return this.getClass().getName() + " - " + consumer.toString(); } } private static class TtlBiConsumer implements BiConsumer, TtlWrapper>, TtlEnhanced { final BiConsumer consumer; final Capture captured; TtlBiConsumer(@NonNull BiConsumer consumer) { this.consumer = consumer; this.captured = capture(); } @Override public void accept(T t, U u) { final Backup backup = replay(captured); try { consumer.accept(t, u); } finally { restore(backup); } } @NonNull @Override public BiConsumer unwrap() { return consumer; } @Override public String toString() { return this.getClass().getName() + " - " + consumer.toString(); } } private static class TtlFunction implements Function, TtlWrapper>, TtlEnhanced { final Function fn; final Capture captured; TtlFunction(@NonNull Function fn) { this.fn = fn; this.captured = capture(); } @Override public R apply(T t) { final Backup backup = replay(captured); try { return fn.apply(t); } finally { restore(backup); } } @NonNull @Override public Function unwrap() { return fn; } @Override public String toString() { return this.getClass().getName() + " - " + fn.toString(); } } private static class TtlBiFunction implements BiFunction, TtlWrapper>, TtlEnhanced { final BiFunction fn; final Capture captured; TtlBiFunction(@NonNull BiFunction fn) { this.fn = fn; this.captured = capture(); } @Override public R apply(T t, U u) { final Backup backup = replay(captured); try { return fn.apply(t, u); } finally { restore(backup); } } @NonNull @Override public BiFunction unwrap() { return fn; } @Override public String toString() { return this.getClass().getName() + " - " + fn.toString(); } } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlWrappers() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/agent/EmptyTtlAgentStatus.java ================================================ package com.alibaba.ttl3.agent; import edu.umd.cs.findbugs.annotations.NonNull; import static com.alibaba.ttl3.internal.util.Utils.propagateIfFatal; final class EmptyTtlAgentStatus implements TtlAgentStatus { @Override public boolean isTtlAgentLoaded() { return false; } private EmptyTtlAgentStatus() { } /////////////////////////////////////////////////////////////////////////// // Singleton maintenance logic /////////////////////////////////////////////////////////////////////////// private static volatile TtlAgentStatus ttlAgentStatus = null; @NonNull static TtlAgentStatus getLoadedAgentOrEmpty() { if (ttlAgentStatus != null) return ttlAgentStatus; synchronized (EmptyTtlAgentStatus.class) { // double check if (ttlAgentStatus != null) return ttlAgentStatus; final String TTL_AGENT_CLASS = "com.alibaba.ttl3.agent.TtlAgent"; TtlAgentStatus ret; try { ret = (TtlAgentStatus) Class.forName(TTL_AGENT_CLASS).getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException e) { ret = new EmptyTtlAgentStatus(); } catch (Exception e) { propagateIfFatal(e); throw new IllegalStateException("Fail to create the TTL agent instance(" + TTL_AGENT_CLASS + "), should be a bug! report to the TTL project. cause: " + e, e); } ttlAgentStatus = ret; return ret; } } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/agent/TtlAgentStatus.java ================================================ package com.alibaba.ttl3.agent; import edu.umd.cs.findbugs.annotations.NonNull; import static com.alibaba.ttl3.agent.EmptyTtlAgentStatus.getLoadedAgentOrEmpty; public interface TtlAgentStatus { /** * Whether TTL agent is loaded. */ boolean isTtlAgentLoaded(); @NonNull static TtlAgentStatus getInstance() { return getLoadedAgentOrEmpty(); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/agent/package-info.java ================================================ /** * Provide {@link com.alibaba.ttl3.agent.TtlAgentStatus} * to get {@code TTL Agent status}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.agent.TtlAgentStatus */ package com.alibaba.ttl3.agent; ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/ComparableComparator.java ================================================ /* * 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. */ package com.alibaba.ttl3.executor; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This source code file is copied and small adopted from commons-collections4 4.4: // // https://github.com/apache/commons-collections/blob/commons-commons-collections-4.4/src/main/java/org/apache/commons/collections4/comparators/ComparableComparator.java // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// import java.io.Serializable; import java.util.Comparator; /** * A {@link Comparator Comparator} that compares {@link Comparable Comparable} * objects. *

* This Comparator is useful, for example, for enforcing the natural order in * custom implementations of {@link java.util.SortedSet SortedSet} and * {@link java.util.SortedMap SortedMap}. *

*

* Note: In the 2.0 and 2.1 releases of Commons Collections, this class would * throw a {@link ClassCastException} if either of the arguments to * {@link #compare compare} were null, not * {@link Comparable Comparable}, or for which * {@link Comparable#compareTo(Object) compareTo} gave inconsistent results. * This is no longer the case. See {@link #compare compare} for * details. *

* * @param the type of objects compared by this comparator * * @see java.util.Collections#reverseOrder() */ final class ComparableComparator> implements Comparator, Serializable { /** Serialization version. */ private static final long serialVersionUID=-291439688585137865L; /** The singleton instance. */ @SuppressWarnings("rawtypes") public static final ComparableComparator INSTANCE = new ComparableComparator(); //----------------------------------------------------------------------- /** * Gets the singleton instance of a ComparableComparator. *

* Developers are encouraged to use the comparator returned from this method * instead of constructing a new instance to reduce allocation and GC overhead * when multiple comparable comparators may be used in the same VM. * * @param the element type * @return the singleton ComparableComparator */ @SuppressWarnings("unchecked") public static > ComparableComparator comparableComparator() { return INSTANCE; } //----------------------------------------------------------------------- /** * Constructor whose use should be avoided. *

* Please use the {@link #comparableComparator()} method whenever possible. */ public ComparableComparator() { super(); } //----------------------------------------------------------------------- /** * Compare the two {@link Comparable Comparable} arguments. * This method is equivalent to: *

((Comparable)obj1).compareTo(obj2)
* * @param obj1 the first object to compare * @param obj2 the second object to compare * @return negative if obj1 is less, positive if greater, zero if equal * @throws NullPointerException if obj1 is null, * or when ((Comparable)obj1).compareTo(obj2) does * @throws ClassCastException if obj1 is not a Comparable, * or when ((Comparable)obj1).compareTo(obj2) does */ @Override public int compare(final E obj1, final E obj2) { return obj1.compareTo(obj2); } //----------------------------------------------------------------------- /** * Implement a hash code for this comparator that is consistent with * {@link #equals(Object) equals}. * * @return a hash code for this comparator. */ @Override public int hashCode() { return "ComparableComparator".hashCode(); } /** * Returns {@code true} iff that Object is a {@link Comparator Comparator} * whose ordering is known to be equivalent to mine. *

* This implementation returns {@code true} iff * object.{@link Object#getClass() getClass()} equals * this.getClass(). Subclasses may want to override this behavior to remain * consistent with the {@link Comparator#equals(Object)} contract. * * @param object the object to compare with * @return {@code true} if equal */ @Override public boolean equals(final Object object) { return this == object || null != object && object.getClass().equals(this.getClass()); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.crr.composite.Backup; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; import java.util.concurrent.ForkJoinWorkerThread; import static com.alibaba.ttl3.transmitter.Transmitter.clear; import static com.alibaba.ttl3.transmitter.Transmitter.restore; /** * @author Jerry Lee (oldratlee at gmail dot com) */ final class DisableInheritableForkJoinWorkerThreadFactoryWrapper implements ForkJoinWorkerThreadFactory, TtlWrapper { private final ForkJoinWorkerThreadFactory threadFactory; DisableInheritableForkJoinWorkerThreadFactoryWrapper(@NonNull ForkJoinWorkerThreadFactory threadFactory) { this.threadFactory = threadFactory; } @Override public ForkJoinWorkerThread newThread(ForkJoinPool pool) { final Backup backup = clear(); try { return threadFactory.newThread(pool); } finally { restore(backup); } } @Override @NonNull public ForkJoinWorkerThreadFactory unwrap() { return threadFactory; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DisableInheritableForkJoinWorkerThreadFactoryWrapper that = (DisableInheritableForkJoinWorkerThreadFactoryWrapper) o; return threadFactory.equals(that.threadFactory); } @Override public int hashCode() { return threadFactory.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + threadFactory; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/DisableInheritableThreadFactoryWrapper.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.crr.composite.Backup; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ThreadFactory; import static com.alibaba.ttl3.transmitter.Transmitter.clear; import static com.alibaba.ttl3.transmitter.Transmitter.restore; /** * @author Jerry Lee (oldratlee at gmail dot com) */ final class DisableInheritableThreadFactoryWrapper implements ThreadFactory, TtlWrapper { private final ThreadFactory threadFactory; DisableInheritableThreadFactoryWrapper(@NonNull ThreadFactory threadFactory) { this.threadFactory = threadFactory; } @Override public Thread newThread(@NonNull Runnable r) { final Backup backup = clear(); try { return threadFactory.newThread(r); } finally { restore(backup); } } @NonNull @Override public ThreadFactory unwrap() { return threadFactory; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DisableInheritableThreadFactoryWrapper that = (DisableInheritableThreadFactoryWrapper) o; return threadFactory.equals(that.threadFactory); } @Override public int hashCode() { return threadFactory.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + threadFactory; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/ExecutorServiceTtlWrapper.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.ttl3.TransmittableThreadLocal; import com.alibaba.ttl3.TtlCallable; import com.alibaba.ttl3.TtlRunnable; import com.alibaba.ttl3.spi.TtlEnhanced; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Collection; import java.util.List; import java.util.concurrent.*; /** * {@link TransmittableThreadLocal} Wrapper of {@link ExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} * to the execution time of {@link Runnable} or {@link Callable}. * * @author Jerry Lee (oldratlee at gmail dot com) */ @SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"}) class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced { private final ExecutorService executorService; ExecutorServiceTtlWrapper(@NonNull ExecutorService executorService, boolean idempotent) { super(executorService, idempotent); this.executorService = executorService; } @Override public void shutdown() { executorService.shutdown(); } @NonNull @Override public List shutdownNow() { return executorService.shutdownNow(); } @Override public boolean isShutdown() { return executorService.isShutdown(); } @Override public boolean isTerminated() { return executorService.isTerminated(); } @Override public boolean awaitTermination(long timeout, @NonNull TimeUnit unit) throws InterruptedException { return executorService.awaitTermination(timeout, unit); } @NonNull @Override public Future submit(@NonNull Callable task) { return executorService.submit(TtlCallable.get(task, false, idempotent)); } @NonNull @Override public Future submit(@NonNull Runnable task, T result) { return executorService.submit(TtlRunnable.get(task, false, idempotent), result); } @NonNull @Override public Future submit(@NonNull Runnable task) { return executorService.submit(TtlRunnable.get(task, false, idempotent)); } @NonNull @Override public List> invokeAll(@NonNull Collection> tasks) throws InterruptedException { return executorService.invokeAll(TtlCallable.gets(tasks, false, idempotent)); } @NonNull @Override public List> invokeAll(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException { return executorService.invokeAll(TtlCallable.gets(tasks, false, idempotent), timeout, unit); } @NonNull @Override public T invokeAny(@NonNull Collection> tasks) throws InterruptedException, ExecutionException { return executorService.invokeAny(TtlCallable.gets(tasks, false, idempotent)); } @Override public T invokeAny(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return executorService.invokeAny(TtlCallable.gets(tasks, false, idempotent), timeout, unit); } @NonNull @Override public ExecutorService unwrap() { return executorService; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/ExecutorTtlWrapper.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.ttl3.TransmittableThreadLocal; import com.alibaba.ttl3.TtlRunnable; import com.alibaba.ttl3.spi.TtlEnhanced; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.Executor; /** * {@link TransmittableThreadLocal} Wrapper of {@link Executor}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} * to the execution time of {@link Runnable}. * * @author Jerry Lee (oldratlee at gmail dot com) */ class ExecutorTtlWrapper implements Executor, TtlWrapper, TtlEnhanced { private final Executor executor; protected final boolean idempotent; ExecutorTtlWrapper(@NonNull Executor executor, boolean idempotent) { this.executor = executor; this.idempotent = idempotent; } @Override public void execute(@NonNull Runnable command) { executor.execute(TtlRunnable.get(command, false, idempotent)); } @NonNull @Override public Executor unwrap() { return executor; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExecutorTtlWrapper that = (ExecutorTtlWrapper) o; if (idempotent != that.idempotent) return false; return executor.equals(that.executor); } @Override public int hashCode() { int result = executor.hashCode(); result = 31 * result + (idempotent ? 1 : 0); return result; } @Override public String toString() { return this.getClass().getName() + " - " + executor; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/ScheduledExecutorServiceTtlWrapper.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.ttl3.TransmittableThreadLocal; import com.alibaba.ttl3.TtlCallable; import com.alibaba.ttl3.TtlRunnable; import com.alibaba.ttl3.spi.TtlEnhanced; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * {@link TransmittableThreadLocal} Wrapper of {@link ScheduledExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} * to the execution time of {@link Runnable} or {@link Callable}. * * @author Jerry Lee (oldratlee at gmail dot com) */ @SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"}) class ScheduledExecutorServiceTtlWrapper extends ExecutorServiceTtlWrapper implements ScheduledExecutorService, TtlEnhanced { final ScheduledExecutorService scheduledExecutorService; public ScheduledExecutorServiceTtlWrapper(@NonNull ScheduledExecutorService scheduledExecutorService, boolean idempotent) { super(scheduledExecutorService, idempotent); this.scheduledExecutorService = scheduledExecutorService; } @NonNull @Override public ScheduledFuture schedule(@NonNull Runnable command, long delay, @NonNull TimeUnit unit) { return scheduledExecutorService.schedule(TtlRunnable.get(command, false, idempotent), delay, unit); } @NonNull @Override public ScheduledFuture schedule(@NonNull Callable callable, long delay, @NonNull TimeUnit unit) { return scheduledExecutorService.schedule(TtlCallable.get(callable, false, idempotent), delay, unit); } @NonNull @Override public ScheduledFuture scheduleAtFixedRate(@NonNull Runnable command, long initialDelay, long period, @NonNull TimeUnit unit) { return scheduledExecutorService.scheduleAtFixedRate(TtlRunnable.get(command, false, idempotent), initialDelay, period, unit); } @NonNull @Override public ScheduledFuture scheduleWithFixedDelay(@NonNull Runnable command, long initialDelay, long delay, @NonNull TimeUnit unit) { return scheduledExecutorService.scheduleWithFixedDelay(TtlRunnable.get(command, false, idempotent), initialDelay, delay, unit); } @NonNull @Override public ScheduledExecutorService unwrap() { return scheduledExecutorService; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/TtlExecutors.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.ttl3.TransmittableThreadLocal; import com.alibaba.ttl3.agent.TtlAgentStatus; import com.alibaba.ttl3.spi.TtlEnhanced; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.Comparator; import java.util.concurrent.*; import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; /** * Util methods for TTL wrapper of jdk executors. * *

    *
  1. wrap/check/unwrap methods for TTL wrapper of * jdk executors({@link Executor}, {@link ExecutorService}, {@link ScheduledExecutorService}).
  2. *
  3. wrap/check/unwrap methods for disable Inheritable wrapper of {@link ThreadFactory}.
  4. *
  5. wrap/check/unwrap methods for disable Inheritable wrapper of {@link ForkJoinWorkerThreadFactory}.
  6. *
  7. wrap/check/unwrap methods for {@code TtlRunnableUnwrapComparator} wrapper of {@link PriorityBlockingQueue} * for {@link ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue)}.
  8. *
*

* Note: *

    *
  • all method is {@code null}-safe. * for wrap/unwrap methods when input parameter is {@code null}, return {@code null}. * for check methods when input parameter is {@code null}, return {@code false}.
  • *
  • skip wrap/decoration thread pool/{@code executor}(aka. just return input {@code executor}) * when ttl agent is loaded, Or when input {@code executor} is already wrapped/decorated.
  • *
* * @author Jerry Lee (oldratlee at gmail dot com) * @see Executor * @see ExecutorService * @see ScheduledExecutorService * @see ThreadPoolExecutor * @see ScheduledThreadPoolExecutor * @see Executors * @see ThreadFactory * @see Executors#defaultThreadFactory() * @see ForkJoinPool * @see ForkJoinWorkerThreadFactory * @see ForkJoinPool#defaultForkJoinWorkerThreadFactory * @see ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue) * @see PriorityBlockingQueue */ public final class TtlExecutors { /////////////////////////////////////////////////////////////////////////// // Executor utils /////////////////////////////////////////////////////////////////////////// /** * {@link TransmittableThreadLocal} Wrapper of {@link Executor}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} * to the execution time of {@link Runnable}. * * @param executor input Executor * @return wrapped Executor * @see com.alibaba.ttl3.TtlRunnable#get(Runnable, boolean, boolean) * @see com.alibaba.ttl3.TtlCallable#get(Callable, boolean, boolean) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Executor getTtlExecutor(@Nullable Executor executor) { if (TtlAgentStatus.getInstance().isTtlAgentLoaded() || executor == null || executor instanceof TtlEnhanced) { return executor; } return new ExecutorTtlWrapper(executor, true); } /** * {@link TransmittableThreadLocal} Wrapper of {@link ExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} * to the execution time of {@link Runnable} or {@link Callable}. * * @param executorService input ExecutorService * @return wrapped ExecutorService * @see com.alibaba.ttl3.TtlRunnable#get(Runnable, boolean, boolean) * @see com.alibaba.ttl3.TtlCallable#get(Callable, boolean, boolean) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) { if (TtlAgentStatus.getInstance().isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) { return executorService; } return new ExecutorServiceTtlWrapper(executorService, true); } /** * {@link TransmittableThreadLocal} Wrapper of {@link ScheduledExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} * to the execution time of {@link Runnable} or {@link Callable}. * * @param scheduledExecutorService input scheduledExecutorService * @return wrapped scheduledExecutorService * @see com.alibaba.ttl3.TtlRunnable#get(Runnable, boolean, boolean) * @see com.alibaba.ttl3.TtlCallable#get(Callable, boolean, boolean) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ScheduledExecutorService getTtlScheduledExecutorService(@Nullable ScheduledExecutorService scheduledExecutorService) { if (TtlAgentStatus.getInstance().isTtlAgentLoaded() || scheduledExecutorService == null || scheduledExecutorService instanceof TtlEnhanced) { return scheduledExecutorService; } return new ScheduledExecutorServiceTtlWrapper(scheduledExecutorService, true); } /** * check the executor is a TTL executor wrapper or not. *

* if the parameter executor is TTL wrapper, return {@code true}, otherwise {@code false}. *

* NOTE: if input executor is {@code null}, return {@code false}. * * @param executor input executor * @param Executor type * @see #getTtlExecutor(Executor) * @see #getTtlExecutorService(ExecutorService) * @see #getTtlScheduledExecutorService(ScheduledExecutorService) * @see #unwrapTtlExecutor(Executor) */ public static boolean isTtlExecutor(@Nullable T executor) { return executor instanceof TtlWrapper; } /** * Unwrap TTL executor wrapper to the original/underneath one. *

* if the parameter executor is TTL wrapper, return the original/underneath executor; * otherwise, just return the input parameter executor. *

* NOTE: if input executor is {@code null}, return {@code null}. * * @param executor input executor * @param Executor type * @see #getTtlExecutor(Executor) * @see #getTtlExecutorService(ExecutorService) * @see #getTtlScheduledExecutorService(ScheduledExecutorService) * @see #isTtlExecutor(Executor) * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) @SuppressWarnings("unchecked") public static T unwrapTtlExecutor(@Nullable T executor) { if (!isTtlExecutor(executor)) return executor; return (T) ((ExecutorTtlWrapper) executor).unwrap(); } /** * Wrapper of {@link ThreadFactory}, disable inheritable. * * @param threadFactory input thread factory * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ThreadFactory getDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { if (threadFactory == null || isDisableInheritableThreadFactory(threadFactory)) return threadFactory; return new DisableInheritableThreadFactoryWrapper(threadFactory); } /** * Wrapper of {@link Executors#defaultThreadFactory()}, disable inheritable. * * @see #getDisableInheritableThreadFactory(ThreadFactory) * @see TtlExecutors#getDefaultDisableInheritableForkJoinWorkerThreadFactory() */ @NonNull public static ThreadFactory getDefaultDisableInheritableThreadFactory() { return getDisableInheritableThreadFactory(Executors.defaultThreadFactory()); } /** * check the {@link ThreadFactory} is {@code DisableInheritableThreadFactory} or not. * * @see TtlExecutors#getDisableInheritableForkJoinWorkerThreadFactory * @see #getDefaultDisableInheritableThreadFactory() */ public static boolean isDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { return threadFactory instanceof DisableInheritableThreadFactoryWrapper; } /** * Unwrap {@code DisableInheritableThreadFactory} to the original/underneath one. * * @see #getDisableInheritableThreadFactory(ThreadFactory) * @see #getDefaultDisableInheritableThreadFactory() * @see #isDisableInheritableThreadFactory(ThreadFactory) * @see TtlExecutors#unwrapDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ThreadFactory unwrapDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { if (!isDisableInheritableThreadFactory(threadFactory)) return threadFactory; return ((DisableInheritableThreadFactoryWrapper) threadFactory).unwrap(); } /////////////////////////////////////////////////////////////////////////// // ForkJoinPool utils /////////////////////////////////////////////////////////////////////////// /** * Wrapper of {@link ForkJoinWorkerThreadFactory}, disable inheritable. * * @param threadFactory input thread factory * @see TtlExecutors#getDisableInheritableThreadFactory(ThreadFactory) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ForkJoinWorkerThreadFactory getDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { if (threadFactory == null || isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) return threadFactory; return new DisableInheritableForkJoinWorkerThreadFactoryWrapper(threadFactory); } /** * Wrapper of {@link ForkJoinPool#defaultForkJoinWorkerThreadFactory}, disable inheritable. * * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() */ @NonNull public static ForkJoinWorkerThreadFactory getDefaultDisableInheritableForkJoinWorkerThreadFactory() { return getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinPool.defaultForkJoinWorkerThreadFactory); } /** * check the {@link ForkJoinWorkerThreadFactory} is {@code DisableInheritableForkJoinWorkerThreadFactory} or not. * * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see #getDefaultDisableInheritableForkJoinWorkerThreadFactory() */ public static boolean isDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { return threadFactory instanceof DisableInheritableForkJoinWorkerThreadFactoryWrapper; } /** * Unwrap {@code DisableInheritableForkJoinWorkerThreadFactory} to the original/underneath one. * * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) * @see TtlExecutors#unwrapDisableInheritableThreadFactory(ThreadFactory) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ForkJoinWorkerThreadFactory unwrapDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { if (!isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) return threadFactory; return ((DisableInheritableForkJoinWorkerThreadFactoryWrapper) threadFactory).unwrap(); } /////////////////////////////////////////////////////////////////////////// // Comparator utils /////////////////////////////////////////////////////////////////////////// /** * Wrapper of {@code Comparator} which unwrap {@link com.alibaba.ttl3.TtlRunnable} before compare, * aka {@code TtlRunnableUnwrapComparator}. *

* Prepared for {@code comparator} parameter of constructor * {@link PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator)}. *

* {@link PriorityBlockingQueue} can be used by constructor * {@link ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue)}. * * @param comparator input comparator * @return wrapped comparator * @see ThreadPoolExecutor * @see ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue) * @see PriorityBlockingQueue * @see PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Comparator getTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { if (comparator == null || isTtlRunnableUnwrapComparator(comparator)) return comparator; return new TtlUnwrapComparator<>(comparator); } @SuppressWarnings({"unchecked", "rawtypes"}) private static final Comparator INSTANCE = new TtlUnwrapComparator(ComparableComparator.INSTANCE); /** * {@code TtlRunnableUnwrapComparator} that compares {@link Comparable Comparable} objects. * * @see #getTtlRunnableUnwrapComparator(Comparator) */ @NonNull @SuppressWarnings("unchecked") public static Comparator getTtlRunnableUnwrapComparatorForComparableRunnable() { return (Comparator) INSTANCE; } /** * check the {@code Comparator} is a wrapper {@code TtlRunnableUnwrapComparator} or not. * * @see #getTtlRunnableUnwrapComparator(Comparator) * @see #getTtlRunnableUnwrapComparatorForComparableRunnable() */ public static boolean isTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { return comparator instanceof TtlUnwrapComparator; } /** * Unwrap {@code TtlRunnableUnwrapComparator} to the original/underneath {@code Comparator}. * * @see #getTtlRunnableUnwrapComparator(Comparator) * @see #getTtlRunnableUnwrapComparatorForComparableRunnable() * @see #isTtlRunnableUnwrapComparator(Comparator) * @see com.alibaba.ttl3.TtlWrappers#unwrap(Object) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Comparator unwrapTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { if (!isTtlRunnableUnwrapComparator(comparator)) return comparator; return ((TtlUnwrapComparator) comparator).unwrap(); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlExecutors() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/TtlUnwrapComparator.java ================================================ package com.alibaba.ttl3.executor; import com.alibaba.ttl3.TtlWrappers; import com.alibaba.ttl3.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Comparator; /** * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) * @see TtlExecutors#isTtlRunnableUnwrapComparator(Comparator) * @see TtlExecutors#unwrapTtlRunnableUnwrapComparator(Comparator) */ final class TtlUnwrapComparator implements Comparator, TtlWrapper> { private final Comparator comparator; public TtlUnwrapComparator(@NonNull Comparator comparator) { this.comparator = comparator; } @Override public int compare(T o1, T o2) { return comparator.compare(TtlWrappers.unwrap(o1), TtlWrappers.unwrap(o2)); } @NonNull @Override public Comparator unwrap() { return comparator; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlUnwrapComparator that = (TtlUnwrapComparator) o; return comparator.equals(that.comparator); } @Override public int hashCode() { return comparator.hashCode(); } @Override public String toString() { return "TtlUnwrapComparator{comparator=" + comparator + '}'; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/executor/package-info.java ================================================ /** * {@code TTL} wrap/decoration utils for {@code executor}s. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.executor.TtlExecutors */ package com.alibaba.ttl3.executor; ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/internal/util/Assert.java ================================================ /* * Copyright 2002-2020 the original author or 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 * * https://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 com.alibaba.ttl3.internal.util; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This source code file is copied from spring v5.3.24: // // https://github.com/spring-projects/spring-framework/blob/v5.3.24/spring-core/src/main/java/org/springframework/util/Assert.java // // with adoption: // - remove unused elements // - adjust visible modifier and code format // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// import edu.umd.cs.findbugs.annotations.Nullable; /** * Assertion utility class that assists in validating arguments. * *

Useful for identifying programmer errors early and clearly at runtime. * *

For example, if the contract of a public method states it does not * allow {@code null} arguments, {@code Assert} can be used to validate that * contract. Doing this clearly indicates a contract violation when it * occurs and protects the class's invariants. * *

Typically used to validate method arguments rather than configuration * properties, to check for cases that are usually programmer errors rather * than configuration errors. In contrast to configuration initialization * code, there is usually no point in falling back to defaults in such methods. * *

This class is similar to JUnit's assertion library. If an argument value is * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). * For example: * *

 * Assert.notNull(clazz, "The class must not be null");
 * Assert.isTrue(i > 0, "The value must be greater than zero");
* *

Mainly for internal use within the framework; for a more comprehensive suite * of assertion utilities consider {@code org.apache.commons.lang3.Validate} from * Apache Commons Lang, * Google Guava's * Preconditions, * or similar third-party libraries. * * @author Keith Donald * @author Juergen Hoeller * @author Sam Brannen * @author Colin Sampaleanu * @author Rob Harrop */ final class Assert { /** * Assert a boolean expression, throwing an {@code IllegalStateException} * if the expression evaluates to {@code false}. *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} * on an assertion failure. *

Assert.state(id == null, "The id property must not already be initialized");
* * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalStateException if {@code expression} is {@code false} */ public static void state(boolean expression, String message) { if (!expression) { throw new IllegalStateException(message); } } /** * Assert a boolean expression, throwing an {@code IllegalArgumentException} * if the expression evaluates to {@code false}. *
Assert.isTrue(i > 0, "The value must be greater than zero");
* * @param expression a boolean expression * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if {@code expression} is {@code false} */ public static void isTrue(boolean expression, String message) { if (!expression) { throw new IllegalArgumentException(message); } } /** * Assert that an object is not {@code null}. *
Assert.notNull(clazz, "The class must not be null");
* * @param object the object to check * @param message the exception message to use if the assertion fails * @throws IllegalArgumentException if the object is {@code null} */ public static void notNull(@Nullable Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/internal/util/ConcurrentReferenceHashMap.java ================================================ /* * Copyright 2002-2021 the original author or 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 * * https://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 com.alibaba.ttl3.internal.util; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This source code file is copied from spring v5.3.24: // // https://github.com/spring-projects/spring-framework/blob/v5.3.24/spring-core/src/main/java/org/springframework/util/ConcurrentReferenceHashMap.java // // with adoption: // - adjust visible modifier and code format // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; /** * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}. * *

This class can be used as an alternative to * {@code Collections.synchronizedMap(new WeakHashMap>())} in order to * support better performance when accessed concurrently. This implementation follows the * same design constraints as {@link ConcurrentHashMap} with the exception that * {@code null} values and {@code null} keys are supported. * *

NOTE: The use of references means that there is no guarantee that items * placed into the map will be subsequently available. The garbage collector may discard * references at any time, so it may appear that an unknown thread is silently removing * entries. * *

If not explicitly specified, this implementation will use * {@linkplain SoftReference soft entry references}. * * @param the key type * @param the value type * @author Phillip Webb * @author Juergen Hoeller */ @SuppressWarnings("ALL") // Is there a class annotation in FindBugs to ignore all warning in a file // https://stackoverflow.com/questions/13398685 @SuppressFBWarnings class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { private static final int DEFAULT_INITIAL_CAPACITY = 16; private static final float DEFAULT_LOAD_FACTOR = 0.75f; private static final int DEFAULT_CONCURRENCY_LEVEL = 16; private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT; private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16; private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30; /** * Array of segments indexed using the high order bits from the hash. */ private final Segment[] segments; /** * When the average number of references per table exceeds this value resize will be attempted. */ private final float loadFactor; /** * The reference type: SOFT or WEAK. */ private final ReferenceType referenceType; /** * The shift value used to calculate the size of the segments array and an index from the hash. */ private final int shift; /** * Late binding entry set. */ @Nullable private volatile Set> entrySet; /** * Create a new {@code ConcurrentReferenceHashMap} instance. */ public ConcurrentReferenceHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * * @param initialCapacity the initial capacity of the map */ public ConcurrentReferenceHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * * @param initialCapacity the initial capacity of the map * @param loadFactor the load factor. When the average number of references per table * exceeds this value resize will be attempted */ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * * @param initialCapacity the initial capacity of the map * @param concurrencyLevel the expected number of threads that will concurrently * write to the map */ public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * * @param initialCapacity the initial capacity of the map * @param referenceType the reference type used for entries (soft or weak) */ public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) { this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * * @param initialCapacity the initial capacity of the map * @param loadFactor the load factor. When the average number of references per * table exceeds this value, resize will be attempted. * @param concurrencyLevel the expected number of threads that will concurrently * write to the map */ public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); } /** * Create a new {@code ConcurrentReferenceHashMap} instance. * * @param initialCapacity the initial capacity of the map * @param loadFactor the load factor. When the average number of references per * table exceeds this value, resize will be attempted. * @param concurrencyLevel the expected number of threads that will concurrently * write to the map * @param referenceType the reference type used for entries (soft or weak) */ @SuppressWarnings("unchecked") public ConcurrentReferenceHashMap( int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) { Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); Assert.notNull(referenceType, "Reference type must not be null"); this.loadFactor = loadFactor; this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); int size = 1 << this.shift; this.referenceType = referenceType; int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE); Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size); int resizeThreshold = (int) (initialSize * getLoadFactor()); for (int i = 0; i < segments.length; i++) { segments[i] = new Segment(initialSize, resizeThreshold); } this.segments = segments; } protected final float getLoadFactor() { return this.loadFactor; } protected final int getSegmentsSize() { return this.segments.length; } protected final Segment getSegment(int index) { return this.segments[index]; } /** * Factory method that returns the {@link ReferenceManager}. * This method will be called once for each {@link Segment}. * * @return a new reference manager */ protected ReferenceManager createReferenceManager() { return new ReferenceManager(); } /** * Get the hash for a given object, apply an additional hash function to reduce * collisions. This implementation uses the same Wang/Jenkins algorithm as * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing. * * @param o the object to hash (may be null) * @return the resulting hash code */ protected int getHash(@Nullable Object o) { int hash = (o != null ? o.hashCode() : 0); hash += (hash << 15) ^ 0xffffcd7d; hash ^= (hash >>> 10); hash += (hash << 3); hash ^= (hash >>> 6); hash += (hash << 2) + (hash << 14); hash ^= (hash >>> 16); return hash; } @Override @Nullable public V get(@Nullable Object key) { Reference ref = getReference(key, Restructure.WHEN_NECESSARY); Entry entry = (ref != null ? ref.get() : null); return (entry != null ? entry.getValue() : null); } @Override @Nullable public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) { Reference ref = getReference(key, Restructure.WHEN_NECESSARY); Entry entry = (ref != null ? ref.get() : null); return (entry != null ? entry.getValue() : defaultValue); } @Override public boolean containsKey(@Nullable Object key) { Reference ref = getReference(key, Restructure.WHEN_NECESSARY); Entry entry = (ref != null ? ref.get() : null); return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key)); } /** * Return a {@link Reference} to the {@link Entry} for the specified {@code key}, * or {@code null} if not found. * * @param key the key (can be {@code null}) * @param restructure types of restructure allowed during this call * @return the reference, or {@code null} if not found */ @Nullable protected final Reference getReference(@Nullable Object key, Restructure restructure) { int hash = getHash(key); return getSegmentForHash(hash).getReference(key, hash, restructure); } @Override @Nullable public V put(@Nullable K key, @Nullable V value) { return put(key, value, true); } @Override @Nullable public V putIfAbsent(@Nullable K key, @Nullable V value) { return put(key, value, false); } @Nullable private V put(@Nullable final K key, @Nullable final V value, final boolean overwriteExisting) { return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { @Override @Nullable protected V execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { if (entry != null) { V oldValue = entry.getValue(); if (overwriteExisting) { entry.setValue(value); } return oldValue; } Assert.state(entries != null, "No entries segment"); entries.add(value); return null; } }); } @Override @Nullable public V remove(@Nullable Object key) { return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { @Override @Nullable protected V execute(@Nullable Reference ref, @Nullable Entry entry) { if (entry != null) { if (ref != null) { ref.release(); } return entry.value; } return null; } }); } @Override public boolean remove(@Nullable Object key, final @Nullable Object value) { Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { @Override protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) { if (ref != null) { ref.release(); } return true; } return false; } }); return (Boolean.TRUE.equals(result)); } @Override public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) { Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { @Override protected Boolean execute(@Nullable Reference ref, @Nullable Entry entry) { if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) { entry.setValue(newValue); return true; } return false; } }); return (Boolean.TRUE.equals(result)); } @Override @Nullable public V replace(@Nullable K key, final @Nullable V value) { return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { @Override @Nullable protected V execute(@Nullable Reference ref, @Nullable Entry entry) { if (entry != null) { V oldValue = entry.getValue(); entry.setValue(value); return oldValue; } return null; } }); } @Override public void clear() { for (Segment segment : this.segments) { segment.clear(); } } /** * Remove any entries that have been garbage collected and are no longer referenced. * Under normal circumstances garbage collected entries are automatically purged as * items are added or removed from the Map. This method can be used to force a purge, * and is useful when the Map is read frequently but updated less often. */ public void purgeUnreferencedEntries() { for (Segment segment : this.segments) { segment.restructureIfNecessary(false); } } @Override public int size() { int size = 0; for (Segment segment : this.segments) { size += segment.getCount(); } return size; } @Override public boolean isEmpty() { for (Segment segment : this.segments) { if (segment.getCount() > 0) { return false; } } return true; } @Override public Set> entrySet() { Set> entrySet = this.entrySet; if (entrySet == null) { entrySet = new EntrySet(); this.entrySet = entrySet; } return entrySet; } @Nullable private T doTask(@Nullable Object key, Task task) { int hash = getHash(key); return getSegmentForHash(hash).doTask(hash, key, task); } private Segment getSegmentForHash(int hash) { return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)]; } /** * Calculate a shift value that can be used to create a power-of-two value between * the specified maximum and minimum values. * * @param minimumValue the minimum value * @param maximumValue the maximum value * @return the calculated shift (use {@code 1 << shift} to obtain a value) */ protected static int calculateShift(int minimumValue, int maximumValue) { int shift = 0; int value = 1; while (value < minimumValue && value < maximumValue) { value <<= 1; shift++; } return shift; } /** * Various reference types supported by this map. */ public enum ReferenceType { /** * Use {@link SoftReference SoftReferences}. */ SOFT, /** * Use {@link WeakReference WeakReferences}. */ WEAK } /** * A single segment used to divide the map to allow better concurrent performance. */ @SuppressWarnings("serial") @SuppressFBWarnings("EI_EXPOSE_REP2") protected final class Segment extends ReentrantLock { private final ReferenceManager referenceManager; private final int initialSize; /** * Array of references indexed using the low order bits from the hash. * This property should only be set along with {@code resizeThreshold}. */ private volatile Reference[] references; /** * The total number of references contained in this segment. This includes chained * references and references that have been garbage collected but not purged. */ private final AtomicInteger count = new AtomicInteger(); /** * The threshold when resizing of the references should occur. When {@code count} * exceeds this value references will be resized. */ private int resizeThreshold; public Segment(int initialSize, int resizeThreshold) { this.referenceManager = createReferenceManager(); this.initialSize = initialSize; this.references = createReferenceArray(initialSize); this.resizeThreshold = resizeThreshold; } @Nullable public Reference getReference(@Nullable Object key, int hash, Restructure restructure) { if (restructure == Restructure.WHEN_NECESSARY) { restructureIfNecessary(false); } if (this.count.get() == 0) { return null; } // Use a local copy to protect against other threads writing Reference[] references = this.references; int index = getIndex(hash, references); Reference head = references[index]; return findInChain(head, key, hash); } /** * Apply an update operation to this segment. * The segment will be locked during the update. * * @param hash the hash of the key * @param key the key * @param task the update operation * @return the result of the operation */ @Nullable public T doTask(final int hash, @Nullable final Object key, final Task task) { boolean resize = task.hasOption(TaskOption.RESIZE); if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { restructureIfNecessary(resize); } if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count.get() == 0) { return task.execute(null, null, null); } lock(); try { final int index = getIndex(hash, this.references); final Reference head = this.references[index]; Reference ref = findInChain(head, key, hash); Entry entry = (ref != null ? ref.get() : null); Entries entries = value -> { @SuppressWarnings("unchecked") Entry newEntry = new Entry<>((K) key, value); Reference newReference = Segment.this.referenceManager.createReference(newEntry, hash, head); Segment.this.references[index] = newReference; Segment.this.count.incrementAndGet(); }; return task.execute(ref, entry, entries); } finally { unlock(); if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { restructureIfNecessary(resize); } } } /** * Clear all items from this segment. */ public void clear() { if (this.count.get() == 0) { return; } lock(); try { this.references = createReferenceArray(this.initialSize); this.resizeThreshold = (int) (this.references.length * getLoadFactor()); this.count.set(0); } finally { unlock(); } } /** * Restructure the underlying data structure when it becomes necessary. This * method can increase the size of the references table as well as purge any * references that have been garbage collected. * * @param allowResize if resizing is permitted */ protected final void restructureIfNecessary(boolean allowResize) { int currCount = this.count.get(); boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold); Reference ref = this.referenceManager.pollForPurge(); if (ref != null || (needsResize)) { restructure(allowResize, ref); } } private void restructure(boolean allowResize, @Nullable Reference ref) { boolean needsResize; lock(); try { int countAfterRestructure = this.count.get(); Set> toPurge = Collections.emptySet(); if (ref != null) { toPurge = new HashSet<>(); while (ref != null) { toPurge.add(ref); ref = this.referenceManager.pollForPurge(); } } countAfterRestructure -= toPurge.size(); // Recalculate taking into account count inside lock and items that // will be purged needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold); boolean resizing = false; int restructureSize = this.references.length; if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { restructureSize <<= 1; resizing = true; } // Either create a new table or reuse the existing one Reference[] restructured = (resizing ? createReferenceArray(restructureSize) : this.references); // Restructure for (int i = 0; i < this.references.length; i++) { ref = this.references[i]; if (!resizing) { restructured[i] = null; } while (ref != null) { if (!toPurge.contains(ref)) { Entry entry = ref.get(); if (entry != null) { int index = getIndex(ref.getHash(), restructured); restructured[index] = this.referenceManager.createReference( entry, ref.getHash(), restructured[index]); } } ref = ref.getNext(); } } // Replace volatile members if (resizing) { this.references = restructured; this.resizeThreshold = (int) (this.references.length * getLoadFactor()); } this.count.set(Math.max(countAfterRestructure, 0)); } finally { unlock(); } } @Nullable private Reference findInChain(Reference ref, @Nullable Object key, int hash) { Reference currRef = ref; while (currRef != null) { if (currRef.getHash() == hash) { Entry entry = currRef.get(); if (entry != null) { K entryKey = entry.getKey(); if (ObjectUtils.nullSafeEquals(entryKey, key)) { return currRef; } } } currRef = currRef.getNext(); } return null; } @SuppressWarnings({"unchecked"}) private Reference[] createReferenceArray(int size) { return new Reference[size]; } private int getIndex(int hash, Reference[] references) { return (hash & (references.length - 1)); } /** * Return the size of the current references array. */ public final int getSize() { return this.references.length; } /** * Return the total number of references in this segment. */ public final int getCount() { return this.count.get(); } } /** * A reference to an {@link Entry} contained in the map. Implementations are usually * wrappers around specific Java reference implementations (e.g., {@link SoftReference}). * * @param the key type * @param the value type */ protected interface Reference { /** * Return the referenced entry, or {@code null} if the entry is no longer available. */ @Nullable Entry get(); /** * Return the hash for the reference. */ int getHash(); /** * Return the next reference in the chain, or {@code null} if none. */ @Nullable Reference getNext(); /** * Release this entry and ensure that it will be returned from * {@code ReferenceManager#pollForPurge()}. */ void release(); } /** * A single map entry. * * @param the key type * @param the value type */ @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION") protected static final class Entry implements Map.Entry { @Nullable private final K key; @Nullable private volatile V value; public Entry(@Nullable K key, @Nullable V value) { this.key = key; this.value = value; } @Override @Nullable public K getKey() { return this.key; } @Override @Nullable public V getValue() { return this.value; } @Override @Nullable public V setValue(@Nullable V value) { V previous = this.value; this.value = value; return previous; } @Override public String toString() { return (this.key + "=" + this.value); } @Override @SuppressWarnings("rawtypes") public final boolean equals(@Nullable Object other) { if (this == other) { return true; } if (!(other instanceof Map.Entry)) { return false; } Map.Entry otherEntry = (Map.Entry) other; return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) && ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue())); } @Override public final int hashCode() { return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value)); } } /** * A task that can be {@link Segment#doTask run} against a {@link Segment}. */ private abstract class Task { private final EnumSet options; public Task(TaskOption... options) { this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options)); } public boolean hasOption(TaskOption option) { return this.options.contains(option); } /** * Execute the task. * * @param ref the found reference (or {@code null}) * @param entry the found entry (or {@code null}) * @param entries access to the underlying entries * @return the result of the task * @see #execute(Reference, Entry) */ @Nullable protected T execute(@Nullable Reference ref, @Nullable Entry entry, @Nullable Entries entries) { return execute(ref, entry); } /** * Convenience method that can be used for tasks that do not need access to {@link Entries}. * * @param ref the found reference (or {@code null}) * @param entry the found entry (or {@code null}) * @return the result of the task * @see #execute(Reference, Entry, Entries) */ @Nullable protected T execute(@Nullable Reference ref, @Nullable Entry entry) { return null; } } /** * Various options supported by a {@code Task}. */ private enum TaskOption { RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE } /** * Allows a task access to {@link ConcurrentReferenceHashMap.Segment} entries. */ private interface Entries { /** * Add a new entry with the specified value. * * @param value the value to add */ void add(@Nullable V value); } /** * Internal entry-set implementation. */ private class EntrySet extends AbstractSet> { @Override public Iterator> iterator() { return new EntryIterator(); } @Override public boolean contains(@Nullable Object o) { if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; Reference ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER); Entry otherEntry = (ref != null ? ref.get() : null); if (otherEntry != null) { return ObjectUtils.nullSafeEquals(entry.getValue(), otherEntry.getValue()); } } return false; } @Override public boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry entry = (Map.Entry) o; return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); } return false; } @Override public int size() { return ConcurrentReferenceHashMap.this.size(); } @Override public void clear() { ConcurrentReferenceHashMap.this.clear(); } } /** * Internal entry iterator implementation. */ private class EntryIterator implements Iterator> { private int segmentIndex; private int referenceIndex; @Nullable private Reference[] references; @Nullable private Reference reference; @Nullable private Entry next; @Nullable private Entry last; public EntryIterator() { moveToNextSegment(); } @Override public boolean hasNext() { getNextIfNecessary(); return (this.next != null); } @Override public Entry next() { getNextIfNecessary(); if (this.next == null) { throw new NoSuchElementException(); } this.last = this.next; this.next = null; return this.last; } private void getNextIfNecessary() { while (this.next == null) { moveToNextReference(); if (this.reference == null) { return; } this.next = this.reference.get(); } } private void moveToNextReference() { if (this.reference != null) { this.reference = this.reference.getNext(); } while (this.reference == null && this.references != null) { if (this.referenceIndex >= this.references.length) { moveToNextSegment(); this.referenceIndex = 0; } else { this.reference = this.references[this.referenceIndex]; this.referenceIndex++; } } } private void moveToNextSegment() { this.reference = null; this.references = null; if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) { this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references; this.segmentIndex++; } } @Override public void remove() { Assert.state(this.last != null, "No element to remove"); ConcurrentReferenceHashMap.this.remove(this.last.getKey()); this.last = null; } } /** * The types of restructuring that can be performed. */ protected enum Restructure { WHEN_NECESSARY, NEVER } /** * Strategy class used to manage {@link Reference References}. * This class can be overridden if alternative reference types need to be supported. */ protected class ReferenceManager { private final ReferenceQueue> queue = new ReferenceQueue<>(); /** * Factory method used to create a new {@link Reference}. * * @param entry the entry contained in the reference * @param hash the hash * @param next the next reference in the chain, or {@code null} if none * @return a new {@link Reference} */ public Reference createReference(Entry entry, int hash, @Nullable Reference next) { if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { return new WeakEntryReference<>(entry, hash, next, this.queue); } return new SoftEntryReference<>(entry, hash, next, this.queue); } /** * Return any reference that has been garbage collected and can be purged from the * underlying structure or {@code null} if no references need purging. This * method must be thread safe and ideally should not block when returning * {@code null}. References should be returned once and only once. * * @return a reference to purge or {@code null} */ @SuppressWarnings("unchecked") @Nullable public Reference pollForPurge() { return (Reference) this.queue.poll(); } } /** * Internal {@link Reference} implementation for {@link SoftReference SoftReferences}. */ private static final class SoftEntryReference extends SoftReference> implements Reference { private final int hash; @Nullable private final Reference nextReference; public SoftEntryReference(Entry entry, int hash, @Nullable Reference next, ReferenceQueue> queue) { super(entry, queue); this.hash = hash; this.nextReference = next; } @Override public int getHash() { return this.hash; } @Override @Nullable public Reference getNext() { return this.nextReference; } @Override public void release() { enqueue(); clear(); } } /** * Internal {@link Reference} implementation for {@link WeakReference WeakReferences}. */ private static final class WeakEntryReference extends WeakReference> implements Reference { private final int hash; @Nullable private final Reference nextReference; public WeakEntryReference(Entry entry, int hash, @Nullable Reference next, ReferenceQueue> queue) { super(entry, queue); this.hash = hash; this.nextReference = next; } @Override public int getHash() { return this.hash; } @Override @Nullable public Reference getNext() { return this.nextReference; } @Override public void release() { enqueue(); clear(); } } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/internal/util/ObjectUtils.java ================================================ /* * Copyright 2002-2021 the original author or 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 * * https://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 com.alibaba.ttl3.internal.util; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This source code file is copied from spring v5.3.24: // // https://github.com/spring-projects/spring-framework/blob/v5.3.24/spring-core/src/main/java/org/springframework/util/ObjectUtils.java // // with adoption: // - remove unused elements // - adjust visible modifier and code format // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Arrays; /** * Miscellaneous object utility methods. * *

Mainly for internal use within the framework. * *

Thanks to Alex Ruiz for contributing several enhancements to this class! * * @author Juergen Hoeller * @author Keith Donald * @author Rod Johnson * @author Rob Harrop * @author Chris Beams * @author Sam Brannen */ final class ObjectUtils { private static final int INITIAL_HASH = 7; private static final int MULTIPLIER = 31; //--------------------------------------------------------------------- // Convenience methods for content-based equality/hash-code handling //--------------------------------------------------------------------- /** * Determine if the given objects are equal, returning {@code true} if * both are {@code null} or {@code false} if only one is {@code null}. *

Compares arrays with {@code Arrays.equals}, performing an equality * check based on the array elements rather than the array reference. * * @param o1 first Object to compare * @param o2 second Object to compare * @return whether the given objects are equal * @see Object#equals(Object) * @see java.util.Arrays#equals */ public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) { if (o1 == o2) { return true; } if (o1 == null || o2 == null) { return false; } if (o1.equals(o2)) { return true; } if (o1.getClass().isArray() && o2.getClass().isArray()) { return arrayEquals(o1, o2); } return false; } /** * Compare the given arrays with {@code Arrays.equals}, performing an equality * check based on the array elements rather than the array reference. * * @param o1 first array to compare * @param o2 second array to compare * @return whether the given objects are equal * @see #nullSafeEquals(Object, Object) * @see java.util.Arrays#equals */ private static boolean arrayEquals(Object o1, Object o2) { if (o1 instanceof Object[] && o2 instanceof Object[]) { return Arrays.equals((Object[]) o1, (Object[]) o2); } if (o1 instanceof boolean[] && o2 instanceof boolean[]) { return Arrays.equals((boolean[]) o1, (boolean[]) o2); } if (o1 instanceof byte[] && o2 instanceof byte[]) { return Arrays.equals((byte[]) o1, (byte[]) o2); } if (o1 instanceof char[] && o2 instanceof char[]) { return Arrays.equals((char[]) o1, (char[]) o2); } if (o1 instanceof double[] && o2 instanceof double[]) { return Arrays.equals((double[]) o1, (double[]) o2); } if (o1 instanceof float[] && o2 instanceof float[]) { return Arrays.equals((float[]) o1, (float[]) o2); } if (o1 instanceof int[] && o2 instanceof int[]) { return Arrays.equals((int[]) o1, (int[]) o2); } if (o1 instanceof long[] && o2 instanceof long[]) { return Arrays.equals((long[]) o1, (long[]) o2); } if (o1 instanceof short[] && o2 instanceof short[]) { return Arrays.equals((short[]) o1, (short[]) o2); } return false; } /** * Return as hash code for the given object; typically the value of * {@code Object#hashCode()}}. If the object is an array, * this method will delegate to any of the {@code nullSafeHashCode} * methods for arrays in this class. If the object is {@code null}, * this method returns 0. * * @see Object#hashCode() * @see #nullSafeHashCode(Object[]) * @see #nullSafeHashCode(boolean[]) * @see #nullSafeHashCode(byte[]) * @see #nullSafeHashCode(char[]) * @see #nullSafeHashCode(double[]) * @see #nullSafeHashCode(float[]) * @see #nullSafeHashCode(int[]) * @see #nullSafeHashCode(long[]) * @see #nullSafeHashCode(short[]) */ public static int nullSafeHashCode(@Nullable Object obj) { if (obj == null) { return 0; } if (obj.getClass().isArray()) { if (obj instanceof Object[]) { return nullSafeHashCode((Object[]) obj); } if (obj instanceof boolean[]) { return nullSafeHashCode((boolean[]) obj); } if (obj instanceof byte[]) { return nullSafeHashCode((byte[]) obj); } if (obj instanceof char[]) { return nullSafeHashCode((char[]) obj); } if (obj instanceof double[]) { return nullSafeHashCode((double[]) obj); } if (obj instanceof float[]) { return nullSafeHashCode((float[]) obj); } if (obj instanceof int[]) { return nullSafeHashCode((int[]) obj); } if (obj instanceof long[]) { return nullSafeHashCode((long[]) obj); } if (obj instanceof short[]) { return nullSafeHashCode((short[]) obj); } } return obj.hashCode(); } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable Object[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (Object element : array) { hash = MULTIPLIER * hash + nullSafeHashCode(element); } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable boolean[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (boolean element : array) { hash = MULTIPLIER * hash + Boolean.hashCode(element); } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable byte[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (byte element : array) { hash = MULTIPLIER * hash + element; } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable char[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (char element : array) { hash = MULTIPLIER * hash + element; } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable double[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (double element : array) { hash = MULTIPLIER * hash + Double.hashCode(element); } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable float[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (float element : array) { hash = MULTIPLIER * hash + Float.hashCode(element); } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable int[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (int element : array) { hash = MULTIPLIER * hash + element; } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable long[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (long element : array) { hash = MULTIPLIER * hash + Long.hashCode(element); } return hash; } /** * Return a hash code based on the contents of the specified array. * If {@code array} is {@code null}, this method returns 0. */ public static int nullSafeHashCode(@Nullable short[] array) { if (array == null) { return 0; } int hash = INITIAL_HASH; for (short element : array) { hash = MULTIPLIER * hash + element; } return hash; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/internal/util/Utils.java ================================================ package com.alibaba.ttl3.internal.util; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.concurrent.ConcurrentMap; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Most source code of this file is copied from spring v5.3.24: // // https://github.com/spring-projects/spring-framework/blob/v5.3.24/spring-core/src/main/java/org/springframework/util/CollectionUtils.java // // with adoption: // - adjust visible modifier and code format // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @ApiStatus.Internal public final class Utils { /** * Default load factor for {@link HashMap}/{@link LinkedHashMap} variants. * * @see #newHashMap(int) */ private static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * Instantiate a new {@link HashMap} with an initial capacity * that can accommodate the specified number of elements without * any immediate resize/rehash operations to be expected. *

This differs from the regular {@link HashMap} constructor * which takes an initial capacity relative to a load factor * but is effectively aligned with the JDK's * {@link java.util.concurrent.ConcurrentHashMap#ConcurrentHashMap(int)}. * * @param expectedSize the expected number of elements (with a corresponding * capacity to be derived so that no resize/rehash operations are needed) */ public static HashMap newHashMap(int expectedSize) { return new HashMap<>(computeMapInitialCapacity(expectedSize), DEFAULT_LOAD_FACTOR); } /** * Instantiate a new {@code ConcurrentWeakHashMap} with an initial capacity * that can accommodate the specified number of elements without * any immediate resize/rehash operations to be expected. * * @param expectedSize the expected number of elements (with a corresponding * capacity to be derived so that no resize/rehash operations are needed) */ public static ConcurrentMap newConcurrentWeakHashMap(int expectedSize) { return new ConcurrentReferenceHashMap<>(computeMapInitialCapacity(expectedSize), ConcurrentReferenceHashMap.ReferenceType.WEAK); } private static int computeMapInitialCapacity(int expectedSize) { return (int) Math.ceil(expectedSize / (double) DEFAULT_LOAD_FACTOR); } /** * @see scala.util.control * @see github.com/scala/scala/blob/v2.13.9/src/library/scala/util/control/NonFatal.scala */ public static void propagateIfFatal(@Nullable Throwable throwable) { if (throwable == null) return; if (throwable instanceof VirtualMachineError || throwable instanceof ThreadDeath || throwable instanceof InterruptedException || throwable instanceof LinkageError) { sneakyThrow(throwable); } } @SuppressWarnings("unchecked") private static void sneakyThrow(Throwable e) throws E { throw (E) e; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/package-info.java ================================================ /** * TTL API. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.TransmittableThreadLocal * @see com.alibaba.ttl3.TtlRunnable * @see com.alibaba.ttl3.TtlCallable * @see com.alibaba.ttl3.TtlWrappers */ package com.alibaba.ttl3; ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachments.java ================================================ package com.alibaba.ttl3.spi; import edu.umd.cs.findbugs.annotations.NonNull; /** * The TTL attachments for TTL tasks, * eg: {@link com.alibaba.ttl3.TtlRunnable}, {@link com.alibaba.ttl3.TtlCallable}. * * @author Jerry Lee (oldratlee at gmail dot com) */ public interface TtlAttachments extends TtlEnhanced { /** * set the TTL attachments for TTL tasks * * @param key attachment key * @param value attachment value */ void setTtlAttachment(@NonNull String key, Object value); /** * get the TTL attachment for TTL tasks * * @param key attachment key */ T getTtlAttachment(@NonNull String key); /** * The attachment key of TTL task, weather this task is a auto wrapper task. *

* so the value of this attachment is a {@code boolean}. */ String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper"; } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlAttachmentsDelegate.java ================================================ package com.alibaba.ttl3.spi; import com.alibaba.ttl3.TtlWrappers; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * {@link TtlAttachments} delegate/implementation. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.TtlRunnable * @see com.alibaba.ttl3.TtlCallable */ public class TtlAttachmentsDelegate implements TtlAttachments { private final ConcurrentMap attachments = new ConcurrentHashMap<>(); @Override public void setTtlAttachment(@NonNull String key, Object value) { attachments.put(key, value); } @Override @SuppressWarnings("unchecked") public T getTtlAttachment(@NonNull String key) { return (T) attachments.get(key); } // ======== AutoWrapper Util Methods ======== /** * @see TtlAttachments#KEY_IS_AUTO_WRAPPER */ public static boolean isAutoWrapper(@Nullable Object ttlAttachments) { if (!(ttlAttachments instanceof TtlAttachments)) return false; final Boolean value = ((TtlAttachments) ttlAttachments).getTtlAttachment(KEY_IS_AUTO_WRAPPER); if (value == null) return false; return value; } /** * @see TtlAttachments#KEY_IS_AUTO_WRAPPER */ public static void setAutoWrapperAttachment(@Nullable Object ttlAttachment) { if (!(ttlAttachment instanceof TtlAttachments)) return; ((TtlAttachments) ttlAttachment).setTtlAttachment(TtlAttachments.KEY_IS_AUTO_WRAPPER, true); } /** * @see TtlAttachments#KEY_IS_AUTO_WRAPPER */ @Nullable public static T unwrapIfIsAutoWrapper(@Nullable T obj) { if (isAutoWrapper(obj)) return TtlWrappers.unwrap(obj); else return obj; } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlEnhanced.java ================================================ package com.alibaba.ttl3.spi; /** * a Ttl marker/tag interface, for ttl enhanced class, for example {@code TTL wrapper} * like {@link com.alibaba.ttl3.TtlRunnable}, {@link com.alibaba.ttl3.TtlCallable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.TtlRunnable * @see com.alibaba.ttl3.TtlCallable * @see com.alibaba.ttl3.TtlRecursiveAction * @see com.alibaba.ttl3.TtlRecursiveTask * @see TtlAttachments */ public interface TtlEnhanced { } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/spi/TtlWrapper.java ================================================ package com.alibaba.ttl3.spi; import edu.umd.cs.findbugs.annotations.NonNull; /** * Ttl Wrapper interface. *

* Used to mark wrapper types, for example: *

    *
  • {@link com.alibaba.ttl3.TtlRunnable TtlRunnable}s, {@link com.alibaba.ttl3.TtlCallable TtlCallable}s *
  • {@code TtlWrappers} created by util methods {@code wrap*()} of class * {@link com.alibaba.ttl3.TtlWrappers TtlWrappers}, * e.g. {@link com.alibaba.ttl3.TtlWrappers#wrapSupplier(java.util.function.Supplier) wrapSupplier} *
  • {@code TtlExecutors} created by util methods {@code get*()} of class * {@link com.alibaba.ttl3.executor.TtlExecutors TtlExecutors}, * e.g. {@link com.alibaba.ttl3.executor.TtlExecutors#getTtlExecutorService(java.util.concurrent.ExecutorService) getTtlExecutorService} *
  • {@code DisableInheritableThreadFactories} created by util method * {@link com.alibaba.ttl3.executor.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) getDisableInheritableThreadFactory} *
* * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.TtlWrappers#unwrap * @see com.alibaba.ttl3.TtlRunnable * @see com.alibaba.ttl3.TtlCallable * @see com.alibaba.ttl3.TtlWrappers * @see com.alibaba.ttl3.executor.TtlExecutors */ public interface TtlWrapper extends TtlEnhanced { /** * unwrap {@link TtlWrapper} to the original/underneath one. * * @see com.alibaba.ttl3.TtlWrappers#unwrap */ @NonNull T unwrap(); } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/spi/package-info.java ================================================ /** * TTL SPI contains internal types for {@code TTL} developer to integrate with {@code TTL}; * You will never need use this package in the biz/application codes. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.spi.TtlEnhanced * @see com.alibaba.ttl3.spi.TtlWrapper * @see com.alibaba.ttl3.spi.TtlAttachments */ package com.alibaba.ttl3.spi; ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/transmitter/ThreadLocalTransmitRegistry.java ================================================ package com.alibaba.ttl3.transmitter; import com.alibaba.ttl3.TransmittableThreadLocal; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import java.util.function.UnaryOperator; import java.util.logging.Logger; import static com.alibaba.ttl3.internal.util.Utils.newHashMap; /** * {@code ThreadLocalTransmitRegistry}, {@code ThreadLocal} transmit integration. *

* If you can not rewrite the existed code which use {@link ThreadLocal} to {@link TransmittableThreadLocal}, * register the {@link ThreadLocal} instances via the methods * {@link ThreadLocalTransmitRegistry#registerThreadLocal(ThreadLocal, UnaryOperator)} * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* {@code ThreadLocalTransmitRegistry} implement a {@link Transmittee} internally, * and register the {@link Transmittee} by {@link TransmitteeRegistry#registerTransmittee(Transmittee)} * to transmit all registered {@link ThreadLocal} instances. * *

* Below is the example code: * *

{@code
 * // the value of this ThreadLocal instance will be transmitted after registered
 * ThreadLocalTransmitRegistry.registerThreadLocal(aThreadLocal, generator);
 *
 * // Then the value of this ThreadLocal instance will not be transmitted after unregistered
 * ThreadLocalTransmitRegistry.unregisterThreadLocal(aThreadLocal);}
*

* The fields stored the {@code ThreadLocal} instances are generally {@code private static}, * so the {@code ThreadLocal} instances need be got by reflection, for example: * *

 * Field field = TheClassStoredThreadLocal.class.getDeclaredField(staticFieldName);
 * field.setAccessible(true);
 * {@code @SuppressWarnings("unchecked")}
 * {@code ThreadLocal} threadLocal = {@code (ThreadLocal)} field.get(null);
* * Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @author Jerry Lee (oldratlee at gmail dot com) * @see TransmitteeRegistry#registerTransmittee(Transmittee) */ public final class ThreadLocalTransmitRegistry { private static final Logger logger = Logger.getLogger(ThreadLocalTransmitRegistry.class.getName()); private static volatile WeakHashMap, UnaryOperator> threadLocalHolder = new WeakHashMap<>(); private static final Object threadLocalHolderUpdateLock = new Object(); /** * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, * it is unnecessary to register a {@link TransmittableThreadLocal} instance. *

* Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability * @param generator the value generator of type {@code UnaryOperator} * @return {@code true} if register the {@link ThreadLocal} instance and set {@code generator}, otherwise {@code false} * @see #registerThreadLocal(ThreadLocal, UnaryOperator, boolean) */ public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull UnaryOperator generator) { return registerThreadLocal(threadLocal, generator, false); } /** * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, * it is unnecessary to register a {@link TransmittableThreadLocal} instance. *

* Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability * @param generator the value generator of type {@code UnaryOperator} * @param force if {@code true}, update {@code generator} to {@link ThreadLocal} instance * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. * @return {@code true} if register the {@link ThreadLocal} instance and set {@code generator}, otherwise {@code false} * @see #registerThreadLocal(ThreadLocal, UnaryOperator) */ @SuppressWarnings("unchecked") public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull UnaryOperator generator, boolean force) { if (threadLocal instanceof TransmittableThreadLocal) { logger.warning("register a TransmittableThreadLocal instance, this is unnecessary!"); return true; } synchronized (threadLocalHolderUpdateLock) { if (!force && threadLocalHolder.containsKey(threadLocal)) return false; WeakHashMap, UnaryOperator> newHolder = new WeakHashMap<>(threadLocalHolder); newHolder.put((ThreadLocal) threadLocal, (UnaryOperator) generator); threadLocalHolder = newHolder; return true; } } /** * Unregister the {@link ThreadLocal} instances * to remove the Transmittable ability for the {@link ThreadLocal} instances. *

* If the {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * * @see #registerThreadLocal(ThreadLocal, UnaryOperator) */ public static boolean unregisterThreadLocal(@NonNull ThreadLocal threadLocal) { if (threadLocal instanceof TransmittableThreadLocal) { logger.warning("unregister a TransmittableThreadLocal instance, this is unnecessary!"); return true; } synchronized (threadLocalHolderUpdateLock) { if (!threadLocalHolder.containsKey(threadLocal)) return false; WeakHashMap, UnaryOperator> newHolder = new WeakHashMap<>(threadLocalHolder); newHolder.remove(threadLocal); threadLocalHolder = newHolder; return true; } } private static class ThreadLocalTransmittee implements Transmittee, Object>, HashMap, Object>> { private static final Object threadLocalClearMark = new Object(); @NonNull @Override public HashMap, Object> capture() { final HashMap, Object> threadLocal2Value = newHashMap(threadLocalHolder.size()); for (Map.Entry, UnaryOperator> entry : threadLocalHolder.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); final UnaryOperator generator = entry.getValue(); threadLocal2Value.put(threadLocal, generator.apply(threadLocal.get())); } return threadLocal2Value; } @NonNull @Override public HashMap, Object> replay(@NonNull HashMap, Object> captured) { final HashMap, Object> backup = newHashMap(captured.size()); for (Map.Entry, Object> entry : captured.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); backup.put(threadLocal, threadLocal.get()); final Object value = entry.getValue(); if (value == threadLocalClearMark) threadLocal.remove(); else threadLocal.set(value); } return backup; } @NonNull @Override public HashMap, Object> clear() { final HashMap, Object> threadLocal2Value = newHashMap(threadLocalHolder.size()); for (Map.Entry, UnaryOperator> entry : threadLocalHolder.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); threadLocal2Value.put(threadLocal, threadLocalClearMark); } return replay(threadLocal2Value); } @Override public void restore(@NonNull HashMap, Object> backup) { for (Map.Entry, Object> entry : backup.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } } private static final ThreadLocalTransmittee threadLocalTransmittee = new ThreadLocalTransmittee(); static { TransmitteeRegistry.registerTransmittee(threadLocalTransmittee); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private ThreadLocalTransmitRegistry() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmittee.java ================================================ package com.alibaba.ttl3.transmitter; import com.alibaba.crr.Transmittable; import edu.umd.cs.findbugs.annotations.NonNull; /** * The transmittee is the extension point to transmit {@code ThreadLocal}s * ({@code JDK} {@link ThreadLocal}, {@code FastThreadLocal} of {@code Netty}, etc.). *

* {@code ThreadLocal} transmittance is registered * by {@link TransmitteeRegistry#registerTransmittee(Transmittee)} method. *

* Transmittance process is represented by methods {@link #capture()} => * {@link #replay(Object)} => {@link #restore(Object)} (aka {@code CRR} operations). * * @param the transmittee capture data type * @param the transmittee backup data type * @author Jerry Lee (oldratlee at gmail dot com) * @see TransmitteeRegistry#registerTransmittee(Transmittee) * @see TransmitteeRegistry#unregisterTransmittee(Transmittee) */ public interface Transmittee extends Transmittable { /** * Capture. *

* NOTE: *

    *
  • do NOT return {@code null}.
  • *
  • do NOT throw any exceptions, just ignored.
  • *
* * @return the capture data of transmittee */ @NonNull C capture(); /** * Replay. *

* NOTE: *

    *
  • do NOT return {@code null}.
  • *
  • do NOT throw any exceptions, just ignored.
  • *
* * @param captured the capture data of transmittee, the return value of method {@link #capture()} * @return the backup data of transmittee */ @NonNull B replay(@NonNull C captured); /** * Clear. *

* NOTE: *

    *
  • do NOT return {@code null}.
  • *
  • do NOT throw any exceptions, just ignored.
  • *
*

* Semantically, the code {@code `B backup = clear();`} is same as {@code `B backup = replay(EMPTY_CAPTURE);`}. *

* The reason for providing this method is: *

    *
  1. lead to more readable code
  2. *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. *
* * @return the backup data of transmittee */ @NonNull B clear(); /** * Restore. *

* NOTE:
* do NOT throw any exceptions, just ignored. * * @param backup the backup data of transmittee, the return value of methods {@link #replay(Object)} or {@link #clear()} * @see #replay(Object) * @see #clear() */ void restore(@NonNull B backup); } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/transmitter/TransmitteeRegistry.java ================================================ package com.alibaba.ttl3.transmitter; import com.alibaba.ttl3.TransmittableThreadLocal; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Transmittee(aka {@code ThreadLocal}) Integration, * {@link TransmittableThreadLocal} and {@code JDK} {@link ThreadLocal} * (via {@link ThreadLocalTransmitRegistry}) have been builtin integrated. * *

About {@code JDK} {@link ThreadLocal} Integration

*

* If you can not rewrite the existed code which use {@code JDK} {@link ThreadLocal} * to {@link TransmittableThreadLocal}, register the {@link ThreadLocal} instances * via {@link ThreadLocalTransmitRegistry} * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. * *

Other {@code ThreadLocal} Integration

*

* For other {@code ThreadLocal}s integration(e.g. {@code FastThreadLocal} of {@code Netty}), * you can implement your own {@code XxxThreadLocalRegistry} * (e.g. {@code FastThreadLocalRegistry}) like {@link ThreadLocalTransmitRegistry}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see Transmittee * @see ThreadLocalTransmitRegistry */ public final class TransmitteeRegistry { /** * Register the transmittee({@code CRR}), the extension point for other {@code ThreadLocal}. * * @param the transmittee capture data type * @param the transmittee backup data type * @return true if the input transmittee is not registered * @see #unregisterTransmittee(Transmittee) */ public static boolean registerTransmittee(@NonNull Transmittee transmittee) { return Transmitter.compositeTransmittable.registerTransmittable(transmittee); } /** * Unregister the transmittee({@code CRR}), the extension point for other {@code ThreadLocal}. * * @param the transmittee capture data type * @param the transmittee backup data type * @return true if the input transmittee is registered * @see #registerTransmittee(Transmittee) */ public static boolean unregisterTransmittee(@NonNull Transmittee transmittee) { return Transmitter.compositeTransmittable.unregisterTransmittable(transmittee); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TransmitteeRegistry() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/transmitter/Transmitter.java ================================================ package com.alibaba.ttl3.transmitter; import com.alibaba.crr.TransmitCallback; import com.alibaba.crr.composite.Backup; import com.alibaba.crr.composite.Capture; import com.alibaba.crr.composite.CompositeTransmittable; import com.alibaba.crr.composite.CompositeTransmitCallback; import com.alibaba.ttl3.TransmittableThreadLocal; import com.alibaba.ttl3.TtlCallable; import com.alibaba.ttl3.TtlRunnable; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.concurrent.Callable; import java.util.function.Supplier; /** * {@link Transmitter} transmit all {@link TransmittableThreadLocal} * and other registered {@link ThreadLocal} values of the current thread to other thread. *

* Transmittance is completed by static methods {@link #capture()} => * {@link #replay(Capture)} => {@link #restore(Backup)} (aka {@code CRR} operations). * {@code JDK} {@link ThreadLocal} instances can be registered via {@link ThreadLocalTransmitRegistry}. *

* {@link Transmitter Transmitter} is internal manipulation api * for framework/middleware integration; * In general, you will never use it in the biz/application codes! * *

Executor framework/middleware integration to TTL transmittance

* Below is the example code: * *
{@code
 * ///////////////////////////////////////////////////////////////////////////
 * // in thread A, capture all TransmittableThreadLocal values of thread A
 * ///////////////////////////////////////////////////////////////////////////
 *
 * Capture captured = Transmitter.capture(); // (1)
 *
 * ///////////////////////////////////////////////////////////////////////////
 * // in thread B
 * ///////////////////////////////////////////////////////////////////////////
 *
 * // replay all TransmittableThreadLocal values from thread A
 * Backup backup = Transmitter.replay(captured); // (2)
 * try {
 *     // your biz logic, run with the TransmittableThreadLocal values of thread B
 *     System.out.println("Hello");
 *     // ...
 *     return "World";
 * } finally {
 *     // restore the TransmittableThreadLocal of thread B when replay
 *     Transmitter.restore(backup); // (3)
 * }}
*

* see the implementation code of {@link TtlRunnable} and {@link TtlCallable} * for more actual code samples. *

* Of course, {@link #replay(Capture)} and {@link #restore(Backup)} operations * can be simplified by util methods {@link #runCallableWithCaptured(Capture, Callable)} * or {@link #runSupplierWithCaptured(Capture, Supplier)} * and the adorable {@code Java 8 lambda syntax}. *

* Below is the example code: * *

{@code
 * ///////////////////////////////////////////////////////////////////////////
 * // in thread A, capture all TransmittableThreadLocal values of thread A
 * ///////////////////////////////////////////////////////////////////////////
 *
 * Capture captured = Transmitter.capture(); // (1)
 *
 * ///////////////////////////////////////////////////////////////////////////
 * // in thread B
 * ///////////////////////////////////////////////////////////////////////////
 *
 * String result = runSupplierWithCaptured(captured, () -> {
 *      // your biz logic, run with the TransmittableThreadLocal values of thread A
 *      System.out.println("Hello");
 *      ...
 *      return "World";
 * }); // (2) + (3)}
*

* The reason of providing 2 util methods is the different {@code throws Exception} type * to satisfy your biz logic({@code lambda}): *

    *
  1. {@link #runCallableWithCaptured(Capture, Callable)}: {@code throws Exception}
  2. *
  3. {@link #runSupplierWithCaptured(Capture, Supplier)}: No {@code throws}
  4. *
*

* If you need the different {@code throws Exception} type, * you can define your own util method alike for function interface({@code lambda}) * with your own {@code throws Exception} type. * *

Other ThreadLocal Integration

*

* If you can not rewrite the existed code which use {@code JDK} {@link ThreadLocal} * to {@link TransmittableThreadLocal}, register the {@link ThreadLocal} instances via method * {@link ThreadLocalTransmitRegistry#registerThreadLocal(ThreadLocal, java.util.function.UnaryOperator) ThreadLocalTransmitRegistry#registerThreadLocal} * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* For other {@code ThreadLocal}s integration(e.g. {@code FastThreadLocal} of {@code Netty}), * you can implement your own {@code XxxThreadLocalRegistry} * (e.g. {@code FastThreadLocalRegistry}) like {@link ThreadLocalTransmitRegistry}. * * @author Yang Fang (snoop dot fy at gmail dot com) * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlRunnable * @see TtlCallable * @see TransmitteeRegistry */ public final class Transmitter { private static final CompositeTransmitCallback compositeCallback = new CompositeTransmitCallback(); static final CompositeTransmittable compositeTransmittable = new CompositeTransmittable(compositeCallback); /** * Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread. * * @return the captured {@link TransmittableThreadLocal} values */ @NonNull public static Capture capture() { return compositeTransmittable.capture(); } /** * Replay the captured {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values from {@link #capture()}, * and return the backup {@link TransmittableThreadLocal} values in the current thread before replay. * * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} * @return the backup {@link TransmittableThreadLocal} values before replay * @see #capture() */ @NonNull public static Backup replay(@NonNull Capture captured) { return compositeTransmittable.replay(captured); } /** * Clear all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread, * and return the backup {@link TransmittableThreadLocal} values in the current thread before clear. *

* Semantically, the code {@code `Backup backup = clear();`} is same as {@code `Backup backup = replay(EMPTY_CAPTURE);`}. *

* The reason for providing this method is: * *

    *
  1. lead to more readable code
  2. *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. *
* * @return the backup {@link TransmittableThreadLocal} values before clear * @see #replay(Capture) */ @NonNull public static Backup clear() { return compositeTransmittable.clear(); } /** * Restore the backup {@link TransmittableThreadLocal} and * registered {@link ThreadLocal} values from {@link #replay(Capture)}/{@link #clear()}. * * @param backup the backup {@link TransmittableThreadLocal} values from {@link #replay(Capture)}/{@link #clear()} * @see #replay(Capture) * @see #clear() */ public static void restore(@NonNull Backup backup) { compositeTransmittable.restore(backup); } /** * Util method for simplifying {@link #replay(Capture)} and {@link #restore(Backup)} operations. * * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @see #capture() * @see #replay(Capture) * @see #restore(Backup) */ public static R runSupplierWithCaptured(@NonNull Capture captured, @NonNull Supplier bizLogic) { final Backup backup = replay(captured); try { return bizLogic.get(); } finally { restore(backup); } } /** * Util method for simplifying {@link #clear()} and {@link #restore(Backup)} operations. * * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @see #clear() * @see #restore(Backup) */ public static R runSupplierWithClear(@NonNull Supplier bizLogic) { final Backup backup = clear(); try { return bizLogic.get(); } finally { restore(backup); } } /** * Util method for simplifying {@link #replay(Capture)} and {@link #restore(Backup)} operations. * * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @throws Exception the exception threw by biz logic * @see #capture() * @see #replay(Capture) * @see #restore(Backup) */ @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public static R runCallableWithCaptured(@NonNull Capture captured, @NonNull Callable bizLogic) throws Exception { final Backup backup = replay(captured); try { return bizLogic.call(); } finally { restore(backup); } } /** * Util method for simplifying {@link #clear()} and {@link #restore(Backup)} operations. * * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @throws Exception the exception threw by biz logic * @see #clear() * @see #restore(Backup) */ @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public static R runCallableWithClear(@NonNull Callable bizLogic) throws Exception { final Backup backup = clear(); try { return bizLogic.call(); } finally { restore(backup); } } /** * Register the {@link TransmitCallback}. * * @return true if the input callback is not registered * @see #unregisterCallback(TransmitCallback) */ public static boolean registerCallback(@NonNull TransmitCallback callback) { return compositeCallback.registerCallback(callback); } /** * Unregister the {@link TransmitCallback}. * * @return true if the input callback is registered * @see #registerCallback(TransmitCallback) */ public static boolean unregisterCallback(@NonNull TransmitCallback callback) { return compositeCallback.unregisterCallback(callback); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private Transmitter() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl-core/src/main/java/com/alibaba/ttl3/transmitter/package-info.java ================================================ /** * The base(under layer) API for {@code TTL} developer to integrate with {@code TTL}; * You will never use this package in the biz/application codes. *

* Provide: * *

    *
  • Integration entrance({@link com.alibaba.ttl3.transmitter.Transmitter}) * for executors.
  • *
  • Extension point({@link com.alibaba.ttl3.transmitter.TransmitteeRegistry} * for other {@code ThreadLocal}s.
  • *
*

* {@code JDK} {@link java.lang.ThreadLocal} is builtin supported * by {@link com.alibaba.ttl3.transmitter.ThreadLocalTransmitRegistry}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl3.transmitter.Transmitter * @see com.alibaba.ttl3.transmitter.TransmitteeRegistry * @see com.alibaba.ttl3.transmitter.Transmittee * @see com.alibaba.ttl3.transmitter.ThreadLocalTransmitRegistry */ package com.alibaba.ttl3.transmitter; ================================================ FILE: ttl-core/src/main/javadoc/overview.html ================================================

This is the API documentation for the 📌 TransmittableThreadLocal(TTL), The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.

================================================ FILE: ttl-core/src/test/java/com/alibaba/Utils.kt ================================================ package com.alibaba import com.alibaba.ttl3.TtlCallable import com.alibaba.ttl3.TtlRunnable import com.alibaba.ttl3.agent.TtlAgentStatus import io.kotest.assertions.withClue import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.shouldBe import java.lang.Thread.sleep import java.time.Duration import java.util.concurrent.* /** * Expand thread pool, to pre-create and cache threads. */ fun expandThreadPool(executor: ExecutorService) { val cpuCountX2 = Runtime.getRuntime().availableProcessors() * 2 val count = if (executor is ThreadPoolExecutor) { (executor.maximumPoolSize * 2).coerceAtMost(cpuCountX2) } else cpuCountX2 (0 until count).map { executor.submit { sleep(10) } }.forEach { it.getForTest() } } //////////////////////////////////////////////////////////////////////////////// // shutdown/await util methods for test //////////////////////////////////////////////////////////////////////////////// private val timeout = Duration.ofSeconds(3) fun ExecutorService.shutdownForTest() { shutdown() withClue("Fail to shutdown thread pool") { awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS).shouldBeTrue() } } fun Future.getForTest(): T = this.get(timeout.toMillis(), TimeUnit.MILLISECONDS) //////////////////////////////////////////////////////////////////////////////// // TTL Agent //////////////////////////////////////////////////////////////////////////////// fun hasTtlAgentRun(): Boolean = TtlAgentStatus.getInstance().isTtlAgentLoaded.also { val key = "run-ttl-test-under-agent" if (it) { System.getProperties().containsKey(key).shouldBeTrue() System.getProperty(key) shouldBe "true" } else { System.getProperties().containsKey(key).shouldBeFalse() } } fun noTtlAgentRun(): Boolean = !hasTtlAgentRun() fun Runnable.ttlWrapIfNoTtlAgentRun() = if (noTtlAgentRun()) TtlRunnable.get(this)!! else this fun Callable.ttlWrapIfNoTtlAgentRun() = if (noTtlAgentRun()) TtlCallable.get(this)!! else this ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/cow/CowDemo.kt ================================================ package com.alibaba.demo.cow import com.alibaba.expandThreadPool import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.executor.TtlExecutors import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit fun main() { val threadPool: ExecutorService = Executors.newCachedThreadPool().let { expandThreadPool(it) TtlExecutors.getTtlExecutorService(it) }!! traceContext.print() threadPool.execute { traceContext.print() traceContext.increaseSpan() traceContext.print() threadPool.execute { traceContext.print() traceContext.increaseSpan() traceContext.print() } } Thread.sleep(100) threadPool.shutdown() threadPool.awaitTermination(1, TimeUnit.SECONDS) } private val traceContext = object : TransmittableThreadLocal() { override fun initialValue(): Trace = Trace("init", Span("first", 0)) override fun transmitteeValue(parentValue: Trace): Trace = parentValue.copy() // shadow copy Trace, this is fast override fun childValue(parentValue: Trace): Trace = parentValue.copy() // shadow copy Trace, this is fast fun increaseSpan() { get().run { // COW the Span object in Trace span = span.copy(id = "${span.id} + PONG", counter = span.counter + 1) } } override fun toString(): String { return "${get()}[${super.toString()}]" } } private fun TransmittableThreadLocal.print() { println("${Thread.currentThread().name}: $this") } private data class Trace(var name: String, var span: Span) private data class Span(val id: String, val counter: Int) ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/forkjoinpool/ForkJoinPoolDemo.kt ================================================ package com.alibaba.demo.forkjoinpool import java.util.concurrent.ForkJoinPool import java.util.concurrent.RecursiveTask /** * ForkJoinPool use demo. * * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val pool = ForkJoinPool.commonPool() val result = pool.invoke(SumTask(1..1000)) println("computed result: $result") // result is 500500 } private class SumTask(private val numbers: IntRange, private val forkLevel: Int = 0) : RecursiveTask() { override fun compute(): Int = if (numbers.count() <= 16) { println(String.format("direct compute %9s[%4s] at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // compute directly numbers.sum() } else { println(String.format("fork compute %9s[%4s] at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // split task val middle = numbers.first + numbers.count() / 2 val nextForkLevel = forkLevel + 1 val taskLeft = SumTask(numbers.first until middle, nextForkLevel) val taskRight = SumTask(middle..numbers.last, nextForkLevel) // fork-join compute taskLeft.fork() taskRight.fork() taskLeft.join() + taskRight.join() } } /* Output: fork compute 1..1000[1000] at fork level 0 @ thread main fork compute 1..500[ 500] at fork level 1 @ thread ForkJoinPool.commonPool-worker-19 fork compute 501..1000[ 500] at fork level 1 @ thread ForkJoinPool.commonPool-worker-5 fork compute 501..750[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-23 fork compute 751..1000[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-13 fork compute 251..500[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-27 fork compute 501..625[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-17 fork compute 1..250[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..875[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-13 fork compute 876..1000[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-3 fork compute 251..375[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-27 fork compute 376..500[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-7 fork compute 751..812[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 1..125[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-9 fork compute 376..437[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 fork compute 876..937[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 626..750[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-31 fork compute 376..406[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 1..62[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..781[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 fork compute 501..562[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 fork compute 251..312[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-27 fork compute 626..687[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 direct compute 751..765[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 876..906[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 563..625[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-21 fork compute 501..531[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 1..31[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 876..890[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 376..390[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 563..593[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 1..15[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 766..781[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 626..656[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 391..406[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 251..281[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 direct compute 16..31[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 563..577[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 891..906[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 407..437[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 32..62[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 578..593[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 501..515[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 407..421[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 907..937[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 251..265[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 626..640[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 782..812[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 907..921[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 266..281[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 516..531[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 422..437[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 594..625[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 922..937[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 32..46[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 532..562[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 282..312[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 direct compute 782..796[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 47..62[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 641..656[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 797..812[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 63..125[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 direct compute 282..296[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 532..546[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 938..1000[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 63..93[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 594..609[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 438..500[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 direct compute 547..562[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 297..312[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 fork compute 813..875[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 657..687[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 fork compute 438..468[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 313..375[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-27 direct compute 610..625[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 63..77[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 438..452[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 938..968[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 344..375[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 78..93[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 688..750[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 fork compute 94..125[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 938..952[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 688..718[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 direct compute 94..109[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 313..343[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 direct compute 657..671[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 813..843[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 110..125[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 672..687[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 fork compute 719..750[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 813..827[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 688..702[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 953..968[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 453..468[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 344..359[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 703..718[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 828..843[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 719..734[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 126..250[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-9 direct compute 313..327[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 fork compute 844..875[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 735..750[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 360..375[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 469..500[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 969..1000[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 860..875[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 188..250[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 direct compute 485..500[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 direct compute 969..984[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 844..859[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 126..187[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 direct compute 328..343[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 fork compute 219..250[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-23 fork compute 188..218[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 126..156[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 985..1000[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 469..484[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 188..202[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 219..234[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 direct compute 203..218[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 157..187[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 126..140[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 141..156[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 235..250[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-5 direct compute 172..187[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 157..171[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 computed result: 500500 */ ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/forkjoinpool/ParallelStreamDemo.kt ================================================ package com.alibaba.demo.forkjoinpool import java.util.concurrent.ConcurrentSkipListSet /** * Parallel Stream use demo. * * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { println("availableProcessors: ${Runtime.getRuntime().availableProcessors()}") val threadNames: MutableSet = ConcurrentSkipListSet() (0..100).toList().stream().parallel().mapToInt { threadNames.add(Thread.currentThread().name) Thread.sleep(10) println("map $it @ thread ${Thread.currentThread().name}") it }.sum().let { println("sum result: $it") } println(threadNames.joinToString( separator = "\n\t", prefix = "run threads(${threadNames.size}):\n\t" )) } /* Output: availableProcessors: 12 map 78 @ thread ForkJoinPool.commonPool-worker-7 map 76 @ thread ForkJoinPool.commonPool-worker-21 map 91 @ thread ForkJoinPool.commonPool-worker-19 map 71 @ thread ForkJoinPool.commonPool-worker-13 map 65 @ thread main map 97 @ thread ForkJoinPool.commonPool-worker-3 map 32 @ thread ForkJoinPool.commonPool-worker-5 map 15 @ thread ForkJoinPool.commonPool-worker-9 map 79 @ thread ForkJoinPool.commonPool-worker-17 map 82 @ thread ForkJoinPool.commonPool-worker-27 map 53 @ thread ForkJoinPool.commonPool-worker-31 map 57 @ thread ForkJoinPool.commonPool-worker-23 map 77 @ thread ForkJoinPool.commonPool-worker-21 map 86 @ thread ForkJoinPool.commonPool-worker-7 map 72 @ thread ForkJoinPool.commonPool-worker-13 map 92 @ thread ForkJoinPool.commonPool-worker-19 map 66 @ thread main map 83 @ thread ForkJoinPool.commonPool-worker-27 map 58 @ thread ForkJoinPool.commonPool-worker-23 map 54 @ thread ForkJoinPool.commonPool-worker-31 map 98 @ thread ForkJoinPool.commonPool-worker-3 map 33 @ thread ForkJoinPool.commonPool-worker-5 map 16 @ thread ForkJoinPool.commonPool-worker-9 map 80 @ thread ForkJoinPool.commonPool-worker-17 map 75 @ thread ForkJoinPool.commonPool-worker-21 map 87 @ thread ForkJoinPool.commonPool-worker-7 map 93 @ thread ForkJoinPool.commonPool-worker-19 map 73 @ thread ForkJoinPool.commonPool-worker-13 map 67 @ thread main map 81 @ thread ForkJoinPool.commonPool-worker-27 map 56 @ thread ForkJoinPool.commonPool-worker-23 map 55 @ thread ForkJoinPool.commonPool-worker-31 map 17 @ thread ForkJoinPool.commonPool-worker-9 map 99 @ thread ForkJoinPool.commonPool-worker-3 map 31 @ thread ForkJoinPool.commonPool-worker-5 map 84 @ thread ForkJoinPool.commonPool-worker-17 map 63 @ thread ForkJoinPool.commonPool-worker-19 map 89 @ thread ForkJoinPool.commonPool-worker-7 map 74 @ thread ForkJoinPool.commonPool-worker-13 map 95 @ thread ForkJoinPool.commonPool-worker-21 map 62 @ thread ForkJoinPool.commonPool-worker-27 map 60 @ thread ForkJoinPool.commonPool-worker-23 map 100 @ thread ForkJoinPool.commonPool-worker-3 map 51 @ thread ForkJoinPool.commonPool-worker-31 map 13 @ thread ForkJoinPool.commonPool-worker-9 map 35 @ thread ForkJoinPool.commonPool-worker-5 map 85 @ thread ForkJoinPool.commonPool-worker-17 map 64 @ thread ForkJoinPool.commonPool-worker-19 map 90 @ thread ForkJoinPool.commonPool-worker-7 map 69 @ thread ForkJoinPool.commonPool-worker-13 map 96 @ thread ForkJoinPool.commonPool-worker-21 map 94 @ thread ForkJoinPool.commonPool-worker-27 map 61 @ thread ForkJoinPool.commonPool-worker-23 map 14 @ thread ForkJoinPool.commonPool-worker-9 map 36 @ thread ForkJoinPool.commonPool-worker-5 map 44 @ thread ForkJoinPool.commonPool-worker-3 map 52 @ thread ForkJoinPool.commonPool-worker-31 map 88 @ thread ForkJoinPool.commonPool-worker-17 map 68 @ thread ForkJoinPool.commonPool-worker-19 map 40 @ thread ForkJoinPool.commonPool-worker-7 map 70 @ thread ForkJoinPool.commonPool-worker-13 map 48 @ thread ForkJoinPool.commonPool-worker-21 map 46 @ thread ForkJoinPool.commonPool-worker-27 map 59 @ thread ForkJoinPool.commonPool-worker-23 map 12 @ thread ForkJoinPool.commonPool-worker-9 map 34 @ thread ForkJoinPool.commonPool-worker-5 map 50 @ thread ForkJoinPool.commonPool-worker-31 map 38 @ thread ForkJoinPool.commonPool-worker-17 map 45 @ thread ForkJoinPool.commonPool-worker-3 map 41 @ thread ForkJoinPool.commonPool-worker-7 map 43 @ thread ForkJoinPool.commonPool-worker-19 map 28 @ thread ForkJoinPool.commonPool-worker-13 map 49 @ thread ForkJoinPool.commonPool-worker-21 map 47 @ thread ForkJoinPool.commonPool-worker-27 map 7 @ thread ForkJoinPool.commonPool-worker-23 map 21 @ thread ForkJoinPool.commonPool-worker-9 map 25 @ thread ForkJoinPool.commonPool-worker-3 map 3 @ thread ForkJoinPool.commonPool-worker-31 map 26 @ thread ForkJoinPool.commonPool-worker-5 map 39 @ thread ForkJoinPool.commonPool-worker-17 map 42 @ thread ForkJoinPool.commonPool-worker-7 map 19 @ thread ForkJoinPool.commonPool-worker-19 map 29 @ thread ForkJoinPool.commonPool-worker-13 map 1 @ thread ForkJoinPool.commonPool-worker-21 map 0 @ thread ForkJoinPool.commonPool-worker-27 map 8 @ thread ForkJoinPool.commonPool-worker-23 map 27 @ thread ForkJoinPool.commonPool-worker-5 map 22 @ thread ForkJoinPool.commonPool-worker-9 map 37 @ thread ForkJoinPool.commonPool-worker-17 map 4 @ thread ForkJoinPool.commonPool-worker-3 map 10 @ thread ForkJoinPool.commonPool-worker-31 map 20 @ thread ForkJoinPool.commonPool-worker-19 map 18 @ thread ForkJoinPool.commonPool-worker-7 map 30 @ thread ForkJoinPool.commonPool-worker-13 map 2 @ thread ForkJoinPool.commonPool-worker-21 map 23 @ thread ForkJoinPool.commonPool-worker-23 map 6 @ thread ForkJoinPool.commonPool-worker-27 map 9 @ thread ForkJoinPool.commonPool-worker-5 map 5 @ thread ForkJoinPool.commonPool-worker-3 map 11 @ thread ForkJoinPool.commonPool-worker-31 map 24 @ thread ForkJoinPool.commonPool-worker-23 sum result: 5050 run threads(12): ForkJoinPool.commonPool-worker-13 ForkJoinPool.commonPool-worker-17 ForkJoinPool.commonPool-worker-19 ForkJoinPool.commonPool-worker-21 ForkJoinPool.commonPool-worker-23 ForkJoinPool.commonPool-worker-27 ForkJoinPool.commonPool-worker-3 ForkJoinPool.commonPool-worker-31 ForkJoinPool.commonPool-worker-5 ForkJoinPool.commonPool-worker-7 ForkJoinPool.commonPool-worker-9 main */ ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/scheduled_thread_pool_executor/ScheduledFutureTaskDemo.kt ================================================ package com.alibaba.demo.scheduled_thread_pool_executor import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit /** * ScheduledThreadPoolExecutor usage demo for Issue 148 * https://github.com/alibaba/transmittable-thread-local/issues/148 */ fun main() { val scheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(10) val task = Runnable { println("I'm a Runnable task, I'm working...") } val scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(task, 500, 500, TimeUnit.MILLISECONDS) Thread.sleep(2_000) println("cancel") val cancelResult = scheduledFuture.cancel(false) println("canceled: $cancelResult") // scheduled task cancel success! Thread.sleep(2_000) scheduledThreadPoolExecutor.shutdown() println("Bye") } ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/session_cache/SessionCacheDemo.kt ================================================ package com.alibaba.demo.session_cache import com.alibaba.expandThreadPool import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.TtlRunnable import com.alibaba.ttl3.executor.TtlExecutors import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.booleans.shouldBeTrue import io.reactivex.Flowable import io.reactivex.plugins.RxJavaPlugins import io.reactivex.schedulers.Schedulers import java.util.concurrent.* class SessionCacheDemo : AnnotationSpec() { @Test fun invokeInThreadOfThreadPool() { val bizService = BizService() val printer: () -> Unit = { System.out.printf("[%20s] cache: %s%n", Thread.currentThread().name, bizService.getCacheItem()) } executorService.submit(Callable { bizService.getItemByCache().also { printer() } }).get() printer() // here service invocation will use item cache bizService.getItemByCache() printer() } @Test fun invokeInThreadOfRxJava() { val bizService = BizService() val printer: (Item) -> Unit = { System.out.printf("[%30s] cache: %s%n", Thread.currentThread().name, bizService.getCacheItem()) } Flowable.just(bizService) .observeOn(Schedulers.io()) .map(BizService::getItemByCache) .doOnNext(printer) .blockingSubscribe(printer) // here service invocation will use item cache bizService.getItemByCache() .let(printer) } @BeforeAll fun beforeAll() { // expand Schedulers.io() (0 until Runtime.getRuntime().availableProcessors() * 2) .map { FutureTask { Thread.sleep(10) it }.apply { Schedulers.io().scheduleDirect(this) } } .forEach { it.get() } // TTL integration for RxJava RxJavaPlugins.setScheduleHandler(TtlRunnable::get) } @AfterAll fun afterAll() { executorService.shutdown() // Fail to shut down thread pool executorService.awaitTermination(1, TimeUnit.SECONDS).shouldBeTrue() } @After fun tearDown() { BizService.clearCache() } companion object { private val executorService = Executors.newFixedThreadPool(3).let { expandThreadPool(it) // TTL integration for thread pool TtlExecutors.getTtlExecutorService(it)!! } } } /** * Mock Service */ private class BizService { init { // NOTE: AVOID cache object lazy init getCache() } fun getItem(): Item = Item(ThreadLocalRandom.current().nextInt(0, 10_000)) /** * get biz data, usually use spring cache. here is simple implementation */ fun getItemByCache(): Item { return getCache().computeIfAbsent(ONLY_KEY) { getItem() } } fun getCacheItem(): Item? = getCache()[ONLY_KEY] companion object { private const val ONLY_KEY = "ONLY_KEY" private val cacheContext = object : TransmittableThreadLocal>() { // init cache override fun initialValue(): ConcurrentMap = ConcurrentHashMap() } private fun getCache() = cacheContext.get() fun clearCache() { getCache().clear() } } } /** * Mock Cache Data */ private data class Item(val id: Int) ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/timer/TimerTaskDemo.kt ================================================ package com.alibaba.demo.timer import java.text.SimpleDateFormat import java.util.* /** * @see [Java Timer TimerTask Example](https://www.journaldev.com/1050/java-timer-timertask-example) */ fun main() { val timerTask = MyTimerTask() // running timer task as daemon thread val timer = Timer(true) timer.scheduleAtFixedRate(timerTask, 0, 300) println("TimerTask scheduled") // cancel after sometime Thread.sleep(1_000) timer.cancel() println("TimerTask cancelled") Thread.sleep(300) } private class MyTimerTask : TimerTask() { override fun run() { val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") println("Timer task started at: ${format.format(Date())}") Thread.sleep(200) println("Timer task finished at: ${format.format(Date())}") } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/ttl3/CustomizedBlockingQueueWithTtlDemo.java ================================================ package com.alibaba.demo.ttl3; import com.alibaba.ttl3.TtlRunnable; import com.alibaba.ttl3.executor.TtlExecutors; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Simple demo code for issue * https://github.com/alibaba/transmittable-thread-local/issues/340 */ public class CustomizedBlockingQueueWithTtlDemo { public static void main(String[] args) throws Exception { final MyBlockingQueue myBlockingQueue = new MyBlockingQueue(); final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 1, 1, 1L, TimeUnit.SECONDS, myBlockingQueue); final ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor); ttlExecutorService.execute(new MyTask("accept-1")); ttlExecutorService.execute(new MyTask("DISCARDED")); ttlExecutorService.execute(new MyTask("accept-2")); ttlExecutorService.shutdown(); if (!ttlExecutorService.awaitTermination(10, TimeUnit.SECONDS)) { throw new IllegalStateException("Fail to shutdown executor service"); } } private static class MyTask implements Runnable { private final String msg; private MyTask(String msg) { this.msg = msg; } @Override public void run() { System.out.println("MyTask: " + msg); } } private static class MyBlockingQueue extends ArrayBlockingQueue { public MyBlockingQueue() { super(16); } @Override public boolean offer(@NonNull Runnable runnable) { // unwrap TtlRunnable first final Runnable unwrap = TtlRunnable.unwrap(runnable); final MyTask myTask = (MyTask) unwrap; if (myTask.msg.startsWith("accept-")) { // ignore result/return value of offer, BAD!! // does not follow the contract of method offer // this is just a simple demo! super.offer(runnable); } // always return true even if discard the task, BAD!! // does not follow the contract of method offer // this is just a simple demo! return true; } @Override public void put(@NonNull Runnable runnable) throws InterruptedException { // unwrap TtlRunnable first final Runnable unwrap = TtlRunnable.unwrap(runnable); final MyTask myTask = (MyTask) unwrap; // discard task if not satisfied, BAD!! // does not follow the contract of method put // this is just a simple demo! if (myTask.msg.startsWith("accept-")) { super.put(runnable); } } } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/ttl3/SimpleDemo.kt ================================================ package com.alibaba.demo.ttl3 import com.alibaba.ttl3.TransmittableThreadLocal import kotlin.concurrent.thread /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val context = TransmittableThreadLocal() context.set("value-set-in-parent") println("[parent thread] set ${context.get()}") ///////////////////////////////////// // create sub-thread ///////////////////////////////////// thread { val value = context.get() println("[child thread] get $value") }.join() println("[parent thread] get ${context.get()}") } ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlExecutorServiceWithPriorityBlockingQueueDemo.kt ================================================ package com.alibaba.demo.ttl3 import com.alibaba.ttl3.executor.TtlExecutors import java.lang.Thread.sleep import java.util.concurrent.BlockingQueue import java.util.concurrent.PriorityBlockingQueue import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit fun main() { demoSubmitComparableTaskToTtlExecutorServiceWithPriorityBlockingQueue() demoSubmitOrderTaskToTtlExecutorServiceWithPriorityBlockingQueue() } /** * Demo for cooperation TTL executor(ThreadPoolExecutor) with PriorityBlockingQueue, for Comparable Runnable. * * if you use TTL Agent, no extra work(getTtlRunnableUnwrapComparatorForComparableRunnable) is need. * aka. rewriting PriorityBlockingQueue by TTL Agent automatically and transparently. */ private fun demoSubmitComparableTaskToTtlExecutorServiceWithPriorityBlockingQueue() { val comparator: Comparator = TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable() // explicit PriorityBlockingQueue Comparator argument // instead of default constructor PriorityBlockingQueue() // // aka. rewrite // val priorityBlockingQueue = PriorityBlockingQueue() // to val priorityBlockingQueue: BlockingQueue = PriorityBlockingQueue(11, comparator) val threadPoolExecutor = ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, priorityBlockingQueue) val ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor)!! ttlExecutorService.execute(BizComparableTask(0)) ttlExecutorService.execute(BizComparableTask(1)) ttlExecutorService.execute(BizComparableTask(2)) ttlExecutorService.execute(BizComparableTask(42)) ttlExecutorService.execute(BizComparableTask(9)) ttlExecutorService.execute(BizComparableTask(8)) ttlExecutorService.execute(BizComparableTask(7)) threadPoolExecutor.shutdown() threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS) } /** * Demo for cooperation TTL executor(ThreadPoolExecutor) with PriorityBlockingQueue, for Runnable Comparator. * * if you use TTL Agent, no extra work(getTtlRunnableUnwrapComparator) is need. * aka. rewriting Comparator by TTL Agent automatically and transparently. */ private fun demoSubmitOrderTaskToTtlExecutorServiceWithPriorityBlockingQueue() { val comparator: Comparator = compareBy { (it as BizOrderTask).order } val ttlRunnableUnwrapComparator: Comparator? = TtlExecutors.getTtlRunnableUnwrapComparator(comparator) // use TtlRunnableUnwrapComparator instead original comparator // // aka. rewrite // val priorityBlockingQueue = PriorityBlockingQueue(11, comparator) // to val priorityBlockingQueue: PriorityBlockingQueue = PriorityBlockingQueue(11, ttlRunnableUnwrapComparator) val threadPoolExecutor = ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, priorityBlockingQueue) val ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor)!! ttlExecutorService.execute(BizOrderTask(0)) ttlExecutorService.execute(BizOrderTask(1)) ttlExecutorService.execute(BizOrderTask(2)) ttlExecutorService.execute(BizOrderTask(42)) ttlExecutorService.execute(BizOrderTask(9)) ttlExecutorService.execute(BizOrderTask(8)) ttlExecutorService.execute(BizOrderTask(7)) threadPoolExecutor.shutdown() threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS) } private data class BizComparableTask(val num: Int) : Runnable, Comparable { override fun run() { sleep(10) println("run BizComparableTask $num") } override fun compareTo(other: Runnable): Int = num - (other as BizComparableTask).num } private data class BizOrderTask(val order: Int) : Runnable { override fun run() { sleep(10) println("run BizOrderTask $order") } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlExecutorWrapperDemo.kt ================================================ package com.alibaba.demo.ttl3 import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.executor.TtlExecutors import java.util.concurrent.Callable import java.util.concurrent.Executors /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val ttlExecutorService = Executors.newCachedThreadPool().let { // return TTL wrapper from normal ExecutorService TtlExecutors.getTtlExecutorService(it) }!! val context = TransmittableThreadLocal() context.set("value-set-in-parent") println("[parent thread] set ${context.get()}") ///////////////////////////////////// // Runnable ///////////////////////////////////// val task = Runnable { println("[child thread] get ${context.get()} in Runnable") } ttlExecutorService.submit(task).get() ///////////////////////////////////// // Callable ///////////////////////////////////// val call = Callable { println("[child thread] get ${context.get()} in Callable") 42 } ttlExecutorService.submit(call).get() ///////////////////////////////////// // cleanup ///////////////////////////////////// ttlExecutorService.shutdown() } ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlForkJoinTaskDemo.kt ================================================ package com.alibaba.demo.ttl3 import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.TtlRecursiveTask import java.util.concurrent.ForkJoinPool val context = TransmittableThreadLocal().apply { set("value-set-in-parent") println("[parent thread] set ${get()} @ thread ${Thread.currentThread().name}") } /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val pool = ForkJoinPool.commonPool() val result = pool.invoke(SumTask(1..1000)) println("[parent thread] computed result: $result @ thread ${Thread.currentThread().name}") // result is 500500 } private class SumTask(private val numbers: IntRange, private val forkLevel: Int = 0) : TtlRecursiveTask() { override fun compute(): Int = if (numbers.count() <= 16) { println(String.format("direct compute %9s[%4s] with context ${context.get()} at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // compute directly numbers.sum() } else { println(String.format("fork compute %9s[%4s] with context ${context.get()} at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // split task val middle = numbers.first + numbers.count() / 2 val nextForkLevel = forkLevel + 1 val taskLeft = SumTask(numbers.first until middle, nextForkLevel) val taskRight = SumTask(middle..numbers.last, nextForkLevel) // fork-join compute taskLeft.fork() taskRight.fork() taskLeft.join() + taskRight.join() } } /* Output: [parent thread] set value-set-in-parent @ thread main fork compute 1..1000[1000] with context value-set-in-parent at fork level 0 @ thread main fork compute 501..1000[ 500] with context value-set-in-parent at fork level 1 @ thread ForkJoinPool.commonPool-worker-5 fork compute 1..500[ 500] with context value-set-in-parent at fork level 1 @ thread ForkJoinPool.commonPool-worker-19 fork compute 1..250[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-23 fork compute 251..500[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..1000[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-27 fork compute 501..750[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-13 fork compute 126..250[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-31 fork compute 251..375[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-9 fork compute 876..1000[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-7 fork compute 1..125[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-17 fork compute 751..875[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-21 fork compute 376..500[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-3 fork compute 126..187[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 fork compute 251..312[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 fork compute 1..62[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 fork compute 501..625[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-13 fork compute 751..812[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-21 fork compute 876..937[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 fork compute 376..437[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 126..156[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 fork compute 1..31[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 251..281[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..781[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 fork compute 501..562[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 376..406[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 1..15[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 876..906[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 251..265[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 126..140[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 751..765[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 501..531[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 376..390[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 266..281[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 141..156[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 16..31[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 876..890[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 501..515[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 766..781[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 282..312[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 391..406[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 891..906[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 32..62[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 157..187[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 fork compute 782..812[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 516..531[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 282..296[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 32..46[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 407..437[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 532..562[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 157..171[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 907..937[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 47..62[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 532..546[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 297..312[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 782..796[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 907..921[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 172..187[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 407..421[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 313..375[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 direct compute 797..812[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 547..562[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 63..125[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 direct compute 422..437[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 188..250[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 direct compute 922..937[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 563..625[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 813..875[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-21 fork compute 313..343[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 fork compute 438..500[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 63..93[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 563..593[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 313..327[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 63..77[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 938..1000[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 fork compute 188..218[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 328..343[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 78..93[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 563..577[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 438..468[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 813..843[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 fork compute 94..125[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 344..375[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 188..202[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 438..452[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 938..968[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 94..109[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 813..827[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 578..593[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 110..125[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 938..952[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 453..468[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 203..218[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 344..359[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 594..625[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 828..843[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 469..500[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 219..250[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 direct compute 953..968[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 844..875[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 594..609[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 360..375[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 219..234[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 469..484[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 610..625[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 844..859[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 969..1000[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 485..500[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 235..250[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 626..750[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-13 direct compute 860..875[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 969..984[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 985..1000[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 688..750[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 fork compute 626..687[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 fork compute 688..718[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 fork compute 719..750[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 657..687[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 fork compute 626..656[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 688..702[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 719..734[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-19 direct compute 703..718[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 626..640[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 672..687[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 657..671[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 735..750[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 direct compute 641..656[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-19 [parent thread] computed result: 500500 @ thread main */ ================================================ FILE: ttl-core/src/test/java/com/alibaba/demo/ttl3/TtlWrapperDemo.kt ================================================ package com.alibaba.demo.ttl3 import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.TtlCallable import com.alibaba.ttl3.TtlRunnable import java.util.concurrent.Callable import java.util.concurrent.Executors /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val executorService = Executors.newCachedThreadPool() val context = TransmittableThreadLocal() context.set("value-set-in-parent") println("[parent thread] set ${context.get()}") ///////////////////////////////////// // Runnable / TtlRunnable ///////////////////////////////////// val task = Runnable { println("[child thread] get ${context.get()} in Runnable") } val ttlRunnable = TtlRunnable.get(task)!! executorService.submit(ttlRunnable).get() ///////////////////////////////////// // Callable / TtlCallable ///////////////////////////////////// val call = Callable { println("[child thread] get ${context.get()} in Callable") 42 } val ttlCallable = TtlCallable.get(call)!! executorService.submit(ttlCallable).get() ///////////////////////////////////// // cleanup ///////////////////////////////////// executorService.shutdown() } ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/Utils.kt ================================================ package com.alibaba.perf import java.util.* private val random = Random() internal fun bytes2Hex(bytes: ByteArray): String { val sb = StringBuilder(1024) for (b in bytes) { val s = Integer.toHexString(b.toInt() and 0xFF) sb.append(if (s.length == 1) "0$s" else s) } return sb.toString() } internal fun getRandomBytes(): ByteArray { val bytes = ByteArray(1024) random.nextBytes(bytes) return bytes } internal fun getRandomString(): String { return bytes2Hex(getRandomBytes()) } ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/memoryleak/NoMemoryLeak_ThreadLocal_NoRemove.kt ================================================ package com.alibaba.perf.memoryleak import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { var counter: Long = 0 while (true) { val threadLocal = ThreadLocal() threadLocal.set(getRandomString()) if (counter % 1000 == 0L) System.out.printf("%05dK%n", counter / 1000) counter++ } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/memoryleak/NoMemoryLeak_TransmittableThreadLocal_NoRemove.kt ================================================ package com.alibaba.perf.memoryleak import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { var counter: Long = 0 while (true) { val threadLocal = TransmittableThreadLocal() threadLocal.set(getRandomString()) if (counter % 1000 == 0L) System.out.printf("%05dK%n", counter / 1000) counter++ } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/package-info.java ================================================ /** * Performance test cases. *

* TPS test: *

    *
  • {@link com.alibaba.perf.tps.CreateTransmittableThreadLocalInstanceTpsKt#main()}
  • *
  • {@link com.alibaba.perf.tps.CreateThreadLocalInstanceTpsKt#main()}
  • *
*

* Memory leak test: *

    *
  • {@link com.alibaba.perf.memoryleak.NoMemoryLeak_TransmittableThreadLocal_NoRemoveKt#main()}
  • *
  • {@link com.alibaba.perf.memoryleak.NoMemoryLeak_ThreadLocal_NoRemoveKt#main()}
  • *
* * @author Jerry Lee (oldratlee at gmail dot com) */ package com.alibaba.perf; ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/tps/CreateThreadLocalInstanceTps.kt ================================================ package com.alibaba.perf.tps import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val tpsCounter = TpsCounter(2) tpsCounter.setAction { val threadLocal = ThreadLocal() threadLocal.set(getRandomString()) } while (true) { val start = tpsCounter.count Thread.sleep(1000) System.out.printf("tps: %,d\n", tpsCounter.count - start) } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/tps/CreateTransmittableThreadLocalInstanceTps.kt ================================================ package com.alibaba.perf.tps import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val tpsCounter = TpsCounter(2) tpsCounter.setAction { val threadLocal = TransmittableThreadLocal() threadLocal.set(getRandomString()) } while (true) { val start = tpsCounter.count Thread.sleep(1000) System.out.printf("tps: %d\n", tpsCounter.count - start) } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/perf/tps/TpsCounter.kt ================================================ package com.alibaba.perf.tps import com.alibaba.shutdownForTest import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicLong /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TpsCounter internal constructor(private val threadCount: Int) { private val executorService: ExecutorService = Executors.newFixedThreadPool(threadCount) private val counter = AtomicLong() @Volatile private var stopped = false val count: Long get() = counter.get() internal fun setAction(runnable: Runnable) { val r = { while (!stopped) { runnable.run() counter.incrementAndGet() } } for (i in 0 until threadCount) { executorService.execute(r) } } fun stop() { stopped = true executorService.shutdownForTest() } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/third_part_lib_test/ExecutorsTest.kt ================================================ package com.alibaba.third_part_lib_test import com.alibaba.shutdownForTest import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.booleans.shouldBeTrue import java.util.concurrent.Executors import java.util.concurrent.ThreadPoolExecutor class ExecutorsTest : AnnotationSpec() { @Test fun test_remove_of_ThreadPoolExecutor() { val size = 2 val threadPool = Executors.newFixedThreadPool(size) as ThreadPoolExecutor val futures = (0..size * 2).map { threadPool.submit { Thread.sleep(10) } } Runnable { println("Task should be removed!") }.let { threadPool.execute(it) threadPool.remove(it).shouldBeTrue() threadPool.remove(it).shouldBeFalse() } // wait sleep task finished. futures.forEach { it.get() } threadPool.shutdownForTest() } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/third_part_lib_test/ForkJoinPoolTest.kt ================================================ package com.alibaba.third_part_lib_test import com.alibaba.shutdownForTest import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.shouldBe import java.util.concurrent.ForkJoinPool import java.util.concurrent.RecursiveTask import java.util.concurrent.atomic.AtomicInteger class ForkJoinPoolTest : AnnotationSpec() { @Test fun test_sameTaskDirectReturn_onlyExec1Time_ifHaveRun() { val pool = ForkJoinPool() val numbers = 1L..100L val sumTask = SumTask(numbers) // same task instance run 10 times for (i in 0..9) { pool.invoke(sumTask) shouldBe numbers.sum() } sumTask.execCounter.get() shouldBe 1 pool.shutdownForTest() } } private class SumTask(private val numbers: LongRange) : RecursiveTask() { val execCounter = AtomicInteger(0) override fun compute(): Long { execCounter.incrementAndGet() return if (numbers.count() <= 16) { // compute directly numbers.sum() } else { // split task val middle = numbers.first + numbers.count() / 2 val taskLeft = SumTask(numbers.first until middle) val taskRight = SumTask(middle..numbers.last) taskLeft.fork() taskRight.fork() taskLeft.join() + taskRight.join() } } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/ttl3/TtlCallableTest.kt ================================================ package com.alibaba.ttl3 import com.alibaba.expandThreadPool import com.alibaba.getForTest import com.alibaba.shutdownForTest import com.alibaba.ttlWrapIfNoTtlAgentRun import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainInOrder import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.matchers.types.shouldBeTypeOf import java.util.concurrent.* import kotlin.concurrent.thread /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlCallableTest : FunSpec({ lateinit var executorService: ExecutorService beforeSpec { executorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } afterSpec { executorService.shutdownForTest() } ttlFlowTest("run in current thread") { checkLogicInTask -> val ttlCallable = TtlCallable.get { checkLogicInTask() 42 }!! val taskRunner: TaskRunner = { // run in the *current* thread ttlCallable.call() CompletableFuture.completedFuture(null) } taskRunner } ttlFlowTest("async run by new thread") { checkLogicInTask -> val ttlCallable = TtlCallable.get { checkLogicInTask() 42 }!! val taskRunner: TaskRunner = { val future = CompletableFuture() // run in new thread thread { ttlCallable.call() future.complete(null) } future } taskRunner } ttlFlowTest("async run by executor service", captureTimeByAgentRun) { checkLogicInTask -> val callable = Callable { checkLogicInTask() 42 }.ttlWrapIfNoTtlAgentRun() val taskRunner: TaskRunner = { executorService.submit(callable) } taskRunner } test("release ttl value reference after call") { val call = Callable { 42 } val ttlCallable = TtlCallable.get(call, true)!! ttlCallable.callable shouldBeSameInstanceAs call executorService.submit(ttlCallable).getForTest() shouldBe 42 val exception = shouldThrow { executorService.submit(ttlCallable).getForTest() } exception.cause.shouldBeTypeOf() exception.message shouldContain "TTL value reference is released after call!" } test("get same") { val call = Callable { 42 } val ttlCallable = TtlCallable.get(call)!! ttlCallable.callable shouldBeSameInstanceAs call } test("get idempotent") { val call = TtlCallable.get { 42 } shouldThrow { TtlCallable.get(call) }.message shouldContain "Already TtlCallable" } test("get null input") { TtlCallable.get(null).shouldBeNull() } test("gets") { val call1 = Callable { 1 } val call2 = Callable { 2 } val call3 = Callable { 3 } val callList = TtlCallable.gets( listOf(call1, call2, null, call3) ) callList.shouldHaveSize(4) callList[0].shouldBeTypeOf>() callList[1].shouldBeTypeOf>() callList[2].shouldBeNull() callList[3].shouldBeTypeOf>() } test("unwrap") { TtlCallable.unwrap(null).shouldBeNull() val callable = Callable { "hello" } val ttlCallable = TtlCallable.get(callable) TtlCallable.unwrap(callable) shouldBe callable TtlCallable.unwrap(ttlCallable) shouldBe callable TtlWrappers.unwrap(callable) shouldBe callable TtlWrappers.unwrap(ttlCallable) shouldBe callable TtlCallable.unwraps(listOf(callable)).shouldContainInOrder(callable) TtlCallable.unwraps(listOf(ttlCallable)).shouldContainInOrder(callable) TtlCallable.unwraps(listOf(ttlCallable, callable)).shouldContainInOrder(callable, callable) TtlCallable.unwraps(null).shouldBeEmpty() } }) ================================================ FILE: ttl-core/src/test/java/com/alibaba/ttl3/TtlFlowTester.kt ================================================ package com.alibaba.ttl3 import com.alibaba.getForTest import com.alibaba.noTtlAgentRun import io.kotest.assertions.fail import io.kotest.core.spec.style.scopes.FunSpecRootScope import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.Future import kotlin.random.Random //////////////////////////////////////////////////////////////////////////////// // Entry Methods //////////////////////////////////////////////////////////////////////////////// typealias CheckLogicInTask = () -> Unit fun CheckLogicInTask.toRunnable() = Runnable { this() } typealias TaskRunner = () -> Future<*> /** * - create task immediately with input [CheckLogicInTask] parameter * - return task runner to run task later */ typealias TaskRunnerGenerator = (CheckLogicInTask) -> TaskRunner enum class CaptureTime { TASK_CREATE, TASK_RUN, } fun FunSpecRootScope.ttlFlowTest( name: String, captureTime: CaptureTime = CaptureTime.TASK_CREATE, taskRunnerGenerator: TaskRunnerGenerator, ) { test(name) { runTtlFlowTest(captureTime, taskRunnerGenerator) } } val captureTimeByAgentRun = if (noTtlAgentRun()) CaptureTime.TASK_CREATE else CaptureTime.TASK_RUN //////////////////////////////////////////////////////////////////////////////// // TTL Flow Tester //////////////////////////////////////////////////////////////////////////////// private fun runTtlFlowTest( captureTime: CaptureTime = CaptureTime.TASK_CREATE, taskRunnerGenerator: TaskRunnerGenerator ) { TtlFlowTester(captureTime, taskRunnerGenerator).run() } private class TtlFlowTester( private val captureTime: CaptureTime = CaptureTime.TASK_CREATE, private val taskRunnerGenerator: TaskRunnerGenerator ) { fun run() { register(::StringTtlCreateAtParentBeginAndRemoveImmediately) register(::StringTtlCreateAtParentBegin.fill()) register(::DeepCopyTtlCreateAtParentBeginUnmodifiedInChild) register(::DeepCopyTtlCreateAtParentBeginModifiedInChild) compositeTtlFlow.beginInParent() ////////////////////////////////////////////// // create task ////////////////////////////////////////////// val task = taskRunnerGenerator(::checkInTask) compositeTtlFlow.afterTaskCreatedInParent() register(::StringTtlCreateAtParentAfterCreateTask.fill()) register(::DeepCopyTtlCreateAtParentAfterCreateTask) ////////////////////////////////////////////// // start task ////////////////////////////////////////////// val future: Future<*> = task() compositeTtlFlow.afterTaskStartedInParent() register(::StringTtlCreateAtParentAfterStartTask) ////////////////////////////////////////////// // finish task ////////////////////////////////////////////// future.getForTest() compositeTtlFlow.afterTaskFinishedInParent() } private fun checkInTask() { register(::StringTtlCreateAtChildBegin) register(::DeepCopyTtlCreateAtChildBegin) compositeTtlFlow.beginInChild() compositeTtlFlow.endInChild() } private val compositeTtlFlow = CompositeTtlFlow() private inline fun register(factory: () -> TtlFlow) { val ttlFlow = factory() compositeTtlFlow.register(ttlFlow) ttlFlow.settingAfterConstruct() } private fun ((CaptureTime) -> TtlFlow).fill(): () -> TtlFlow = { this(captureTime) } } private interface TtlFlow { fun settingAfterConstruct() fun beginInParent() fun afterTaskCreatedInParent() fun afterTaskStartedInParent() fun beginInChild() fun endInChild() fun afterTaskFinishedInParent() } /** * TTL instance create in parent thread when begin parent * and remove value immediately * * same as not set */ private class StringTtlCreateAtParentBeginAndRemoveImmediately : TransmittableThreadLocal(), TtlFlow { override fun settingAfterConstruct() { set("init ${Random.nextLong()}") } override fun beginInParent() { remove() } override fun afterTaskCreatedInParent() { } override fun afterTaskStartedInParent() { } override fun beginInChild() { get().shouldBeNull() } override fun endInChild() { get().shouldBeNull() set(Random.nextLong().toString()) } override fun afterTaskFinishedInParent() { get().shouldBeNull() } } /** * TTL instance create in parent thread when begin parent thread */ private class StringTtlCreateAtParentBegin(private val captureTime: CaptureTime) : TransmittableThreadLocal(), TtlFlow { private val initValue = "init ${Random.nextLong()}" private val modifiedValue = "modified ${Random.nextLong()}" override fun settingAfterConstruct() { set(initValue) } override fun beginInParent() { } override fun afterTaskCreatedInParent() { get() shouldBe initValue set(modifiedValue) } override fun afterTaskStartedInParent() { get() shouldBe modifiedValue } override fun beginInChild() { if (captureTime == CaptureTime.TASK_CREATE) { get() shouldBe initValue } else { get() shouldBe modifiedValue } } override fun endInChild() { set("modified by child ${Random.nextLong()}") } override fun afterTaskFinishedInParent() { get() shouldBe modifiedValue } } /** * TTL instance create in parent thread after create task */ private class StringTtlCreateAtParentAfterCreateTask( private val captureTime: CaptureTime ) : TransmittableThreadLocal(), TtlFlow { private val initValue = "init ${Random.nextLong()}" override fun settingAfterConstruct() { set(initValue) } override fun beginInParent() { fail("should never be called") } override fun afterTaskCreatedInParent() { fail("should never be called") } override fun afterTaskStartedInParent() { get() shouldBe initValue } override fun beginInChild() { if (captureTime == CaptureTime.TASK_CREATE) { get().shouldBeNull() } else { get() shouldBe initValue } } override fun endInChild() { set("modified by child ${Random.nextLong()}") } override fun afterTaskFinishedInParent() { get() shouldBe initValue } } /** * TTL instance create in parent thread after start task */ private class StringTtlCreateAtParentAfterStartTask : TransmittableThreadLocal(), TtlFlow { private val initValue = "init ${Random.nextLong()}" override fun settingAfterConstruct() { set(initValue) } override fun beginInParent() { fail("should never be called") } override fun afterTaskCreatedInParent() { fail("should never be called") } override fun afterTaskStartedInParent() { fail("should never be called") } override fun beginInChild() { get().shouldBeNull() } override fun endInChild() { set("modified by child ${Random.nextLong()}") } override fun afterTaskFinishedInParent() { get() shouldBe initValue } } /** * TTL instance create in child thread when begin child task */ private class StringTtlCreateAtChildBegin : TransmittableThreadLocal(), TtlFlow { private val initValue = "init ${Random.nextLong()}" override fun settingAfterConstruct() { set(initValue) } override fun beginInParent() { fail("should never be called") } override fun afterTaskCreatedInParent() { fail("should never be called") } override fun afterTaskStartedInParent() { } override fun beginInChild() { } override fun endInChild() { get() shouldBe initValue } override fun afterTaskFinishedInParent() { get().shouldBeNull() } } private class DeepCopyTtlCreateAtParentBeginUnmodifiedInChild : TransmittableThreadLocal(), TtlFlow { override fun transmitteeValue(parentValue: Pojo?): Pojo? = parentValue?.copy() private val initName = "parent create unmodified in child ${Random.nextLong()}" private val initAge = Random.nextInt() override fun settingAfterConstruct() { set(Pojo(initName, initAge)) } override fun beginInParent() { } override fun afterTaskCreatedInParent() { } override fun afterTaskStartedInParent() { } override fun beginInChild() { get() shouldBe Pojo(initName, initAge) } override fun endInChild() { } override fun afterTaskFinishedInParent() { get() shouldBe Pojo(initName, initAge) } } private class DeepCopyTtlCreateAtParentBeginModifiedInChild : TransmittableThreadLocal(), TtlFlow { override fun transmitteeValue(parentValue: Pojo?): Pojo? = parentValue?.copy() private val initName = "parent create modified in child ${Random.nextLong()}" private val initAge = Random.nextInt() override fun settingAfterConstruct() { set(Pojo(initName, initAge)) } override fun beginInParent() { } override fun afterTaskCreatedInParent() { } override fun afterTaskStartedInParent() { } override fun beginInChild() { get() shouldBe Pojo(initName, initAge) get()!!.name = "modified in child ${Random.nextLong()}" get()!!.age++ } override fun endInChild() { } override fun afterTaskFinishedInParent() { get() shouldBe Pojo(initName, initAge) } } private class DeepCopyTtlCreateAtParentAfterCreateTask : TransmittableThreadLocal(), TtlFlow { override fun transmitteeValue(parentValue: Pojo?): Pojo? = parentValue?.copy() private val initName = "parent create after create task ${Random.nextLong()}" private val initAge = Random.nextInt() override fun settingAfterConstruct() { set(Pojo(initName, initAge)) } override fun beginInParent() { fail("should never be called") } override fun afterTaskCreatedInParent() { fail("should never be called") } override fun afterTaskStartedInParent() { } override fun beginInChild() { get().shouldBeNull() } override fun endInChild() { } override fun afterTaskFinishedInParent() { get() shouldBe Pojo(initName, initAge) } } private class DeepCopyTtlCreateAtChildBegin : TransmittableThreadLocal(), TtlFlow { override fun transmitteeValue(parentValue: Pojo?): Pojo? = parentValue?.copy() private val initName = "create at child begin ${Random.nextLong()}" private val initAge = Random.nextInt() override fun settingAfterConstruct() { set(Pojo(initName, initAge)) } override fun beginInParent() { fail("should never be called") } override fun afterTaskCreatedInParent() { fail("should never be called") } override fun afterTaskStartedInParent() { } override fun beginInChild() { get() shouldBe Pojo(initName, initAge) } override fun endInChild() { } override fun afterTaskFinishedInParent() { get().shouldBeNull() } } private class CompositeTtlFlow : TtlFlow { private val instances = CopyOnWriteArraySet() fun register(ttlFlow: TtlFlow) { instances.add(ttlFlow).shouldBeTrue() } override fun settingAfterConstruct() { fail("should never be called") } override fun beginInParent() { for (ttl in instances.shuffled()) { ttl.beginInParent() } } override fun afterTaskCreatedInParent() { for (ttl in instances.shuffled()) { ttl.afterTaskCreatedInParent() } } override fun afterTaskStartedInParent() { for (ttl in instances.shuffled()) { ttl.afterTaskStartedInParent() } } override fun beginInChild() { for (ttl in instances.shuffled()) { ttl.beginInChild() } } override fun endInChild() { for (ttl in instances.shuffled()) { ttl.endInChild() } } override fun afterTaskFinishedInParent() { for (ttl in instances.shuffled()) { ttl.afterTaskFinishedInParent() } } } //////////////////////////////////////////////////////////////////////////////// // TTL classes for test //////////////////////////////////////////////////////////////////////////////// private data class Pojo(var name: String?, var age: Int) ================================================ FILE: ttl-core/src/test/java/com/alibaba/ttl3/TtlRunnableTest.kt ================================================ package com.alibaba.ttl3 import com.alibaba.expandThreadPool import com.alibaba.getForTest import com.alibaba.shutdownForTest import com.alibaba.ttlWrapIfNoTtlAgentRun import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainInOrder import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.matchers.types.shouldBeTypeOf import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import kotlin.concurrent.thread /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlRunnableTest : FunSpec({ lateinit var executorService: ExecutorService beforeSpec { executorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } afterSpec { executorService.shutdownForTest() } ttlFlowTest("run in current thread") { checkLogicInTask -> val ttlRunnable = TtlRunnable.get(checkLogicInTask)!! val taskRunner: TaskRunner = { // run in the *current* thread ttlRunnable.run() CompletableFuture.completedFuture(null) } taskRunner } ttlFlowTest("async run by new thread") { checkLogicInTask -> val ttlRunnable = TtlRunnable.get(checkLogicInTask)!! val taskRunner: TaskRunner = { val future = CompletableFuture() // run in new thread thread { ttlRunnable.run() future.complete(null) } future } taskRunner } ttlFlowTest("async run by executor service", captureTimeByAgentRun) { checkLogicInTask -> val runnable: Runnable = checkLogicInTask.toRunnable().ttlWrapIfNoTtlAgentRun() val taskRunner: TaskRunner = { executorService.submit(runnable) } taskRunner } test("release ttl value reference after run") { val ttlRunnable = TtlRunnable.get({ }, true)!! executorService.submit(ttlRunnable).getForTest().shouldBeNull() val exception = shouldThrow { executorService.submit(ttlRunnable).get() } exception.cause.shouldBeTypeOf() exception.message shouldContain "TTL value reference is released after run!" } test("get same") { val task = Runnable {} val ttlRunnable = TtlRunnable.get(task)!! ttlRunnable.runnable shouldBeSameInstanceAs task } test("get idempotent") { val task = TtlRunnable.get {} shouldThrow { TtlRunnable.get(task) }.message shouldContain "Already TtlRunnable" } test("get null input") { TtlRunnable.get(null).shouldBeNull() } test("gets") { val task1 = Runnable {} val task2 = Runnable {} val task3 = Runnable {} val taskList = TtlRunnable.gets(listOf(task1, task2, null, task3)) taskList.shouldHaveSize(4) taskList[0].shouldBeTypeOf() taskList[1].shouldBeTypeOf() taskList[2].shouldBeNull() taskList[3].shouldBeTypeOf() } test("unwrap") { TtlRunnable.unwrap(null).shouldBeNull() val runnable = Runnable {} val ttlRunnable = TtlRunnable.get(runnable) TtlRunnable.unwrap(runnable) shouldBeSameInstanceAs runnable TtlRunnable.unwrap(ttlRunnable) shouldBeSameInstanceAs runnable TtlWrappers.unwrap(runnable) shouldBeSameInstanceAs runnable TtlWrappers.unwrap(ttlRunnable) shouldBeSameInstanceAs runnable TtlRunnable.unwraps(listOf(runnable)).shouldContainInOrder(runnable) TtlRunnable.unwraps(listOf(ttlRunnable)).shouldContainInOrder(runnable) TtlRunnable.unwraps(listOf(ttlRunnable, runnable)).shouldContainInOrder(runnable, runnable) TtlRunnable.unwraps(null).shouldBeEmpty() } }) ================================================ FILE: ttl-core/src/test/java/com/alibaba/ttl3/TtlTimerTaskTest.kt ================================================ package com.alibaba.ttl3 import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.matchers.types.shouldBeTypeOf import java.util.* @Suppress("DEPRECATION") class TtlTimerTaskTest : AnnotationSpec() { @Test fun test_get() { TtlTimerTask.get(null).shouldBeNull() val timerTask = object : TimerTask() { override fun run() {} } val ttlTimerTask = TtlTimerTask.get(timerTask) ttlTimerTask.shouldBeTypeOf() shouldThrow { TtlTimerTask.get(ttlTimerTask) }.message shouldBe "Already TtlTimerTask!" } @Test fun test_unwrap() { TtlTimerTask.unwrap(null).shouldBeNull() val timerTask = object : TimerTask() { override fun run() {} } val ttlTimerTask = TtlTimerTask.get(timerTask) TtlTimerTask.unwrap(timerTask) shouldBeSameInstanceAs timerTask TtlTimerTask.unwrap(ttlTimerTask) shouldBeSameInstanceAs timerTask } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/user_api_test/ttl3/DisableIgnoreNullValueSemanticsTest.kt ================================================ package com.alibaba.user_api_test.ttl3 import com.alibaba.getForTest import com.alibaba.ttl3.TransmittableThreadLocal import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import java.util.concurrent.FutureTask import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread /** * Test the "Ignore-Null-Value Semantics" of [TransmittableThreadLocal] from user code(different package) */ class DisableIgnoreNullValueSemanticsTest : AnnotationSpec() { @Test fun test_TTL_not_disableIgnoreNullValueSemantics_defaultTtlBehavior() { val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String { return "init" } override fun childValue(parentValue: String?): String { return "$parentValue + child" } } ttl.get() shouldBe "init" ttl.set(null) // DO NOT `ttl.get()` ! // `get` operation will re-init the value of ThreadLocal val task = FutureTask { ttl.get() } thread { task.run() }.join() // `get` operation will re-init the value of ThreadLocal ! ttl.get() shouldBe "init" // "Ignore-Null-Value Semantics" will not transmit ThreadLocal with the null value, // so the value in new thread is "init" value ttl.get() shouldBe "init" ////////////////////////////////////// val task2 = FutureTask { ttl.get() } thread { task2.run() }.join() ttl.get() shouldBe "init" task2.getForTest() shouldBe "init + child" } @Test fun test_TTL_not_disableIgnoreNullValueSemantics_defaultTtlBehavior_getSafe_ForNullInit() { val count = AtomicInteger() val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String? { count.getAndIncrement() return super.initialValue() } override fun childValue(parentValue: String?): String? { count.getAndSet(1000) return super.childValue(parentValue) } } ttl.get().shouldBeNull() count.get() shouldBe 1 ttl.set(null) ttl.get().shouldBeNull() count.get() shouldBe 2 } @Test fun test_TTL_disableIgnoreNullValueSemantics_sameAsThreadLocal() { val ttl = object : TransmittableThreadLocal(true) { override fun initialValue(): String { return "init" } override fun childValue(parentValue: String?): String { return "$parentValue + child" } } ttl.get() shouldBe "init" ttl.set(null) ttl.get().shouldBeNull() val task = FutureTask { ttl.get() } thread { task.run() }.join() ttl.get().shouldBeNull() task.getForTest() shouldBe "null + child" ////////////////////////////////////// val task2 = FutureTask { ttl.get() } thread { task2.run() }.join() ttl.get().shouldBeNull() task.getForTest() shouldBe "null + child" } @Test fun test_InheritableThreadLocal() { val ttl = object : InheritableThreadLocal() { override fun initialValue(): String { return "init" } override fun childValue(parentValue: String?): String { return "$parentValue + child" } } ttl.get() shouldBe "init" ttl.set(null) ttl.get().shouldBeNull() val task = FutureTask { ttl.get() } thread { task.run() }.join() ttl.get().shouldBeNull() task.getForTest() shouldBe "null + child" ////////////////////////////////////// val task2 = FutureTask { ttl.get() } thread { task2.run() }.join() ttl.get().shouldBeNull() task.getForTest() shouldBe "null + child" } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/user_api_test/ttl3/TransmittableThreadLocal_Transmitter_UserTest.kt ================================================ package com.alibaba.user_api_test.ttl3 import com.alibaba.expandThreadPool import com.alibaba.getForTest import com.alibaba.shutdownForTest import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.transmitter.Transmitter import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors /** * Test [Transmitter] from user code(different package) */ class TransmittableThreadLocal_Transmitter_UserTest : AnnotationSpec() { @Test fun test_crr() { val ttl = TransmittableThreadLocal() ttl.set(parentValue) val capture = Transmitter.capture() val future = executorService.submit { ttl.set(childValue) val backup = Transmitter.replay(capture) ttl.get() shouldBe parentValue Transmitter.restore(backup) ttl.get() shouldBe childValue } ttl.get() shouldBe parentValue future.getForTest() ttl.get() shouldBe parentValue } @Test fun test_clear_restore() { val ttl = TransmittableThreadLocal() ttl.set(parentValue) val future = executorService.submit { ttl.set(childValue) val backup = Transmitter.clear() ttl.get().shouldBeNull() Transmitter.restore(backup) ttl.get() shouldBe childValue } ttl.get() shouldBe parentValue future.getForTest() ttl.get() shouldBe parentValue } @Test fun test_runSupplierWithCaptured() { val ttl = TransmittableThreadLocal() ttl.set(parentValue) val capture = Transmitter.capture() val future = executorService.submit { ttl.set("child") Transmitter.runSupplierWithCaptured(capture) { ttl.get() shouldBe parentValue ttl.get() } } ttl.get() shouldBe parentValue future.getForTest() ttl.get() shouldBe parentValue } @Test fun test_runSupplierWithClear() { val ttl = TransmittableThreadLocal() ttl.set(parentValue) val future = executorService.submit { ttl.set("child") Transmitter.runSupplierWithClear { ttl.get().shouldBeNull() ttl.get() } } ttl.get() shouldBe parentValue future.getForTest() ttl.get() shouldBe parentValue } @Test fun test_runCallableWithCaptured() { val ttl = TransmittableThreadLocal() ttl.set(parentValue) val capture = Transmitter.capture() val future = executorService.submit { ttl.set("child") try { Transmitter.runCallableWithCaptured(capture) { ttl.get() shouldBe parentValue ttl.get() } } catch (e: Exception) { throw RuntimeException(e) } } ttl.get() shouldBe parentValue future.getForTest() ttl.get() shouldBe parentValue } @Test fun test_runCallableWithClear() { val ttl = TransmittableThreadLocal() ttl.set(parentValue) val future = executorService.submit { ttl.set("child") try { Transmitter.runCallableWithClear { ttl.get().shouldBeNull() ttl.get() } } catch (e: Exception) { throw RuntimeException(e) } } ttl.get() shouldBe parentValue future.getForTest() ttl.get() shouldBe parentValue } private val parentValue = "parent: " + Date() private val childValue = "child: " + Date() private lateinit var executorService: ExecutorService @BeforeAll fun beforeAll() { executorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } @AfterAll fun afterAll() { executorService.shutdownForTest() } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/user_api_test/ttl3/TransmittableThreadLocal_Transmitter_registerTransmittee_UserTest.kt ================================================ package com.alibaba.user_api_test.ttl3 import com.alibaba.ttl3.transmitter.Transmittee import com.alibaba.ttl3.transmitter.TransmitteeRegistry import com.alibaba.ttl3.transmitter.Transmitter import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.booleans.shouldBeTrue import io.mockk.* /** * Test [Transmitter] from user code(different package) */ class TransmittableThreadLocal_Transmitter_registerTransmittee_UserTest : AnnotationSpec() { @Test fun test_registerTransmittee_crr() { // ======================================== // 0. mocks creation and stubbing // ======================================== val transmittee = mockk, Set>>() @Suppress("UnusedEquals", "ReplaceCallWithBinaryOperator") excludeRecords { transmittee.equals(any()) transmittee.hashCode() } every { transmittee.capture() } returns listOf("42", "43") every { transmittee.replay(listOf("42", "43")) } returns setOf(42, 43) every { transmittee.restore(setOf(42, 43)) } just Runs try { // ======================================== // 1. mock record(aka. invocation) // ======================================== TransmitteeRegistry.registerTransmittee(transmittee).shouldBeTrue() val captured = Transmitter.capture() val backup = Transmitter.replay(captured) Transmitter.restore(backup) // ======================================== // 2. mock verification // ======================================== verifySequence { transmittee.capture() transmittee.replay(any()) transmittee.restore(any()) } confirmVerified(transmittee) } finally { TransmitteeRegistry.unregisterTransmittee(transmittee).shouldBeTrue() } } @Test fun test_registerTransmittee_clear_restore() { // ======================================== // 0. mocks creation and stubbing // ======================================== val transmittee = mockk, Set>>() @Suppress("UnusedEquals", "ReplaceCallWithBinaryOperator") excludeRecords { transmittee.equals(any()) transmittee.hashCode() } every { transmittee.clear() } returns setOf(42, 43) every { transmittee.restore(setOf(42, 43)) } just Runs try { // ======================================== // 1. mock record(aka. invocation) // ======================================== TransmitteeRegistry.registerTransmittee(transmittee).shouldBeTrue() val backup = Transmitter.clear() Transmitter.restore(backup) // ======================================== // 2. mock verification // ======================================== verifySequence { transmittee.clear() transmittee.restore(any()) } confirmVerified(transmittee) } finally { TransmitteeRegistry.unregisterTransmittee(transmittee).shouldBeTrue() } } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/user_api_test/ttl3/TransmittableThreadLocal_withInit_Null_Test.java ================================================ package com.alibaba.user_api_test.ttl3; import com.alibaba.ttl3.TransmittableThreadLocal; import org.junit.Test; import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class TransmittableThreadLocal_withInit_Null_Test { @Test public void test_null__withInitial() { try { TransmittableThreadLocal.withInitial(null); fail(); } catch (NullPointerException e) { assertEquals("supplier is null", e.getMessage()); } } @Test public void test_null__withInitialAndGenerator_2() { try { TransmittableThreadLocal.withInitialAndGenerator(null, null); fail(); } catch (NullPointerException e) { assertEquals("supplier is null", e.getMessage()); } try { TransmittableThreadLocal.withInitialAndGenerator((Supplier) () -> null, null); fail(); } catch (NullPointerException e) { assertEquals("value generator is null", e.getMessage()); } } @Test public void test_null__withInitialAndGenerator_3() { try { TransmittableThreadLocal.withInitialAndGenerator(null, null, null); fail(); } catch (NullPointerException e) { assertEquals("supplier is null", e.getMessage()); } try { TransmittableThreadLocal.withInitialAndGenerator((Supplier) () -> null, null, null); fail(); } catch (NullPointerException e) { assertEquals("value generator for child value is null", e.getMessage()); } try { TransmittableThreadLocal.withInitialAndGenerator((Supplier) () -> null, parentValue -> null, null); fail(); } catch (NullPointerException e) { assertEquals("value generator for transmittee value is null", e.getMessage()); } } } ================================================ FILE: ttl-core/src/test/java/com/alibaba/user_api_test/ttl3/TransmittableThreadLocal_withInit_Test.kt ================================================ package com.alibaba.user_api_test.ttl3 import com.alibaba.expandThreadPool import com.alibaba.getForTest import com.alibaba.shutdownForTest import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.executor.TtlExecutors import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread class TransmittableThreadLocal_withInit_Test : AnnotationSpec() { @Test fun test_withInit() { val ttl: TransmittableThreadLocal = TransmittableThreadLocal.withInitial { 42 } ttl.shouldNotBeNull() ttl.get() shouldBe 42 val atomicInteger = AtomicInteger(-1) thread { atomicInteger.set(ttl.get()) }.join() atomicInteger.get() shouldBe 42 atomicInteger.set(-1) executorService.submit { atomicInteger.set(ttl.get()) }.getForTest() atomicInteger.get() shouldBe 42 } @Test fun test_withInitialAndGenerator_2() { val ttl = TransmittableThreadLocal.withInitialAndGenerator( { 42 }, { it + 100 }, ) ttl.shouldNotBeNull() ttl.get() shouldBe 42 val atomicInteger = AtomicInteger(-1) thread { atomicInteger.set(ttl.get()) }.join() atomicInteger.get() shouldBe 142 atomicInteger.set(-1) executorService.submit { atomicInteger.set(ttl.get()) }.getForTest() atomicInteger.get() shouldBe 142 } @Test fun test_withInitialAndGenerator_3() { val ttl = TransmittableThreadLocal.withInitialAndGenerator( { 42 }, { it + 100 }, { it + 1000 }, ) ttl.shouldNotBeNull() ttl.get() shouldBe 42 val atomicInteger = AtomicInteger(-1) thread { atomicInteger.set(ttl.get()) }.join() atomicInteger.get() shouldBe 142 atomicInteger.set(-1) executorService.submit { atomicInteger.set(ttl.get()) }.getForTest() atomicInteger.get() shouldBe 1042 } private lateinit var executorService: ExecutorService @BeforeAll fun beforeAll() { executorService = Executors.newFixedThreadPool(3).let { expandThreadPool(it) TtlExecutors.getTtlExecutorService(it)!! } } @AfterAll fun afterAll() { executorService.shutdownForTest() } } ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/README.md ================================================ # `TTL Agent`扩展`Transformlet`实现的示例工程 ## 扩展`Transformlet`的实现 为了提供`TTL Agent`扩展`Transformlet`,包含2部分: 1. `TTL Agent`扩展`Transformlet`的实现类:[`SampleExtensionTransformlet`](src/main/java/com/alibaba/ttl/agent/extension_transformlet/sample/transformlet/SampleExtensionTransformlet.java)。 - 这个示例`Transformlet`修改了类[`ToBeTransformedClass`](src/main/java/com/alibaba/ttl/agent/extension_transformlet/sample/biz/ToBeTransformedClass.java)的`toBeTransformedMethod`方法:在修改方法前插入一行代码,修改方法参数值乘以2(`$1 *= 2;`)。 1. `TTL Agent`扩展`Transformlet`的配置文件:[`META-INF/ttl.agent.transformlets`](src/main/resources/META-INF/ttl.agent.transformlets) - 配置文件的内容是 扩展`Transformlet`实现类的全类名。 在这个示例工程是`com.alibaba.ttl.agent.extension_transformlet.sample.transformlet.SampleExtensionTransformlet`。 - `TTL Agent`会扫描`Class Path`上的`META-INF/ttl.agent.transformlets`文件,自动发现并启用这些扩展`Transformlet`。 即只要将扩展`Transformlet`的依赖`Jar`引入到应用中就会自动生效。 - 这个扫描并自动加载生效与`JDK`的[`ServiceLoader`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html)一样,只是使用不同的扩展配置文件。 ## 扩展`Transformlet`实现的注意点 在`Transformlet`的实现逻辑中,**_不要_** 加载被`transform`的类 或是 做实例化,如 - `ToBeTransformedClass.class` - `Class.forName("com.alibaba.ttl.agent.extension_transformlet.sample.biz.ToBeTransformedClass")` - `new ToBeTransformedClass()` 加载被`transform`的类会导致对该类的`transform`操作被跳过,也就是`Transformlet`失效了。 ## 扩展`Transformlet`的测试与生效验证 单元测试类 在 [`ToBeTransformedClassTest`](src/test/java/com/alibaba/ttl/agent/extension_transformlet/sample/biz/ToBeTransformedClassTest.java)。 通过运行`Maven`单元测试验证扩展`Transformlet` `SampleExtensionTransformlet`是否生效: ```bash # sample-ttl-agent-extension-transformlet 工程目录,执行 # 1. 先 mvn install TTL lib (cd ../.. && mvn install -Dmaven.test.skip) # 2. 验证 扩展Transformlet SampleExtensionTransformlet 是否生效 mvn test -Penable-TtlAgent-forTest # 更多输出TTL的Transform类操作的日志 mvn test -Penable-TtlAgent-forTest -Penable-LogTransform-forTest ``` ## 运行示例`SampleMain` 可以通过`Java`命令行参数来运行示例`SampleMain`: ```java java -javaagent:path/to/transmittable-thread-local-2.x.y.jar \ -cp target/classes \ com.alibaba.ttl.agent.extension_transformlet.sample.biz.SampleMain ``` 通过脚本[`scripts/run.sh`](scripts/run.sh)快速上面命令行的运行。 ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 sample-ttl-agent-extension-transformlet 1.0.0-SNAPSHOT jar ${project.artifactId} 1.8 ${maven.compiler.source} UTF-8 true true true 3.x-SNAPSHOT com.alibaba transmittable-thread-local ${ttl.version} junit junit 4.13.2 test maven-surefire-plugin 3.5.2 maven-source-plugin 3.3.1 maven-javadoc-plugin 3.8.0 maven-deploy-plugin 3.1.3 enable-TtlAgent-forTest org.apache.maven.plugins maven-surefire-plugin ${surefire.verbose.class} -javaagent:${com.alibaba:transmittable-thread-local:jar}=ttl.agent.logger:STDOUT ${surefire.ttl.agent.log.class.transform} org.apache.maven.plugins maven-dependency-plugin 3.8.1 initialize properties enable-LogTransform-forTest -Dttl.agent.log.class.transform enable-verboseClass-forTest -verbose:class gen-code-cov env.CI true org.jacoco jacoco-maven-plugin 0.8.12 prepare-agent report test report ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/scripts/integration-test.sh ================================================ #!/bin/bash set -eEuo pipefail # adjust current dir to project dir cd "$(dirname "$(readlink -f "$0")")/.." TTL_ROOT_PROJECT_DIR="$(dirname "$(readlink -f "../")")" source "$TTL_ROOT_PROJECT_DIR/scripts/common_build.sh" source "$TTL_ROOT_PROJECT_DIR/scripts/prepare-jdk.sh" for jv in 8 11; do switch_to_jdk "$jv" headInfo "test with JDK $JAVA_HOME" MVN_WITH_BASIC_OPTIONS test MVN_WITH_BASIC_OPTIONS test -Penable-TtlAgent-forTest done ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/scripts/run.sh ================================================ #!/bin/bash set -eEuo pipefail # adjust current dir to project dir cd "$(dirname "$(readlink -f "$0")")/.." TTL_ROOT_PROJECT_DIR="$(dirname "$(readlink -f "../")")" source "$TTL_ROOT_PROJECT_DIR/scripts/common_build.sh" ttl_version=$(extractFirstElementValueFromPom version "../../pom.xml") readonly ttl_agent_path="$TTL_ROOT_PROJECT_DIR/target/transmittable-thread-local-$ttl_version.jar" mvn_ttl_lib() { ( cd "$TTL_ROOT_PROJECT_DIR" MVN_WITH_BASIC_OPTIONS -q -Dmaven.test.skip "$@" ) } if [ "${1:-}" != "skipClean" ]; then mvn_ttl_lib clean package # compile sample-ttl-agent-extension-transformlet MVN_WITH_BASIC_OPTIONS -q clean compile else if [ ! -f "$ttl_agent_path" ]; then mvn_ttl_lib package fi # compile sample-ttl-agent-extension-transformlet MVN_WITH_BASIC_OPTIONS -q compile fi readonly ttl_agent_options="-javaagent:$ttl_agent_path=ttl.agent.logger:STDOUT,ttl.agent.log.class.transform:true" readonly main_class=com.alibaba.ttl.agent.extension_transformlet.sample.biz.SampleMain logAndRun "$JAVA_HOME/bin/java" -Duser.language=en -Duser.country=US \ "${ttl_agent_options}" \ -cp target/classes $main_class ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/src/main/java/com/alibaba/ttl/agent/extension_transformlet/sample/biz/SampleMain.java ================================================ package com.alibaba.ttl.agent.extension_transformlet.sample.biz; import com.alibaba.ttl.threadpool.agent.TtlAgent; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; public class SampleMain { /** * @see ToBeTransformedClass#toBeTransformedMethod(int) * @see com.alibaba.ttl.agent.extension_transformlet.sample.transformlet.SampleExtensionTransformlet#doTransform(ClassInfo) */ public static void main(String[] args) throws Exception { final ToBeTransformedClass instance = new ToBeTransformedClass(); System.out.println("========================================"); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Run WITH TTL Agent"); } else { System.out.println("Run Without TTL Agent"); } System.out.println(instance.toBeTransformedMethod(21)); System.out.println("========================================"); } } ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/src/main/java/com/alibaba/ttl/agent/extension_transformlet/sample/biz/ToBeTransformedClass.java ================================================ package com.alibaba.ttl.agent.extension_transformlet.sample.biz; public class ToBeTransformedClass { public int toBeTransformedMethod(int input) { return input; } } ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/src/main/java/com/alibaba/ttl/agent/extension_transformlet/sample/transformlet/SampleExtensionTransformlet.java ================================================ package com.alibaba.ttl.agent.extension_transformlet.sample.transformlet; import com.alibaba.ttl.agent.extension_transformlet.sample.biz.ToBeTransformedClass; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CannotCompileException; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CtClass; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CtMethod; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.NotFoundException; import java.io.IOException; /** * {@link TtlTransformlet} for {@link ToBeTransformedClass}. * * Caution:
* MUST use string constant for class/method name! *

* MUST NOT use {@code Class class = ToBeTransformedClass.class} to get the class to be transformed({@code ToBeTransformedClass}), * {@code ToBeTransformedClass.class} operation will force to load the class to be transformed, * and cause the Transformlet to SKIP the class transform! */ public class SampleExtensionTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(SampleExtensionTransformlet.class); public static final String TO_BE_TRANSFORMED_CLASS_NAME = "com.alibaba.ttl.agent.extension_transformlet.sample.biz.ToBeTransformedClass"; public static final String TO_BE_TRANSFORMED_METHOD = "toBeTransformedMethod"; public void doTransform(ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { if (!classInfo.getClassName().equals(TO_BE_TRANSFORMED_CLASS_NAME)) return; final CtClass ctClass = classInfo.getCtClass(); final CtMethod method = ctClass.getDeclaredMethod(TO_BE_TRANSFORMED_METHOD); final String code = "$1 *= 2;"; method.insertBefore(code); logger.info("[SampleExtensionTransformlet] insert code before method " + TO_BE_TRANSFORMED_METHOD + " of class " + method.getDeclaringClass().getName() + ": " + code); classInfo.setModified(); } } ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/src/main/resources/META-INF/ttl.agent.transformlets ================================================ com.alibaba.ttl.agent.extension_transformlet.sample.transformlet.SampleExtensionTransformlet ================================================ FILE: ttl-integrations/sample-ttl-agent-extension-transformlet/src/test/java/com/alibaba/ttl/agent/extension_transformlet/sample/biz/ToBeTransformedClassTest.java ================================================ package com.alibaba.ttl.agent.extension_transformlet.sample.biz; import com.alibaba.ttl.threadpool.agent.TtlAgent; import org.junit.Test; import java.util.Properties; import static org.junit.Assert.assertEquals; public class ToBeTransformedClassTest { @Test public void test_method1() { final ToBeTransformedClass instance = new ToBeTransformedClass(); System.out.println("========================================"); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Test **WITH** TTL Agent"); assertEquals(42, instance.toBeTransformedMethod(21)); } else { System.out.println("Test WITHOUT TTL Agent"); assertEquals(21, instance.toBeTransformedMethod(21)); } System.out.println("========================================"); } } ================================================ FILE: ttl-integrations/vertx3-ttl-integration/README-EN.md ================================================ # Vertx 4 integration of TTL ## 1. assure TTL context transmit in callback ## 1.1 Decorate `io.vertx.core.Handler` Use [`TtlVertxHandler`](src/main/java/com/alibaba/ttl/integration/vertx3/TtlVertxHandler.java) to decorate `Handler`。 ## 1.2 Decorate `io.vertx.core.Future` At present, `TTL` agent has decorated below `Vertx` callback components(`io.vertx.core.Future`) implementation: - `io.vertx.core.Future` - `io.vertx.core.impl.future.FutureImpl` - `io.vertx.core.http.impl.HttpClientImpl` - decoration implementation code is in [`VertxFutureTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx3/agent/transformlet/VertxFutureTtlTransformlet.java)。 Sample code: ```java Vertx vertx = Vertx.vertx(); //build channel ManagedChannel channel = VertxChannelBuilder .forAddress(vertx, "localhost", 8080) .usePlaintext() .build(); // set in parent thread TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); //init stub io.grpc.stub.XXX stub = XXX.newVertxStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("Julien").build(); //init handler Handler> handler = event -> { // read in callback, value is "value-set-in-parent" context.get(); if (event.succeeded()) { //do something } else { // find exception } }; // extra work, create decorated TtlVertxHandler object TtlVertxHandler> ttlVertxHandler = TtlVertxHandler.get(handler); //send request stub.sayHello(request).onComplete(ttlVertxHandler); ``` ## 2. assure TTL context transmit in eventbus ### 2. decorate`java.lang.Runnable` Use [`TtlRunnable`](../../src/main/java/com/alibaba/ttl/TtlRunnable.java) to decorate`Runnable`。 ### 2.2 Decorate`io.netty.util.concurrent.SingleThreadEventExecutor` - decoration implementation code is in[`NettySingleThreadEventExecutorTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx3/agent/transformlet/NettySingleThreadEventExecutorTtlTransformlet.java)。 ================================================ FILE: ttl-integrations/vertx3-ttl-integration/README.md ================================================ # Vertx 4的TTL集成 ## 1. 保证异步io回调中传递TTL值 ### 1.1修饰`io.vertx.core.Handler` 使用[`TtlVertxHandler`](src/main/java/com/alibaba/ttl/integration/vertx3/TtlVertxHandler.java)来修饰传入的`Handler`。 ### 1.2 修饰`io.vertx.core.Future` 修饰了的Vert.x执行器组件如下: - `io.vertx.core.Future` - `io.vertx.core.impl.future.FutureImpl` - `io.vertx.core.http.impl.HttpClientImpl` --- - 修饰实现代码在[`VertxFutureTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx3/agent/transformlet/VertxFutureTtlTransformlet.java)。 示例代码: ```java Vertx vertx = Vertx.vertx(); //build channel ManagedChannel channel = VertxChannelBuilder .forAddress(vertx, "localhost", 8080) .usePlaintext() .build(); // set in parent thread TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); //init stub io.grpc.stub.XXX stub = XXX.newVertxStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("Julien").build(); //init handler Handler> handler = event -> { // read in callback, value is "value-set-in-parent" context.get(); if (event.succeeded()) { //do something } else { // find exception } }; // extra work, create decorated TtlVertxHandler object TtlVertxHandler> ttlVertxHandler = TtlVertxHandler.get(handler); //send request stub.sayHello(request).onComplete(ttlVertxHandler); ``` ## 2. 保证`eventbus`上传递TTL值 ### 2.1修饰`java.lang.Runnable` 使用[`TtlRunnable`](../../src/main/java/com/alibaba/ttl/TtlRunnable.java)来修饰传入的`Runnable`。 ### 2.2 修饰`io.netty.util.concurrent.SingleThreadEventExecutor` 修饰实现代码在[`NettySingleThreadEventExecutorTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx3/agent/transformlet/NettySingleThreadEventExecutorTtlTransformlet.java)。 ================================================ FILE: ttl-integrations/vertx3-ttl-integration/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT ../../pom.xml vertx3-ttl-integration 0.1.0-SNAPSHOT jar ${project.artifactId} TTL integration for vert.x 3 https://github.com/alibaba/transmittable-thread-local/tree/master/ttl-integrations/vertx3-ttl-integration 2021 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions true true ${ttl.integration.test.skip} io.vertx vertx-core provided io.netty netty-all provided com.alibaba transmittable-thread-local ${project.parent.version} provided io.netty netty-bom 4.1.84.Final pom import io.vertx vertx-stack-depchain 3.9.13 pom import enable-TtlAgent-forTest org.apache.maven.plugins maven-surefire-plugin ${surefire.verbose.class} -javaagent:${com.alibaba:transmittable-thread-local:jar}=ttl.agent.logger:STDOUT ${surefire.ttl.agent.log.class.transform} org.apache.maven.plugins maven-dependency-plugin 3.8.1 initialize properties enable-LogTransform-forTest -Dttl.agent.log.class.transform enable-verboseClass-forTest -verbose:class ================================================ FILE: ttl-integrations/vertx3-ttl-integration/src/main/java/com/alibaba/ttl/integration/vertx3/TtlVertxHandler.java ================================================ package com.alibaba.ttl.integration.vertx3; import com.alibaba.ttl.spi.TtlAttachments; import com.alibaba.ttl.spi.TtlAttachmentsDelegate; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.vertx.core.Handler; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * {@link TtlVertxHandler} decorate {@link Handler}, so as to get {@link com.alibaba.ttl.TransmittableThreadLocal} * and transmit it to the time of {@link Handler} execution, * needed when use {@link Handler} to {@link io.vertx.core.Future}. *

* we will capture ttl value in another thread by modify {@link io.netty.util.concurrent.SingleThreadEventExecutor#execute(Runnable)}, * but we can not capture the ttl value which we expect in callback of identical thread. * the reason of above issue is some async io callback was invoked by the * {@link io.netty.channel.nio.NioEventLoop#run()} rather than the {@link com.alibaba.ttl.TtlRunnable#run()} * * @author tk (305809299 at qq dot com) * @see io.vertx.core.Future * @see io.netty.channel.nio.NioEventLoop#run() * @see io.netty.channel.nio.NioEventLoop#processSelectedKeys() */ public class TtlVertxHandler implements Handler, TtlWrapper>, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Handler handler; private final boolean releaseTtlValueReferenceAfterRun; private TtlVertxHandler(@NonNull Handler handler, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<>(capture()); this.handler = handler; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link Handler#handle(Object)}. */ @Override public void handle(E event) { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Object backup = replay(captured); try { handler.handle(event); } finally { restore(backup); } } /** * return original/unwrapped {@link Handler}. */ @NonNull public Handler getHandler() { return unwrap(); } /** * unwrap to original/unwrapped {@link Handler}. * * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) */ @NonNull @Override public Handler unwrap() { return handler; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @SuppressWarnings("unchecked") TtlVertxHandler that = (TtlVertxHandler) o; return handler.equals(that.handler); } @Override public int hashCode() { return handler.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + handler.toString(); } /** * Factory method, wrap input {@link Handler} to {@link TtlVertxHandler}. * * @param handler input {@link Handler}. if input is {@code null}, return {@code null}. * @return Wrapped {@link Handler} * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @Nullable public static TtlVertxHandler get(@Nullable Handler handler) { return get(handler, false, false); } /** * Factory method, wrap input {@link Handler} to {@link TtlVertxHandler}. * * @param handler input {@link Handler}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @return Wrapped {@link Handler} * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @Nullable public static TtlVertxHandler get(@Nullable Handler handler, boolean releaseTtlValueReferenceAfterRun) { return get(handler, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link Handler} to {@link TtlVertxHandler}. * * @param handler input {@link Handler}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Handler} when it's {@link TtlVertxHandler}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Handler} * @throws IllegalStateException when input is {@link TtlVertxHandler} already and not idempotent. */ @Nullable public static TtlVertxHandler get(@Nullable Handler handler, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (handler == null) { return null; } if (handler instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) { return (TtlVertxHandler) handler; } else { throw new IllegalStateException("Already TtlVertxHandler!"); } } return new TtlVertxHandler<>(handler, releaseTtlValueReferenceAfterRun); } /** * wrap input {@link Handler} Collection to {@link TtlVertxHandler} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @NonNull public static List> gets(@Nullable Collection> tasks) { return gets(tasks, false, false); } /** * wrap input {@link Handler} Collection to {@link TtlVertxHandler} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterRun) { return gets(tasks, releaseTtlValueReferenceAfterRun, false); } /** * wrap input {@link Handler} Collection to {@link TtlVertxHandler} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Handler} when it's {@link TtlVertxHandler}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlVertxHandler} already and not idempotent. */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (tasks == null) { return Collections.emptyList(); } List> copy = new ArrayList<>(); for (Handler task : tasks) { copy.add(TtlVertxHandler.get(task, releaseTtlValueReferenceAfterRun, idempotent)); } return copy; } /** * Unwrap {@link TtlVertxHandler} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code Function} parameter is {@code null}, return {@code null}; * if input {@code Function} parameter is not a {@link TtlVertxHandler} just return input {@code Function}. *

* so {@code TtlVertxHandler.unwrap(TtlVertxHandler.get(function))} will always return the same input {@code function} object. * * @see #handle(Object) * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) */ @Nullable public static Handler unwrap(@Nullable Handler handler) { if (!(handler instanceof TtlVertxHandler)) { return handler; } else { return ((TtlVertxHandler) handler).getHandler(); } } /** * Unwrap {@link TtlVertxHandler} to the original/underneath one for collection. *

* Invoke {@link #unwrap(Handler)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code Handler} parameter collection is {@code null}, return a empty list. * * @see #gets(Collection) * @see #unwrap(Handler) */ @NonNull public static List> unwraps(@Nullable Collection> tasks) { if (tasks == null) { return Collections.emptyList(); } List> copy = new ArrayList<>(); for (Handler task : tasks) { if (!(task instanceof TtlVertxHandler)) { copy.add(task); } else { copy.add(((TtlVertxHandler) task).getHandler()); } } return copy; } private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); /** * see {@link TtlAttachments#setTtlAttachment(String, Object)} */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } } ================================================ FILE: ttl-integrations/vertx3-ttl-integration/src/main/java/com/alibaba/ttl/integration/vertx3/agent/transformlet/NettySingleThreadEventExecutorTtlTransformlet.java ================================================ package com.alibaba.ttl.integration.vertx3.agent.transformlet; import com.alibaba.ttl.threadpool.agent.transformlet.helper.AbstractExecutorTtlTransformlet; import java.util.HashSet; import java.util.Set; /** * {@link com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet} * for {@link io.netty.util.concurrent.SingleThreadEventExecutor}. * * @author tk (305809299 at qq dot com) * @see io.netty.util.concurrent.SingleThreadEventExecutor * @see io.vertx.core.eventbus.EventBus * @see io.vertx.core.impl.EventLoopContext * @see io.vertx.core.eventbus.Message */ public final class NettySingleThreadEventExecutorTtlTransformlet extends AbstractExecutorTtlTransformlet { private static Set getExecutorClassNames() { Set executorClassNames = new HashSet<>(); executorClassNames.add("io.netty.util.concurrent.SingleThreadEventExecutor"); return executorClassNames; } public NettySingleThreadEventExecutorTtlTransformlet() { super(getExecutorClassNames(), false); } } ================================================ FILE: ttl-integrations/vertx3-ttl-integration/src/main/java/com/alibaba/ttl/integration/vertx3/agent/transformlet/VertxFutureTtlTransformlet.java ================================================ package com.alibaba.ttl.integration.vertx3.agent.transformlet; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CannotCompileException; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CtClass; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CtMethod; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.NotFoundException; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; /** * {@link TtlTransformlet} for {@link io.vertx.core.Future}. * * @author tk (305809299 at qq dot com) * @see com.alibaba.ttl.integration.vertx3.TtlVertxHandler * @see io.vertx.core.Future * @see io.vertx.core.Handler */ public class VertxFutureTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(VertxFutureTtlTransformlet.class); private static final String HANDLER_CLASS_NAME = "io.vertx.core.Handler"; private static final String TTL_HANDLER_CLASS_NAME = "com.alibaba.ttl.integration.vertx3.TtlVertxHandler"; private static final String FUTURE_CLASS_NAME = "io.vertx.core.Future"; private static final String HTTP_CLIENT_CLASS_NAME = "io.vertx.core.http.impl.HttpClientImpl"; private static final String DNS_CLIENT_CLASS_NAME = "io.vertx.core.dns.impl.DnsClientImpl"; private static final String VERTX_IMPL_CLASS_NAME = "io.vertx.core.impl.VertxImpl"; private static final String FUTURE_IMPL_CLASS_NAME = "io.vertx.core.impl.future.FutureImpl"; private static final Set TO_BE_TRANSFORMED_CLASS_NAMES = new HashSet<>(); static { TO_BE_TRANSFORMED_CLASS_NAMES.add(FUTURE_CLASS_NAME); TO_BE_TRANSFORMED_CLASS_NAMES.add(FUTURE_IMPL_CLASS_NAME); TO_BE_TRANSFORMED_CLASS_NAMES.add(HTTP_CLIENT_CLASS_NAME); TO_BE_TRANSFORMED_CLASS_NAMES.add(VERTX_IMPL_CLASS_NAME); } @Override public void doTransform(@NonNull ClassInfo classInfo) throws CannotCompileException, NotFoundException, IOException { final CtClass clazz = classInfo.getCtClass(); if (TO_BE_TRANSFORMED_CLASS_NAMES.contains(classInfo.getClassName())) { for (CtMethod method : clazz.getDeclaredMethods()) { updateSetHandlerMethodsOfFutureClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); } classInfo.setModified(); } } private void updateSetHandlerMethodsOfFutureClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(CtMethod method) throws NotFoundException, CannotCompileException { final int modifiers = method.getModifiers(); if (!checkMethodNeedToBeDecorated(modifiers)) { return; } CtClass[] parameterTypes = method.getParameterTypes(); StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (HANDLER_CLASS_NAME.equals(paramTypeName)) { String code = String.format( // decorate to TTL wrapper, // and then set AutoWrapper attachment/Tag "$%d = %s.get($%1$d, false, true);" + "%n com.alibaba.ttl.spi.TtlAttachmentsDelegate.setAutoWrapperAttachment($%1$d);", i + 1, TTL_HANDLER_CLASS_NAME); logger.info("insert code before method " + signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ":\n" + code); insertCode.append(code); } } if (insertCode.length() > 0) method.insertBefore(insertCode.toString()); } private boolean checkMethodNeedToBeDecorated(int modifiers) { return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && !Modifier.isAbstract(modifiers); } } ================================================ FILE: ttl-integrations/vertx3-ttl-integration/src/main/resources/META-INF/ttl.agent.transformlets ================================================ com.alibaba.ttl.integration.vertx3.agent.transformlet.NettySingleThreadEventExecutorTtlTransformlet com.alibaba.ttl.integration.vertx3.agent.transformlet.VertxFutureTtlTransformlet ================================================ FILE: ttl-integrations/vertx3-ttl-integration/src/test/java/com/alibaba/ttl/integration/vertx3/VertxTransformletTest.java ================================================ package com.alibaba.ttl.integration.vertx3; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.threadpool.agent.TtlAgent; import io.vertx.core.Vertx; import io.vertx.core.eventbus.DeliveryOptions; import org.junit.Test; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * @author tk (soulmate.tangk at gmail dot com) */ public class VertxTransformletTest { @Test public void testTransmitThreadLocal_InEventbus() throws Exception { final TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>(); final InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>(); final String transmittedData = "transmitted_data"; final String inheritedData = "inherited_data_ttl"; final String message = "message_42"; final Vertx vertx = Vertx.vertx(); final String address = "consumer"; vertx.eventBus().consumer(address, msg -> { // be executed in netty event loop thread System.out.println("========================================"); assertEquals(message, msg.body()); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Test **WITH** TTL Agent"); assertEquals(transmittedData, transmittableThreadLocal.get()); } else { System.out.println("Test WITHOUT TTL Agent"); assertNull(transmittableThreadLocal.get()); } // InheritableThreadLocal is always null assertNull(inheritableThreadLocal.get()); System.out.println("========================================"); msg.reply(message); }); transmittableThreadLocal.set(transmittedData); inheritableThreadLocal.set(inheritedData); // delivery message final DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setSendTimeout(1000); vertx.eventBus().send(address, message, deliveryOptions, event -> { System.out.println("receive reply message:" + event.result().body()); }); } @Test public void testCallbackInHttpClient() throws Exception { final TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>(); final InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>(); final String transmittedData = "transmitted_data_ttl"; final String inheritedData = "inherited_data_ttl"; final Vertx vertx = Vertx.vertx(); //set value after eventLoop thread was created transmittableThreadLocal.set(transmittedData); inheritableThreadLocal.set(inheritedData); vertx.createHttpClient().get(80, "bing.com", "/", event -> { System.out.println("receive msg:" + event.statusCode()); System.out.println("===================callback====================="); event.bodyHandler(body -> { System.out.println("receive response body: " + body.toString(UTF_8)); }); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Test **WITH** TTL Agent"); assertEquals(transmittedData, transmittableThreadLocal.get()); assertNull(inheritableThreadLocal.get()); } else { System.out.println("Test WITHOUT TTL Agent"); assertNull(transmittableThreadLocal.get()); assertNull(inheritableThreadLocal.get()); } System.out.println("===================callback====================="); }).end(); //wait for callback Thread.sleep(2000); } @Test public void testCallbackInVertxImpl() throws Exception { final TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>(); final InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>(); final String transmittedData = "transmitted_data_ttl"; final String inheritedData = "inherited_data_ttl"; final String messageReplyInBlockingCode = "messageReplyInBlockingCode___"; final Vertx vertx = Vertx.vertx(); //set value after eventLoop thread was created transmittableThreadLocal.set(transmittedData); inheritableThreadLocal.set(inheritedData); vertx.executeBlocking(a -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } a.complete(messageReplyInBlockingCode); }, event -> { System.out.println("receive msg from blocking code:" + event.result()); System.out.println("===================callback====================="); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Test **WITH** TTL Agent"); assertEquals(transmittedData, transmittableThreadLocal.get()); } else { System.out.println("Test WITHOUT TTL Agent"); assertNull(transmittableThreadLocal.get()); } assertNull(inheritableThreadLocal.get()); System.out.println("===================callback====================="); }); //wait for callback Thread.sleep(2000); } } ================================================ FILE: ttl-integrations/vertx4-ttl-integration/README-EN.md ================================================ # Vertx 4 integration of TTL ## 1. assure TTL context transmit in callback ## 1.1 Decorate `io.vertx.core.Handler` Use [`TtlVertxHandler`](src/main/java/com/alibaba/ttl/integration/vertx4/TtlVertxHandler.java) to decorate `Handler`。 ## 1.2 Decorate `io.vertx.core.Future` At present, `TTL` agent has decorated below `Vertx` callback components(`io.vertx.core.Future`) implementation: - `io.vertx.core.Future` - `io.vertx.core.impl.future.FutureImpl` - decoration implementation code is in [`VertxFutureTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx4/agent/transformlet/VertxFutureTtlTransformlet.java)。 Sample code: ```java Vertx vertx = Vertx.vertx(); //build channel ManagedChannel channel = VertxChannelBuilder .forAddress(vertx, "localhost", 8080) .usePlaintext() .build(); // set in parent thread TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); //init stub io.grpc.stub.XXX stub = XXX.newVertxStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("Julien").build(); //init handler Handler> handler = event -> { // read in callback, value is "value-set-in-parent" context.get(); if (event.succeeded()) { //do something } else { // find exception } }; // extra work, create decorated TtlVertxHandler object TtlVertxHandler> ttlVertxHandler = TtlVertxHandler.get(handler); //send request stub.sayHello(request).onComplete(ttlVertxHandler); ``` ## 2. assure TTL context transmit in eventbus ### 2. decorate`java.lang.Runnable` Use [`TtlRunnable`](../../src/main/java/com/alibaba/ttl/TtlRunnable.java) to decorate`Runnable`。 ### 2.2 Decorate`io.netty.util.concurrent.SingleThreadEventExecutor` - decoration implementation code is in[`NettySingleThreadEventExecutorTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx4/agent/transformlet/NettySingleThreadEventExecutorTtlTransformlet.java)。 ================================================ FILE: ttl-integrations/vertx4-ttl-integration/README.md ================================================ # Vertx 4的TTL集成 ## 1. 保证异步io回调中传递TTL值 ### 1.1修饰`io.vertx.core.Handler` 使用[`TtlVertxHandler`](src/main/java/com/alibaba/ttl/integration/vertx4/TtlVertxHandler.java)来修饰传入的`Handler`。 ### 1.2 修饰`io.vertx.core.Future` 修饰了的Vert.x执行器组件如下: - `io.vertx.core.Future` - `io.vertx.core.impl.future.FutureImpl` --- - 修饰实现代码在[`VertxFutureTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx4/agent/transformlet/VertxFutureTtlTransformlet.java)。 示例代码: ```java Vertx vertx = Vertx.vertx(); //build channel ManagedChannel channel = VertxChannelBuilder .forAddress(vertx, "localhost", 8080) .usePlaintext() .build(); // set in parent thread TransmittableThreadLocal context = new TransmittableThreadLocal<>(); context.set("value-set-in-parent"); //init stub io.grpc.stub.XXX stub = XXX.newVertxStub(channel); HelloRequest request = HelloRequest.newBuilder().setName("Julien").build(); //init handler Handler> handler = event -> { // read in callback, value is "value-set-in-parent" context.get(); if (event.succeeded()) { //do something } else { // find exception } }; // extra work, create decorated TtlVertxHandler object TtlVertxHandler> ttlVertxHandler = TtlVertxHandler.get(handler); //send request stub.sayHello(request).onComplete(ttlVertxHandler); ``` ## 2. 保证`eventbus`上传递TTL值 ### 2.1修饰`java.lang.Runnable` 使用[`TtlRunnable`](../../src/main/java/com/alibaba/ttl/TtlRunnable.java)来修饰传入的`Runnable`。 ### 2.2 修饰`io.netty.util.concurrent.SingleThreadEventExecutor` 修饰实现代码在[`NettySingleThreadEventExecutorTtlTransformlet.java`](src/main/java/com/alibaba/ttl/integration/vertx4/agent/transformlet/NettySingleThreadEventExecutorTtlTransformlet.java)。 ================================================ FILE: ttl-integrations/vertx4-ttl-integration/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT ../../pom.xml vertx4-ttl-integration 0.1.0-SNAPSHOT jar ${project.artifactId} TTL integration for vert.x 4 https://github.com/alibaba/transmittable-thread-local/tree/master/ttl-integrations/vertx4-ttl-integration 2021 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions true true ${ttl.integration.test.skip} io.vertx vertx-core provided io.netty netty-all provided com.alibaba transmittable-thread-local ${project.parent.version} provided io.vertx vertx-web-client test io.netty netty-bom 4.1.84.Final pom import io.vertx vertx-stack-depchain 4.3.4 pom import enable-TtlAgent-forTest org.apache.maven.plugins maven-surefire-plugin ${surefire.verbose.class} -javaagent:${com.alibaba:transmittable-thread-local:jar}=ttl.agent.logger:STDOUT ${surefire.ttl.agent.log.class.transform} org.apache.maven.plugins maven-dependency-plugin 3.8.1 initialize properties enable-LogTransform-forTest -Dttl.agent.log.class.transform enable-verboseClass-forTest -verbose:class ================================================ FILE: ttl-integrations/vertx4-ttl-integration/src/main/java/com/alibaba/ttl/integration/vertx4/TtlVertxHandler.java ================================================ package com.alibaba.ttl.integration.vertx4; import com.alibaba.ttl.spi.TtlAttachments; import com.alibaba.ttl.spi.TtlAttachmentsDelegate; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import io.vertx.core.Handler; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * {@link TtlVertxHandler} decorate {@link Handler}, so as to get {@link com.alibaba.ttl.TransmittableThreadLocal} * and transmit it to the time of {@link Handler} execution, * needed when use {@link Handler} to {@link io.vertx.core.Future}. *

* we will capture ttl value in another thread by modify {@link io.netty.util.concurrent.SingleThreadEventExecutor#execute(Runnable)}, * but we can not capture the ttl value which we expect in callback of identical thread. * the reason of above issue is some async io callback was invoked by the * {@link io.netty.channel.nio.NioEventLoop#run()} rather than the {@link com.alibaba.ttl.TtlRunnable#run()} * * @author tk (305809299 at qq dot com) * @see io.vertx.core.Future * @see io.netty.channel.nio.NioEventLoop#run() * @see io.netty.channel.nio.NioEventLoop#processSelectedKeys() */ public class TtlVertxHandler implements Handler, TtlWrapper>, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Handler handler; private final boolean releaseTtlValueReferenceAfterRun; private TtlVertxHandler(@NonNull Handler handler, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<>(capture()); this.handler = handler; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link Handler#handle(Object)}. */ @Override public void handle(E event) { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Object backup = replay(captured); try { handler.handle(event); } finally { restore(backup); } } /** * return original/unwrapped {@link Handler}. */ @NonNull public Handler getHandler() { return unwrap(); } /** * unwrap to original/unwrapped {@link Handler}. * * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) */ @NonNull @Override public Handler unwrap() { return handler; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @SuppressWarnings("unchecked") TtlVertxHandler that = (TtlVertxHandler) o; return handler.equals(that.handler); } @Override public int hashCode() { return handler.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + handler.toString(); } /** * Factory method, wrap input {@link Handler} to {@link TtlVertxHandler}. * * @param handler input {@link Handler}. if input is {@code null}, return {@code null}. * @return Wrapped {@link Handler} * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @Nullable public static TtlVertxHandler get(@Nullable Handler handler) { return get(handler, false, false); } /** * Factory method, wrap input {@link Handler} to {@link TtlVertxHandler}. * * @param handler input {@link Handler}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @return Wrapped {@link Handler} * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @Nullable public static TtlVertxHandler get(@Nullable Handler handler, boolean releaseTtlValueReferenceAfterRun) { return get(handler, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link Handler} to {@link TtlVertxHandler}. * * @param handler input {@link Handler}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Handler} when it's {@link TtlVertxHandler}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Handler} * @throws IllegalStateException when input is {@link TtlVertxHandler} already and not idempotent. */ @Nullable public static TtlVertxHandler get(@Nullable Handler handler, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (handler == null) { return null; } if (handler instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) { return (TtlVertxHandler) handler; } else { throw new IllegalStateException("Already TtlVertxHandler!"); } } return new TtlVertxHandler<>(handler, releaseTtlValueReferenceAfterRun); } /** * wrap input {@link Handler} Collection to {@link TtlVertxHandler} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @NonNull public static List> gets(@Nullable Collection> tasks) { return gets(tasks, false, false); } /** * wrap input {@link Handler} Collection to {@link TtlVertxHandler} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlVertxHandler} already. */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterRun) { return gets(tasks, releaseTtlValueReferenceAfterRun, false); } /** * wrap input {@link Handler} Collection to {@link TtlVertxHandler} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlVertxHandler} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Handler} when it's {@link TtlVertxHandler}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlVertxHandler} already and not idempotent. */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (tasks == null) { return Collections.emptyList(); } List> copy = new ArrayList<>(); for (Handler task : tasks) { copy.add(TtlVertxHandler.get(task, releaseTtlValueReferenceAfterRun, idempotent)); } return copy; } /** * Unwrap {@link TtlVertxHandler} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code Function} parameter is {@code null}, return {@code null}; * if input {@code Function} parameter is not a {@link TtlVertxHandler} just return input {@code Function}. *

* so {@code TtlVertxHandler.unwrap(TtlVertxHandler.get(function))} will always return the same input {@code function} object. * * @see #handle(Object) * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) */ @Nullable public static Handler unwrap(@Nullable Handler handler) { if (!(handler instanceof TtlVertxHandler)) { return handler; } else { return ((TtlVertxHandler) handler).getHandler(); } } /** * Unwrap {@link TtlVertxHandler} to the original/underneath one for collection. *

* Invoke {@link #unwrap(Handler)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code Handler} parameter collection is {@code null}, return a empty list. * * @see #gets(Collection) * @see #unwrap(Handler) */ @NonNull public static List> unwraps(@Nullable Collection> tasks) { if (tasks == null) { return Collections.emptyList(); } List> copy = new ArrayList<>(); for (Handler task : tasks) { if (!(task instanceof TtlVertxHandler)) { copy.add(task); } else { copy.add(((TtlVertxHandler) task).getHandler()); } } return copy; } private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); /** * see {@link TtlAttachments#setTtlAttachment(String, Object)} */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } } ================================================ FILE: ttl-integrations/vertx4-ttl-integration/src/main/java/com/alibaba/ttl/integration/vertx4/agent/transformlet/NettySingleThreadEventExecutorTtlTransformlet.java ================================================ package com.alibaba.ttl.integration.vertx4.agent.transformlet; import com.alibaba.ttl.threadpool.agent.transformlet.helper.AbstractExecutorTtlTransformlet; import java.util.HashSet; import java.util.Set; /** * {@link com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet} * for {@link io.netty.util.concurrent.SingleThreadEventExecutor}. * * @author tk (305809299 at qq dot com) * @see io.netty.util.concurrent.SingleThreadEventExecutor * @see io.vertx.core.eventbus.EventBus * @see io.vertx.core.impl.EventLoopContext * @see io.vertx.core.eventbus.Message */ public final class NettySingleThreadEventExecutorTtlTransformlet extends AbstractExecutorTtlTransformlet { private static Set getExecutorClassNames() { Set executorClassNames = new HashSet<>(); executorClassNames.add("io.netty.util.concurrent.SingleThreadEventExecutor"); return executorClassNames; } public NettySingleThreadEventExecutorTtlTransformlet() { super(getExecutorClassNames(), false); } } ================================================ FILE: ttl-integrations/vertx4-ttl-integration/src/main/java/com/alibaba/ttl/integration/vertx4/agent/transformlet/VertxFutureTtlTransformlet.java ================================================ package com.alibaba.ttl.integration.vertx4.agent.transformlet; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CannotCompileException; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CtClass; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.CtMethod; import com.alibaba.ttl.threadpool.agent.transformlet.javassist.NotFoundException; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; /** * {@link TtlTransformlet} for {@link io.vertx.core.Future}. * * @author tk (305809299 at qq dot com) * @see com.alibaba.ttl.integration.vertx4.TtlVertxHandler * @see io.vertx.core.Future * @see io.vertx.core.Handler */ public class VertxFutureTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(VertxFutureTtlTransformlet.class); private static final String HANDLER_CLASS_NAME = "io.vertx.core.Handler"; private static final String TTL_HANDLER_CLASS_NAME = "com.alibaba.ttl.integration.vertx4.TtlVertxHandler"; private static final String FUTURE_CLASS_NAME = "io.vertx.core.Future"; private static final String FUTURE_IMPL_CLASS_NAME = "io.vertx.core.impl.future.FutureImpl"; private static final Set TO_BE_TRANSFORMED_CLASS_NAMES = new HashSet<>(); static { TO_BE_TRANSFORMED_CLASS_NAMES.add(FUTURE_CLASS_NAME); TO_BE_TRANSFORMED_CLASS_NAMES.add(FUTURE_IMPL_CLASS_NAME); } @Override public void doTransform(@NonNull ClassInfo classInfo) throws CannotCompileException, NotFoundException, IOException { final CtClass clazz = classInfo.getCtClass(); if (TO_BE_TRANSFORMED_CLASS_NAMES.contains(classInfo.getClassName())) { for (CtMethod method : clazz.getDeclaredMethods()) { updateSetHandlerMethodsOfFutureClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); } classInfo.setModified(); } } private void updateSetHandlerMethodsOfFutureClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(CtMethod method) throws NotFoundException, CannotCompileException { final int modifiers = method.getModifiers(); if (!checkMethodNeedToBeDecorated(modifiers)) { return; } CtClass[] parameterTypes = method.getParameterTypes(); StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (HANDLER_CLASS_NAME.equals(paramTypeName)) { String code = String.format( // decorate to TTL wrapper, // and then set AutoWrapper attachment/Tag "$%d = %s.get($%1$d, false, true);" + "%n com.alibaba.ttl.spi.TtlAttachmentsDelegate.setAutoWrapperAttachment($%1$d);", i + 1, TTL_HANDLER_CLASS_NAME); logger.info("insert code before method " + signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ":\n" + code); insertCode.append(code); } } if (insertCode.length() > 0) method.insertBefore(insertCode.toString()); } private boolean checkMethodNeedToBeDecorated(int modifiers) { return Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && !Modifier.isAbstract(modifiers); } } ================================================ FILE: ttl-integrations/vertx4-ttl-integration/src/main/resources/META-INF/ttl.agent.transformlets ================================================ com.alibaba.ttl.integration.vertx4.agent.transformlet.NettySingleThreadEventExecutorTtlTransformlet com.alibaba.ttl.integration.vertx4.agent.transformlet.VertxFutureTtlTransformlet ================================================ FILE: ttl-integrations/vertx4-ttl-integration/src/test/java/com/alibaba/ttl/integration/vertx4/VertxTransformletTest.java ================================================ package com.alibaba.ttl.integration.vertx4; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.threadpool.agent.TtlAgent; import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.Message; import io.vertx.ext.web.client.HttpResponse; import io.vertx.ext.web.client.WebClient; import org.junit.Test; import java.util.concurrent.TimeUnit; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * @author tk (soulmate.tangk at gmail dot com) */ public class VertxTransformletTest { @Test public void testTransmitThreadLocal_InEventbus() throws Exception { final TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>(); final InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>(); final String transmittedData = "transmitted_data"; final String inheritedData = "inherited_data_ttl"; final String message = "message_42"; final Vertx vertx = Vertx.vertx(); final String address = "consumer"; vertx.eventBus().consumer(address, msg -> { // be executed in netty event loop thread System.out.println("========================================"); assertEquals(message, msg.body()); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Test **WITH** TTL Agent"); assertEquals(transmittedData, transmittableThreadLocal.get()); } else { System.out.println("Test WITHOUT TTL Agent"); assertNull(transmittableThreadLocal.get()); } // InheritableThreadLocal is always null assertNull(inheritableThreadLocal.get()); System.out.println("========================================"); //reply message can be get by {messageFuture.toCompletionStage().toCompletableFuture().get().body()} or {listener} msg.reply(message); }); transmittableThreadLocal.set(transmittedData); inheritableThreadLocal.set(inheritedData); // delivery message final DeliveryOptions deliveryOptions = new DeliveryOptions(); deliveryOptions.setSendTimeout(1000); final Future> messageFuture = vertx.eventBus().request(address, message, deliveryOptions); messageFuture.toCompletionStage().toCompletableFuture().get(); assertEquals(message, messageFuture.toCompletionStage().toCompletableFuture().get().body()); } @Test public void testCallback() throws Exception { final TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>(); final InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal<>(); final String transmittedData = "transmitted_data_ttl"; final String inheritedData = "inherited_data_ttl"; final Vertx vertx = Vertx.vertx(); //here will bind eventLoop to client and create a new Thread for eventLoop final WebClient client = WebClient.create(vertx); client.get(80, "bing.com", "/") .send() .onSuccess(response -> { System.out.println("===================warmup callback====================="); System.out.println(response.headers()); System.out.println("===================warmup callback====================="); }) .toCompletionStage().toCompletableFuture().get(10, TimeUnit.SECONDS); //set value after eventLoop thread was created transmittableThreadLocal.set(transmittedData); inheritableThreadLocal.set(inheritedData); final Future> future = client.get(80, "bing.com", "/") .send() .onSuccess(response -> { System.out.println("===================callback====================="); System.out.println(response.headers()); if (TtlAgent.isTtlAgentLoaded()) { System.out.println("Test **WITH** TTL Agent"); assertEquals(transmittedData, transmittableThreadLocal.get()); } else { System.out.println("Test WITHOUT TTL Agent"); assertNull(transmittableThreadLocal.get()); } System.out.println("===================callback====================="); }); // block and wait to finish future.toCompletionStage().toCompletableFuture().get(10, TimeUnit.SECONDS); } } ================================================ FILE: ttl-kotlin/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT ../pom.xml ttl-kotlin jar TransmittableThreadLocal(TTL) kotlin support ${project.name} https://github.com/alibaba/transmittable-thread-local 2022 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions Alibaba https://www.alibaba.com Jerry Lee oldratlee oldratlee(AT)gmail(DOT)com Developer +8 https://github.com/oldratlee Alibaba https://www.alibaba.com rybalkinsd rybalkinsd yan.brikl(AT)gmail(DOT)com Developer +3 https://github.com/rybalkinsd Alibaba https://www.alibaba.com 1.5 com.alibaba.ttl3 ttl-core ${project.version} org.jetbrains.kotlin kotlin-stdlib-jdk8 org.jetbrains.kotlin kotlin-maven-plugin compile compile ${project.basedir}/src/main/kotlin test-compile test-compile ${project.basedir}/src/test/kotlin maven-compiler-plugin true true gen-api-doc performRelease true org.jetbrains.dokka dokka-maven-plugin ${project.basedir}/src/main/kotlin maven-javadoc-plugin true ================================================ FILE: ttl-kotlin/src/main/kotlin/com/alibaba/ttl3/kotlin/Transmitter.kt ================================================ package com.alibaba.ttl3.kotlin import com.alibaba.crr.composite.Capture import com.alibaba.ttl3.transmitter.Transmitter import com.alibaba.ttl3.transmitter.Transmitter.* /** * Util method for simplifying [Transmitter.capture] and [Transmitter.restore] operations. * * @param captured captured values from other thread from [Transmitter.capture] * @param bizLogic biz logic * @param R the return type of biz logic * @see [Transmitter.runSupplierWithCaptured] * @see [Transmitter.runCallableWithCaptured] */ inline fun ttlRun(captured: Capture, bizLogic: () -> R): R { val backup = replay(captured) return try { bizLogic() } finally { restore(backup) } } /** * Util method for simplifying [Transmitter.clear] and [Transmitter.restore] operations. * * @param bizLogic biz logic * @param R the return type of biz logic * @see [Transmitter.runSupplierWithClear] * @see [Transmitter.runCallableWithClear] */ inline fun ttlRunWithClear(bizLogic: () -> R): R { val backup = clear() return try { bizLogic() } finally { restore(backup) } } ================================================ FILE: ttl-kotlin/src/main/kotlin/com/alibaba/ttl3/kotlin/TtlExtensions.kt ================================================ package com.alibaba.ttl3.kotlin import com.alibaba.ttl3.TtlCallable import com.alibaba.ttl3.TtlRunnable import com.alibaba.ttl3.TtlWrappers import com.alibaba.ttl3.executor.TtlExecutors import com.alibaba.ttl3.spi.TtlEnhanced import com.alibaba.ttl3.spi.TtlWrapper import com.alibaba.ttl3.transmitter.Transmitter import java.util.concurrent.* import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory import java.util.function.* import java.util.function.Function //////////////////////////////////////// // Runnable //////////////////////////////////////// /** * wrap input [Runnable] to [TtlRunnable]. * * @see TtlRunnable.get */ @Suppress("NOTHING_TO_INLINE") inline fun Runnable.ttlWrap( releaseTtlValueReferenceAfterRun: Boolean = false, idempotent: Boolean = false ): TtlRunnable = TtlRunnable.get(this, releaseTtlValueReferenceAfterRun, idempotent) as TtlRunnable /** * unwrap [TtlRunnable] to the original/underneath one. * * @see TtlRunnable.ttlUnwrap */ @Suppress("NOTHING_TO_INLINE") inline fun Runnable.ttlUnwrap(): Runnable = TtlRunnable.unwrap(this) as Runnable /** * wrap input [Runnable] collection to [TtlRunnable] collection. * * @see TtlRunnable.gets */ @JvmName("ttlWrapRunnableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection?.ttlWrap( releaseTtlValueReferenceAfterRun: Boolean = false, idempotent: Boolean = false ): List = TtlRunnable.gets(this, releaseTtlValueReferenceAfterRun, idempotent) /** * wrap input nullable [Runnable] collection to [TtlRunnable] collection. * * @see TtlRunnable.gets */ @JvmName("ttlWrapNullableRunnableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection?.ttlWrap( releaseTtlValueReferenceAfterRun: Boolean = false, idempotent: Boolean = false ): List = TtlRunnable.gets(this, releaseTtlValueReferenceAfterRun, idempotent) /** * unwrap [TtlRunnable] to the original/underneath one for collection. * * @see TtlRunnable.unwraps */ @JvmName("ttlUnwrapRunnableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection?.ttlUnwrap(): List = TtlRunnable.unwraps(this) /** * unwrap nullable [TtlRunnable] to the original/underneath one for collection. * * @see TtlRunnable.unwraps */ @JvmName("ttlUnwrapNullableRunnableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection?.ttlUnwrap(): List = TtlRunnable.unwraps(this) //////////////////////////////////////// // Callable //////////////////////////////////////// /** * wrap input [Callable] to [TtlCallable]. * * @see TtlCallable.get */ @Suppress("NOTHING_TO_INLINE") inline fun Callable.ttlWrap( releaseTtlValueReferenceAfterCall: Boolean = false, idempotent: Boolean = false ): TtlCallable = TtlCallable.get(this, releaseTtlValueReferenceAfterCall, idempotent) as TtlCallable /** * unwrap [TtlCallable] to the original/underneath one. * * @see TtlCallable.ttlUnwrap */ @Suppress("NOTHING_TO_INLINE") inline fun Callable.ttlUnwrap(): Callable = TtlCallable.unwrap(this) as Callable /** * wrap input [Callable] collection to [TtlCallable] collection. * * @see TtlCallable.gets */ @JvmName("ttlWrapCallableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection>?.ttlWrap( releaseTtlValueReferenceAfterCall: Boolean = false, idempotent: Boolean = false ): List> = TtlCallable.gets(this, releaseTtlValueReferenceAfterCall, idempotent) /** * wrap nullable input [Callable] collection to [TtlCallable] collection. * * @see TtlCallable.gets */ @JvmName("ttlWrapNullableCallableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection?>?.ttlWrap( releaseTtlValueReferenceAfterCall: Boolean = false, idempotent: Boolean = false ): List?> = TtlCallable.gets(this, releaseTtlValueReferenceAfterCall, idempotent) /** * unwrap [TtlCallable] to the original/underneath one for collection. * * @see TtlCallable.unwraps */ @JvmName("ttlUnwrapCallableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection>?.ttlUnwrap(): List> = TtlCallable.unwraps(this) /** * unwrap nullable [TtlCallable] to the original/underneath one for collection. * * @see TtlCallable.unwraps */ @JvmName("ttlUnwrapNullableCallableCollection") @Suppress("NOTHING_TO_INLINE") inline fun Collection?>?.ttlUnwrap(): List?> = TtlCallable.unwraps(this) //////////////////////////////////////// // java common functional interface //////////////////////////////////////// /** * wrap [Supplier] to TTL wrapper. * * @see TtlWrappers.wrapSupplier */ @Suppress("NOTHING_TO_INLINE") inline fun Supplier.ttlWrap(): Supplier = TtlWrappers.wrapSupplier(this) as Supplier /** * wrap [Consumer] to TTL wrapper. * * @see TtlWrappers.wrapConsumer */ @Suppress("NOTHING_TO_INLINE") inline fun Consumer.ttlWrap(): Consumer = TtlWrappers.wrapConsumer(this) as Consumer /** * wrap [BiConsumer] to TTL wrapper. * * @see TtlWrappers.wrapBiConsumer */ @Suppress("NOTHING_TO_INLINE") inline fun BiConsumer.ttlWrap(): BiConsumer = TtlWrappers.wrapBiConsumer(this) as BiConsumer /** * wrap [Function] to TTL wrapper. * * @see TtlWrappers.wrapFunction */ @Suppress("NOTHING_TO_INLINE") inline fun Function.ttlWrap(): Function = TtlWrappers.wrapFunction(this) as Function /** * wrap [BiFunction] to TTL wrapper. * * @see TtlWrappers.wrapFunction */ @Suppress("NOTHING_TO_INLINE") inline fun BiFunction.ttlWrap(): BiFunction = TtlWrappers.wrapBiFunction(this) as BiFunction //////////////////////////////////////// // kotlin function types //////////////////////////////////////// /** * wrap to TTL wrapper. */ fun (() -> R).ttlWrap(): () -> R { if (this is TtlEnhanced) return this val captured = Transmitter.capture() return object : () -> R, TtlWrapper<() -> R> { override fun unwrap(): () -> R = this@ttlWrap override fun invoke(): R = ttlRun(captured) { this@ttlWrap.invoke() } } } /** * wrap to TTL wrapper. */ fun ((P1) -> R).ttlWrap(): (P1) -> R { if (this is TtlEnhanced) return this val captured = Transmitter.capture() return object : (P1) -> R, TtlWrapper<(P1) -> R> { override fun unwrap(): (P1) -> R = this@ttlWrap override fun invoke(arg: P1): R = ttlRun(captured) { this@ttlWrap.invoke(arg) } } } /** * wrap to TTL wrapper. */ fun ((P1, P2) -> R).ttlWrap(): (P1, P2) -> R { if (this is TtlEnhanced) return this val captured = Transmitter.capture() return object : (P1, P2) -> R, TtlWrapper<(P1, P2) -> R> { override fun unwrap(): (P1, P2) -> R = this@ttlWrap override fun invoke(arg1: P1, arg2: P2): R = ttlRun(captured) { this@ttlWrap.invoke(arg1, arg2) } } } /** * wrap to TTL wrapper. */ fun ((P1, P2, P3) -> R).ttlWrap(): (P1, P2, P3) -> R { if (this is TtlEnhanced) return this val captured = Transmitter.capture() return object : (P1, P2, P3) -> R, TtlWrapper<(P1, P2, P3) -> R> { override fun unwrap(): (P1, P2, P3) -> R = this@ttlWrap override fun invoke(arg1: P1, arg2: P2, arg3: P3): R = ttlRun(captured) { this@ttlWrap.invoke(arg1, arg2, arg3) } } } /** * wrap to TTL wrapper. */ fun ((P1, P2, P3, P4) -> R).ttlWrap(): (P1, P2, P3, P4) -> R { if (this is TtlEnhanced) return this val captured = Transmitter.capture() return object : (P1, P2, P3, P4) -> R, TtlWrapper<(P1, P2, P3, P4) -> R> { override fun unwrap(): (P1, P2, P3, P4) -> R = this@ttlWrap override fun invoke(arg1: P1, arg2: P2, arg3: P3, arg4: P4): R = ttlRun(captured) { this@ttlWrap.invoke(arg1, arg2, arg3, arg4) } } } //////////////////////////////////////// // Generic unwrap/check method //////////////////////////////////////// /** * generic unwrap method, unwrap [TtlWrapper] to the original/underneath one. * * if input parameter is not a [TtlWrapper] just return input. * * @see TtlWrappers.unwrap */ @Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") inline fun T.ttlUnwrap(): T = TtlWrappers.unwrap(this) as T /** * check the input object is a `TtlWrapper` or not. * * @see TtlWrappers.isWrapper */ @Suppress("NOTHING_TO_INLINE") inline fun T.isTtlWrapper(): Boolean = TtlWrappers.isWrapper(this) //////////////////////////////////////// // Executor //////////////////////////////////////// /** * wrap input [Executor] to `TtlExecutor`. * * @see TtlExecutors.getTtlExecutor */ @Suppress("NOTHING_TO_INLINE") inline fun Executor.ttlWrap(): Executor = TtlExecutors.getTtlExecutor(this) as Executor /** * wrap input [ExecutorService] to `TtlExecutorService`. * * @see TtlExecutors.getTtlExecutorService */ @Suppress("NOTHING_TO_INLINE") inline fun ExecutorService.ttlWrap(): ExecutorService = TtlExecutors.getTtlExecutorService(this) as ExecutorService /** * wrap input [ScheduledExecutorService] to `TtlScheduledExecutorService`. * * @see TtlExecutors.getTtlScheduledExecutorService */ @Suppress("NOTHING_TO_INLINE") inline fun ScheduledExecutorService.ttlWrap(): ScheduledExecutorService = TtlExecutors.getTtlScheduledExecutorService(this) as ScheduledExecutorService /** * check the executor is a TTL executor wrapper or not. */ @Suppress("NOTHING_TO_INLINE") inline fun Executor?.isTtlExecutor(): Boolean = TtlExecutors.isTtlExecutor(this) /** * unwrap `TtlExecutor` to the original/underneath one. * * @see TtlExecutors.unwrapTtlExecutor */ @Suppress("NOTHING_TO_INLINE") inline fun E.ttlUnwrap(): E = TtlExecutors.unwrapTtlExecutor(this) as E //////////////////////////////////////// // Thread Factory //////////////////////////////////////// /** * wrapper of [ThreadFactory], disable inheritable. * * @see TtlExecutors.getDisableInheritableThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun ThreadFactory.ttlWrapToDisableInheritableThreadFactory(): ThreadFactory = TtlExecutors.getDisableInheritableThreadFactory(this) as ThreadFactory /** * wrapper of [Executors.defaultThreadFactory], disable inheritable. * * @see TtlExecutors.getDefaultDisableInheritableThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun getDefaultDisableInheritableThreadFactory(): ThreadFactory = TtlExecutors.getDefaultDisableInheritableThreadFactory() /** * check the [ThreadFactory] is `DisableInheritableThreadFactory` or not. * * @see TtlExecutors.isDisableInheritableThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun ThreadFactory?.isDisableInheritableThreadFactory(): Boolean = TtlExecutors.isDisableInheritableThreadFactory(this) /** * unwrap `DisableInheritableThreadFactory` to the original/underneath one. * * @see TtlExecutors.unwrapDisableInheritableThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun ThreadFactory.ttlUnwrapDisableInheritableThreadFactory(): ThreadFactory = TtlExecutors.unwrapDisableInheritableThreadFactory(this) as ThreadFactory /** * wrapper of [ForkJoinWorkerThreadFactory], disable inheritable. * * @see TtlExecutors.getDisableInheritableForkJoinWorkerThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun ForkJoinWorkerThreadFactory.ttlWrapToDisableInheritableForkJoinWorkerThreadFactory(): ForkJoinWorkerThreadFactory = TtlExecutors.getDisableInheritableForkJoinWorkerThreadFactory(this) as ForkJoinWorkerThreadFactory /** * wrapper of [ForkJoinPool.defaultForkJoinWorkerThreadFactory], disable inheritable. * * @see TtlExecutors.getDefaultDisableInheritableForkJoinWorkerThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun getDefaultDisableInheritableForkJoinWorkerThreadFactory(): ForkJoinWorkerThreadFactory = TtlExecutors.getDefaultDisableInheritableForkJoinWorkerThreadFactory() /** * check the [ForkJoinWorkerThreadFactory] is `DisableInheritableForkJoinWorkerThreadFactory` or not. * * @see TtlExecutors.isDisableInheritableForkJoinWorkerThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun ForkJoinWorkerThreadFactory?.isDisableInheritableForkJoinWorkerThreadFactory(): Boolean = TtlExecutors.isDisableInheritableForkJoinWorkerThreadFactory(this) /** * unwrap `DisableInheritableForkJoinWorkerThreadFactory` to the original/underneath one. * * @see TtlExecutors.unwrapDisableInheritableForkJoinWorkerThreadFactory */ @Suppress("NOTHING_TO_INLINE") inline fun ForkJoinWorkerThreadFactory.ttlUnwrapDisableInheritableForkJoinWorkerThreadFactory(): ForkJoinWorkerThreadFactory = TtlExecutors.unwrapDisableInheritableForkJoinWorkerThreadFactory(this) as ForkJoinWorkerThreadFactory //////////////////////////////////////// // Comparator //////////////////////////////////////// /** * wrapper of `Comparator` which unwrap [TtlRunnable] before compare, * aka `TtlRunnableUnwrapComparator`. * * @see TtlExecutors.getTtlRunnableUnwrapComparator */ @Suppress("NOTHING_TO_INLINE") inline fun Comparator.ttlWrapToTtlRunnableUnwrapComparator() = TtlExecutors.getTtlRunnableUnwrapComparator(this) as Comparator /** * `TtlRunnableUnwrapComparator` that compares {@link Comparable Comparable} objects. * * @see TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable */ @Suppress("NOTHING_TO_INLINE") inline fun getTtlRunnableUnwrapComparatorForComparableRunnable(): Comparator = TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable() /** * check the `Comparator` is a wrapper `TtlRunnableUnwrapComparator` or not. * * @see TtlExecutors.isTtlRunnableUnwrapComparator */ @Suppress("NOTHING_TO_INLINE") inline fun Comparator?.isTtlRunnableUnwrapComparator(): Boolean = TtlExecutors.isTtlRunnableUnwrapComparator(this) /** * unwrap `TtlRunnableUnwrapComparator` to the original/underneath `Comparator`. * * @see TtlExecutors.unwrapTtlRunnableUnwrapComparator */ @Suppress("NOTHING_TO_INLINE") inline fun Comparator.ttlUnwrapTtlRunnableUnwrapComparator(): Comparator = TtlExecutors.unwrapTtlRunnableUnwrapComparator(this) as Comparator ================================================ FILE: ttl-kotlin/src/test/kotlin/com/alibaba/Utils.kt ================================================ package com.alibaba import io.kotest.assertions.withClue import io.kotest.matchers.booleans.shouldBeTrue import java.time.Duration import java.util.concurrent.ExecutorService import java.util.concurrent.Future import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit /** * Expand thread pool, to pre-create and cache threads. */ fun expandThreadPool(executor: ExecutorService) { val cpuCountX2 = Runtime.getRuntime().availableProcessors() * 2 val count = if (executor is ThreadPoolExecutor) { (executor.maximumPoolSize * 2).coerceAtMost(cpuCountX2) } else cpuCountX2 (0 until count).map { executor.submit { Thread.sleep(10) } }.forEach { it.getForTest() } } //////////////////////////////////////////////////////////////////////////////// // shutdown/await util methods for test //////////////////////////////////////////////////////////////////////////////// private val timeout = Duration.ofSeconds(3) fun ExecutorService.shutdownForTest() { shutdown() withClue("Fail to shutdown thread pool") { awaitTermination(timeout.toMillis(), TimeUnit.MILLISECONDS).shouldBeTrue() } } fun Future.getForTest(): T = this.get(timeout.toMillis(), TimeUnit.MILLISECONDS) ================================================ FILE: ttl-kotlin/src/test/kotlin/com/alibaba/ttl3/kotlin/TtlExtensionsTests.kt ================================================ package com.alibaba.ttl3.kotlin import com.alibaba.expandThreadPool import com.alibaba.getForTest import com.alibaba.shutdownForTest import com.alibaba.ttl3.TransmittableThreadLocal import com.alibaba.ttl3.TtlCallable import com.alibaba.ttl3.TtlRunnable import com.alibaba.ttl3.executor.TtlExecutors import com.alibaba.ttl3.spi.TtlWrapper import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.inspectors.forAll import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainInOrder import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.matchers.types.shouldBeTypeOf import io.kotest.matchers.types.shouldNotBeSameInstanceAs import java.util.concurrent.* import java.util.function.* import kotlin.random.Random class TtlExtensionsTests : FunSpec({ lateinit var executorService: ExecutorService beforeSpec { executorService = Executors.newFixedThreadPool(3).let { expandThreadPool(it) TtlExecutors.getTtlExecutorService(it)!! } } afterSpec { executorService.shutdownForTest() } test("ttl runnable wrap & unwrap") { val r = Runnable { } r.ttlUnwrap() shouldBeSameInstanceAs r r.isTtlWrapper().shouldBeFalse() r.ttlWrap().let { wrap -> wrap.ttlWrap(idempotent = true) shouldBeSameInstanceAs wrap r.ttlWrap() shouldNotBeSameInstanceAs wrap r.ttlWrap() shouldNotBe wrap wrap.ttlUnwrap() shouldBeSameInstanceAs r wrap.unwrap() shouldBeSameInstanceAs r wrap.runnable shouldBeSameInstanceAs r (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs r wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeTypeOf() } shouldThrow { r.ttlWrap().ttlWrap() }.message shouldContain "Already TtlRunnable" } test("ttl runnable wraps") { val r1 = Runnable {} val r2 = Runnable {} val r3 = Runnable {} val list = listOf(r1, r2, r3).ttlWrap() list.shouldHaveSize(3) list.forAll { it.shouldBeTypeOf() } val nullableRunnableList = listOf(r1, r2, null, r3).ttlWrap() nullableRunnableList.shouldHaveSize(4) nullableRunnableList[0].shouldBeTypeOf() nullableRunnableList[1].shouldBeTypeOf() nullableRunnableList[2].shouldBeNull() nullableRunnableList[3].shouldBeTypeOf() } test("ttl runnable unwraps") { val r1 = Runnable {} val r2 = Runnable {} val r3 = Runnable {} listOf(null as Runnable?).ttlUnwrap().shouldContainInOrder(null) listOf(r1).ttlUnwrap().shouldContainInOrder(r1) listOf(r1.ttlWrap()).ttlUnwrap().shouldContainInOrder(r1) listOf(r1, r2, r3).ttlUnwrap().shouldContainInOrder(r1, r2, r3) listOf(r1, r2, r3).ttlWrap().ttlUnwrap().shouldContainInOrder(r1, r2, r3) listOf(r1, r2.ttlWrap(), r3).ttlUnwrap() .shouldContainInOrder(r1, r2, r3) listOf(r1, r2.ttlWrap(), null, r3).ttlUnwrap() .shouldContainInOrder(r1, r2, null, r3) (null as Collection?).ttlUnwrap().shouldBeEmpty() (null as Collection?).ttlUnwrap().shouldBeEmpty() } test("ttl callable wrap & unwrap") { val c = Callable { 42 } c.ttlUnwrap() shouldBeSameInstanceAs c c.isTtlWrapper().shouldBeFalse() c.ttlWrap().let { wrap -> wrap.ttlWrap(idempotent = true) shouldBeSameInstanceAs wrap c.ttlWrap() shouldNotBeSameInstanceAs wrap c.ttlWrap() shouldNotBe wrap wrap.ttlUnwrap() shouldBeSameInstanceAs c wrap.unwrap() shouldBeSameInstanceAs c wrap.callable shouldBeSameInstanceAs c (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs c wrap shouldNotBeSameInstanceAs c.ttlWrap() wrap shouldNotBe c.ttlWrap() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeTypeOf>() } shouldThrow { c.ttlWrap().ttlWrap() }.message shouldContain "Already TtlCallable" } test("ttl callable wraps") { val c1 = Callable { 1 } val c2 = Callable { 2 } val c3 = Callable { 3 } val list = listOf(c1, c2, c3).ttlWrap() list.shouldHaveSize(3) list.forAll { it.shouldBeTypeOf>() } val nullableCallableList = listOf(c1, c2, null, c3).ttlWrap() nullableCallableList.shouldHaveSize(4) nullableCallableList[0].shouldBeTypeOf>() nullableCallableList[1].shouldBeTypeOf>() nullableCallableList[2].shouldBeNull() nullableCallableList[3].shouldBeTypeOf>() } test("ttl callable unwraps") { val c1 = Callable { 1 } val c2 = Callable { 2 } val c3 = Callable { 3 } listOf(null as Callable?).shouldContainInOrder(null) listOf(c1).ttlUnwrap().shouldContainInOrder(c1) listOf(c1.ttlWrap()).ttlUnwrap().shouldContainInOrder(c1) listOf(c1, c2, c3).ttlUnwrap().shouldContainInOrder(c1, c2, c3) listOf(c1, c2, c3).ttlWrap().ttlUnwrap().shouldContainInOrder(c1, c2, c3) listOf(c1, c2.ttlWrap(), c3).ttlUnwrap() .shouldContainInOrder(c1, c2, c3) listOf(c1, c2.ttlWrap(), null, c3).ttlUnwrap() .shouldContainInOrder(c1, c2, null, c3) (null as Collection>?).ttlUnwrap().shouldBeEmpty() (null as Collection?>?).ttlUnwrap().shouldBeEmpty() } val ttl = TransmittableThreadLocal() val parentValue = "parent start ${Random.nextLong()}" fun checkLogicInBody() { ttl.get() shouldBe parentValue ttl.set("child ${Random.nextLong()}") } fun checkLogicAfterRun(task: Runnable) { executorService.submit(task).getForTest() ttl.get() shouldBe parentValue } test("java common functional interface") { ttl.set(parentValue) val supplier = Supplier { checkLogicInBody() "Hello" } supplier.ttlUnwrap() shouldBeSameInstanceAs supplier supplier.isTtlWrapper().shouldBeFalse() supplier.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w supplier.ttlWrap() shouldNotBeSameInstanceAs w supplier.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs supplier (w as Any).ttlUnwrap() shouldBeSameInstanceAs supplier w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { supplier.get() } val consumer = Consumer { checkLogicInBody() } consumer.ttlUnwrap() shouldBeSameInstanceAs consumer consumer.isTtlWrapper().shouldBeFalse() consumer.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w consumer.ttlWrap() shouldNotBeSameInstanceAs w consumer.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs consumer (w as Any).ttlUnwrap() shouldBeSameInstanceAs consumer w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { consumer.accept("") } val biConsumer = BiConsumer { _, _ -> checkLogicInBody() } biConsumer.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w biConsumer.ttlWrap() shouldNotBeSameInstanceAs w biConsumer.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs biConsumer (w as Any).ttlUnwrap() shouldBeSameInstanceAs biConsumer w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { biConsumer.accept("", "") } val function = Function { checkLogicInBody() } function.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w function.ttlWrap() shouldNotBeSameInstanceAs w function.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs function (w as Any).ttlUnwrap() shouldBeSameInstanceAs function w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { function.apply("") } val biFunction = BiFunction { _, _ -> checkLogicInBody() } biFunction.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w biFunction.ttlWrap() shouldNotBeSameInstanceAs w biFunction.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs biFunction (w as Any).ttlUnwrap() shouldBeSameInstanceAs biFunction w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { biFunction.apply("", "") } } test("kotlin function types") { ttl.set(parentValue) val f0: () -> Unit = { checkLogicInBody() } f0.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w f0.ttlWrap() shouldNotBeSameInstanceAs w f0.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs f0 (w as Any).ttlUnwrap() shouldBeSameInstanceAs f0 w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun(f0) val f1: (String) -> Unit = { checkLogicInBody() } f1.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w f1.ttlWrap() shouldNotBeSameInstanceAs w f1.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs f1 (w as Any).ttlUnwrap() shouldBeSameInstanceAs f1 w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { f1("") } val f2: (String, Int) -> Unit = { _, _ -> checkLogicInBody() } f2.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w f2.ttlWrap() shouldNotBeSameInstanceAs w f2.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs f2 (w as Any).ttlUnwrap() shouldBeSameInstanceAs f2 w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { f2("", 1) } val f3: (String, Int, Double) -> Unit = { _, _, _ -> checkLogicInBody() } f3.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w f3.ttlWrap() shouldNotBeSameInstanceAs w f3.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs f3 (w as Any).ttlUnwrap() shouldBeSameInstanceAs f3 w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { f3("", 1, 1.0) } val f4: (String, Int, Double, Regex) -> Unit = { _, _, _, _ -> checkLogicInBody() } f4.ttlWrap().let { w -> w.ttlWrap() shouldBeSameInstanceAs w f4.ttlWrap() shouldNotBeSameInstanceAs w f4.ttlWrap() shouldNotBe w w.ttlUnwrap() shouldBeSameInstanceAs f4 (w as Any).ttlUnwrap() shouldBeSameInstanceAs f4 w.isTtlWrapper().shouldBeTrue() } checkLogicAfterRun { f4("", 1, 1.0, Regex(".")) } } test("executor wrap & unwrap") { val executor: Executor = Executors.newCachedThreadPool() executor.ttlUnwrap() shouldBeSameInstanceAs executor executor.isTtlExecutor().shouldBeFalse() executor.isTtlWrapper().shouldBeFalse() executor.ttlWrap().let { wrap -> wrap.ttlWrap() shouldBeSameInstanceAs wrap executor.ttlWrap() shouldNotBeSameInstanceAs wrap executor.ttlWrap() shouldBe wrap wrap.ttlUnwrap() shouldBeSameInstanceAs executor (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs executor wrap.isTtlExecutor().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } val es: ExecutorService = Executors.newCachedThreadPool() es.ttlUnwrap() shouldBeSameInstanceAs es es.isTtlExecutor().shouldBeFalse() es.isTtlWrapper().shouldBeFalse() es.ttlWrap().let { wrap: ExecutorService -> wrap.ttlWrap() shouldBeSameInstanceAs wrap es.ttlWrap() shouldNotBeSameInstanceAs wrap es.ttlWrap() shouldBe wrap wrap.ttlUnwrap() shouldBeSameInstanceAs es (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs es wrap.isTtlExecutor().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } val scheduledExecutorService: ScheduledExecutorService = ScheduledThreadPoolExecutor(1) scheduledExecutorService.ttlUnwrap() shouldBeSameInstanceAs scheduledExecutorService scheduledExecutorService.isTtlExecutor().shouldBeFalse() scheduledExecutorService.isTtlWrapper().shouldBeFalse() scheduledExecutorService.ttlWrap().let { wrap -> wrap.ttlWrap() shouldBeSameInstanceAs wrap scheduledExecutorService.ttlWrap() shouldNotBeSameInstanceAs wrap scheduledExecutorService.ttlWrap() shouldBe wrap wrap.ttlUnwrap() shouldBeSameInstanceAs scheduledExecutorService (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs scheduledExecutorService wrap.isTtlExecutor().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } } test("DisableInheritableThreadFactory") { val factory = Executors.defaultThreadFactory() factory.ttlUnwrap() shouldBeSameInstanceAs factory factory.ttlUnwrapDisableInheritableThreadFactory() shouldBeSameInstanceAs factory factory.isDisableInheritableThreadFactory().shouldBeFalse() factory.isTtlWrapper().shouldBeFalse() factory.ttlWrapToDisableInheritableThreadFactory().let { wrap -> wrap.ttlWrapToDisableInheritableThreadFactory() shouldBeSameInstanceAs wrap factory.ttlWrapToDisableInheritableThreadFactory() shouldNotBeSameInstanceAs wrap factory.ttlWrapToDisableInheritableThreadFactory() shouldBe wrap wrap.ttlUnwrapDisableInheritableThreadFactory() shouldBeSameInstanceAs factory wrap.ttlUnwrap() shouldBeSameInstanceAs factory (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs factory wrap.isDisableInheritableThreadFactory().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } getDefaultDisableInheritableThreadFactory().let { wrap -> wrap.ttlWrapToDisableInheritableThreadFactory() shouldBeSameInstanceAs wrap wrap.ttlUnwrapDisableInheritableThreadFactory().javaClass shouldBe factory.javaClass wrap.ttlUnwrap().javaClass shouldBe factory.javaClass (wrap as Any).ttlUnwrap().javaClass shouldBe factory.javaClass wrap.isDisableInheritableThreadFactory().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } } test("DisableInheritableForkJoinWorkerThreadFactory") { val factory = ForkJoinPool.defaultForkJoinWorkerThreadFactory factory.ttlUnwrap() shouldBeSameInstanceAs factory factory.ttlUnwrapDisableInheritableForkJoinWorkerThreadFactory() shouldBeSameInstanceAs factory factory.isDisableInheritableForkJoinWorkerThreadFactory().shouldBeFalse() factory.isTtlWrapper().shouldBeFalse() factory.ttlWrapToDisableInheritableForkJoinWorkerThreadFactory().let { wrap -> wrap.ttlWrapToDisableInheritableForkJoinWorkerThreadFactory() shouldBeSameInstanceAs wrap factory.ttlWrapToDisableInheritableForkJoinWorkerThreadFactory() shouldNotBeSameInstanceAs wrap factory.ttlWrapToDisableInheritableForkJoinWorkerThreadFactory() shouldBe wrap wrap.ttlUnwrapDisableInheritableForkJoinWorkerThreadFactory() shouldBeSameInstanceAs factory wrap.ttlUnwrap() shouldBeSameInstanceAs factory (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs factory wrap.isDisableInheritableForkJoinWorkerThreadFactory().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } getDefaultDisableInheritableForkJoinWorkerThreadFactory().let { wrap -> wrap.ttlWrapToDisableInheritableForkJoinWorkerThreadFactory() shouldBeSameInstanceAs wrap wrap.ttlUnwrapDisableInheritableForkJoinWorkerThreadFactory().javaClass shouldBe factory.javaClass wrap.ttlUnwrap().javaClass shouldBe factory.javaClass (wrap as Any).ttlUnwrap().javaClass shouldBe factory.javaClass wrap.isDisableInheritableForkJoinWorkerThreadFactory().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } } test("TtlRunnableUnwrapComparator") { val comparator: Comparator = Comparator.comparing { it.hashCode() } comparator.ttlUnwrap() shouldBeSameInstanceAs comparator comparator.ttlUnwrapTtlRunnableUnwrapComparator() shouldBeSameInstanceAs comparator comparator.isTtlRunnableUnwrapComparator().shouldBeFalse() comparator.isTtlWrapper().shouldBeFalse() comparator.ttlWrapToTtlRunnableUnwrapComparator().let { wrap -> wrap.ttlWrapToTtlRunnableUnwrapComparator() shouldBeSameInstanceAs wrap comparator.ttlWrapToTtlRunnableUnwrapComparator() shouldNotBeSameInstanceAs wrap comparator.ttlWrapToTtlRunnableUnwrapComparator() shouldBe wrap wrap.ttlUnwrapTtlRunnableUnwrapComparator() shouldBeSameInstanceAs comparator wrap.ttlUnwrap() shouldBeSameInstanceAs comparator (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs comparator wrap.isTtlRunnableUnwrapComparator().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } getTtlRunnableUnwrapComparatorForComparableRunnable().let { wrap -> wrap.ttlWrapToTtlRunnableUnwrapComparator() shouldBeSameInstanceAs wrap wrap.ttlUnwrapTtlRunnableUnwrapComparator().javaClass.name shouldBe "com.alibaba.ttl3.executor.ComparableComparator" wrap.ttlUnwrapTtlRunnableUnwrapComparator() shouldBeSameInstanceAs wrap.ttlUnwrapTtlRunnableUnwrapComparator() wrap.ttlUnwrap() shouldBeSameInstanceAs wrap.ttlUnwrapTtlRunnableUnwrapComparator() (wrap as Any).ttlUnwrap() shouldBeSameInstanceAs wrap.ttlUnwrapTtlRunnableUnwrapComparator() wrap.isTtlRunnableUnwrapComparator().shouldBeTrue() wrap.isTtlWrapper().shouldBeTrue() wrap.shouldBeInstanceOf>() } } }) ================================================ FILE: ttl2-compatible/pom.xml ================================================ 4.0.0 com.alibaba.ttl3 ttl3-parent 3.x-SNAPSHOT ../pom.xml com.alibaba transmittable-thread-local jar TransmittableThreadLocal(TTL) v2 compatible 📌 The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components. https://github.com/alibaba/transmittable-thread-local 2013 Apache 2 https://www.apache.org/licenses/LICENSE-2.0.txt repo A business-friendly OSS license scm:git:git@github.com:alibaba/transmittable-thread-local.git scm:git:git@github.com:alibaba/transmittable-thread-local.git https://github.com/alibaba/transmittable-thread-local https://github.com/alibaba/transmittable-thread-local/issues GitHub Issues GitHub Actions https://github.com/alibaba/transmittable-thread-local/actions Alibaba https://www.alibaba.com Jerry Lee oldratlee oldratlee(AT)gmail(DOT)com Developer +8 https://github.com/oldratlee Alibaba https://www.alibaba.com Yang Fang driventokill snoop(DOT)fy(AT)gmail(DOT)com Developer +8 https://github.com/driventokill Alibaba https://www.alibaba.com wuwen wuwen5 wuwen.55(AT)aliyun(DOT)com Developer +8 https://github.com/wuwen5 ofpay https://www.ofpay.com David Dai LNAmp 351450944(AT)qq(DOT)com Developer +8 https://github.com/LNAmp Alibaba https://www.alibaba.com org.javassist javassist true org.apache.commons commons-lang3 test org.jetbrains.kotlinx kotlinx-coroutines-core-jvm test io.reactivex.rxjava2 rxjava test io.reactivex.rxjava2 rxkotlin test maven-jar-plugin com.alibaba.ttl.threadpool.agent.TtlAgent ${project.build.finalName}.jar false true false maven-shade-plugin shade-when-package package shade javassist com.alibaba.ttl.threadpool.agent.transformlet.javassist org.javassist:javassist org.javassist:javassist META-INF/MANIFEST.MF config-for-jdk16+ [16,) maven-surefire-plugin **/*$* **/JavassistTest* enable-ttl-agent-for-test ${project.build.directory}/${project.artifactId}-${project.version}.jar ttl.agent.logger:STDOUT -javaagent:${ttl.built.agent.jar}=${ttl.agent.args},${ttl.agent.extra.args} -Drun-ttl-test-under-agent=true ${ttl.agent.extra.d.options} ${ttl.agent.jvm.arg} com.alibaba.demo.ttl.agent.AgentDemo maven-surefire-plugin 1 true org.javassist:javassist com.github.spotbugs:spotbugs-annotations com.google.code.findbugs:jsr305 org.jetbrains:annotations @{argLine} ${ttl.agent.jvm.args} org.codehaus.mojo exec-maven-plugin -Xmx1g -Xms256m -ea -server -Duser.language=en -Duser.country=US ${ttl.agent.jvm.arg} -classpath ${exec.mainClass} gen-src performRelease true maven-source-plugin false maven-shade-plugin shade-when-package true gen-api-doc performRelease true maven-javadoc-plugin com/alibaba/ttl/TtlEnhanced.java gen-code-cov env.CI true org.jacoco jacoco-maven-plugin com/alibaba/ttl/threadpool/agent/**/*.class com/alibaba/ttl/TtlTimerTask.class ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TransmittableThreadLocal.java ================================================ package com.alibaba.ttl; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.TestOnly; import javax.annotation.ParametersAreNonnullByDefault; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; /** * {@link TransmittableThreadLocal}({@code TTL}) can transmit the value from the thread of submitting task * to the thread of executing task even using thread pooling components. *

* Note:
* {@link TransmittableThreadLocal} extends {@link InheritableThreadLocal}, * so {@link TransmittableThreadLocal} first is a {@link InheritableThreadLocal}.
* If the inheritable ability from {@link InheritableThreadLocal} has potential leaking problem, * you can disable the inheritable ability: *

* ❶ For thread pooling components({@link java.util.concurrent.ThreadPoolExecutor}, * {@link java.util.concurrent.ForkJoinPool}), Inheritable feature should never happen, * since threads in thread pooling components is pre-created and pooled, these threads is neutral to biz logic/data. *
* Disable inheritable for thread pooling components by wrapping thread factories using methods * {@link com.alibaba.ttl.threadpool.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) getDisableInheritableThreadFactory} / * {@link com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory() getDefaultDisableInheritableForkJoinWorkerThreadFactory}. *
* Or you can turn on "disable inheritable for thread pool" by {@link com.alibaba.ttl.threadpool.agent.TtlAgent} * to wrap thread factories for thread pooling components automatically and transparently. *

* ❷ In other cases, disable inheritable by overriding method {@link #childValue(Object)}. *
* Whether the value should be inheritable or not can be controlled by the data owner, * disable it carefully when data owner have a clear idea. *

{@code
 * TransmittableThreadLocal transmittableThreadLocal = new TransmittableThreadLocal<>() {
 *     protected String childValue(String parentValue) {
 *         return initialValue();
 *     }
 * }}
*

* More discussion about "disable the inheritable ability" * see * issue #100: disable Inheritable when it's not necessary and buggy. * * @author Jerry Lee (oldratlee at gmail dot com) * @author Yang Fang (snoop dot fy at gmail dot com) * @see user guide docs and code repo of TransmittableThreadLocal(TTL) * @see TtlRunnable * @see TtlCallable * @see com.alibaba.ttl.threadpool.TtlExecutors * @see com.alibaba.ttl.threadpool.TtlExecutors#getTtlExecutor(java.util.concurrent.Executor) * @see com.alibaba.ttl.threadpool.TtlExecutors#getTtlExecutorService(java.util.concurrent.ExecutorService) * @see com.alibaba.ttl.threadpool.TtlExecutors#getTtlScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) * @see com.alibaba.ttl.threadpool.TtlExecutors#getDefaultDisableInheritableThreadFactory() * @see com.alibaba.ttl.threadpool.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) * @see com.alibaba.ttl.threadpool.agent.TtlAgent * @since 0.10.0 */ public class TransmittableThreadLocal extends InheritableThreadLocal implements TtlCopier { private static final Logger logger = Logger.getLogger(TransmittableThreadLocal.class.getName()); private final boolean disableIgnoreNullValueSemantics; /** * Default constructor. Create a {@link TransmittableThreadLocal} instance with "Ignore-Null-Value Semantics". *

* About "Ignore-Null-Value Semantics": * *

    *
  1. If value is {@code null}(check by {@link #get()} method), do NOT transmit this {@code ThreadLocal}.
  2. *
  3. If set {@code null} value, also remove value(invoke {@link #remove()} method).
  4. *
*

* This is a pragmatic design decision: *

    *
  1. use explicit value type rather than {@code null} value to express biz intent.
  2. *
  3. safer and more robust code(avoid {@code NPE} risk).
  4. *
*

* So it's strongly not recommended to use {@code null} value. *

* But the behavior of "Ignore-Null-Value Semantics" is NOT compatible with * {@link ThreadLocal} and {@link InheritableThreadLocal}, * you can disable this behavior/semantics via using constructor {@link #TransmittableThreadLocal(boolean)} * and setting parameter {@code disableIgnoreNullValueSemantics} to {@code true}. *

* More discussion about "Ignore-Null-Value Semantics" see * Issue #157. * * @see #TransmittableThreadLocal(boolean) */ public TransmittableThreadLocal() { this(false); } /** * Constructor, create a {@link TransmittableThreadLocal} instance * with parameter {@code disableIgnoreNullValueSemantics} to control "Ignore-Null-Value Semantics". * * @param disableIgnoreNullValueSemantics disable "Ignore-Null-Value Semantics" * @see #TransmittableThreadLocal() * @since 2.11.3 */ public TransmittableThreadLocal(boolean disableIgnoreNullValueSemantics) { this.disableIgnoreNullValueSemantics = disableIgnoreNullValueSemantics; } /** * Creates a transmittable thread local variable. * The initial value({@link #initialValue()}) of the variable is * determined by invoking the {@link #get()} method on the {@code Supplier}. * * @param the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @return a new transmittable thread local variable * @throws NullPointerException if the specified supplier is null * @see #withInitialAndCopier(Supplier, TtlCopier) * @since 2.12.2 */ @NonNull @SuppressWarnings("ConstantConditions") public static TransmittableThreadLocal withInitial(@NonNull Supplier supplier) { if (supplier == null) throw new NullPointerException("supplier is null"); return new SuppliedTransmittableThreadLocal<>(supplier, null, null); } /** * Creates a transmittable thread local variable. * The initial value({@link #initialValue()}) of the variable is * determined by invoking the {@link #get()} method on the {@code Supplier}; * and the child value({@link #childValue(Object)}) and the transmitting value({@link #copy(Object)}) of the variable is * determined by invoking the {@link TtlCopier#copy(Object)} method on the {@code TtlCopier}. * * @param the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @param copierForChildValueAndCopy the ttl copier to be used to determine the child value and the transmitting value * @return a new transmittable thread local variable * @throws NullPointerException if the specified supplier or copier is null * @see #withInitial(Supplier) * @since 2.12.3 */ @NonNull @ParametersAreNonnullByDefault @SuppressWarnings("ConstantConditions") public static TransmittableThreadLocal withInitialAndCopier(Supplier supplier, TtlCopier copierForChildValueAndCopy) { if (supplier == null) throw new NullPointerException("supplier is null"); if (copierForChildValueAndCopy == null) throw new NullPointerException("ttl copier is null"); return new SuppliedTransmittableThreadLocal<>(supplier, copierForChildValueAndCopy, copierForChildValueAndCopy); } /** * Creates a transmittable thread local variable. * The initial value({@link #initialValue()}) of the variable is * determined by invoking the {@link #get()} method on the {@code Supplier}; * and the child value({@link #childValue(Object)}) and the transmitting value({@link #copy(Object)}) of the variable is * determined by invoking the {@link TtlCopier#copy(Object)} method on the {@code TtlCopier}. *

* NOTE:
* Recommend use {@link #withInitialAndCopier(Supplier, TtlCopier)} instead of this method. * In most cases, the logic of determining the child value({@link #childValue(Object)}) * and the transmitting value({@link #copy(Object)}) should be the same. * * @param the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @param copierForChildValue the ttl copier to be used to determine the child value * @param copierForCopy the ttl copier to be used to determine the transmitting value * @return a new transmittable thread local variable * @throws NullPointerException if the specified supplier or copier is null * @see #withInitial(Supplier) * @see #withInitialAndCopier(Supplier, TtlCopier) * @since 2.12.3 */ @NonNull @ParametersAreNonnullByDefault @SuppressWarnings("ConstantConditions") public static TransmittableThreadLocal withInitialAndCopier(Supplier supplier, TtlCopier copierForChildValue, TtlCopier copierForCopy) { if (supplier == null) throw new NullPointerException("supplier is null"); if (copierForChildValue == null) throw new NullPointerException("ttl copier for child value is null"); if (copierForCopy == null) throw new NullPointerException("ttl copier for copy value is null"); return new SuppliedTransmittableThreadLocal<>(supplier, copierForChildValue, copierForCopy); } /** * An extension of ThreadLocal that obtains its initial value from the specified {@code Supplier} * and obtains its child value and transmitting value from the specified ttl copier. */ private static final class SuppliedTransmittableThreadLocal extends TransmittableThreadLocal { private final Supplier supplier; private final TtlCopier copierForChildValue; private final TtlCopier copierForCopy; SuppliedTransmittableThreadLocal(Supplier supplier, TtlCopier copierForChildValue, TtlCopier copierForCopy) { if (supplier == null) throw new NullPointerException("supplier is null"); this.supplier = supplier; this.copierForChildValue = copierForChildValue; this.copierForCopy = copierForCopy; } @Override protected T initialValue() { return supplier.get(); } @Override protected T childValue(T parentValue) { if (copierForChildValue != null) return copierForChildValue.copy(parentValue); else return super.childValue(parentValue); } @Override public T copy(T parentValue) { if (copierForCopy != null) return copierForCopy.copy(parentValue); else return super.copy(parentValue); } } /** * Computes the value for this transmittable thread-local variable * as a function of the source thread's value at the time the task * Object is created. *

* This method is called from {@link TtlRunnable} or * {@link TtlCallable} when it create, before the task is started. *

* This method merely returns reference of its source thread value(the shadow copy), * and should be overridden if a different behavior is desired. * * @since 1.0.0 */ public T copy(T parentValue) { return parentValue; } /** * Callback method before task object({@link TtlRunnable}/{@link TtlCallable}) execute. *

* Default behavior is to do nothing, and should be overridden * if a different behavior is desired. *

* Do not throw any exception, just ignored. * * @since 1.2.0 */ protected void beforeExecute() { } /** * Callback method after task object({@link TtlRunnable}/{@link TtlCallable}) execute. *

* Default behavior is to do nothing, and should be overridden * if a different behavior is desired. *

* Do not throw any exception, just ignored. * * @since 1.2.0 */ protected void afterExecute() { } /** * {@inheritDoc} */ @Override public final T get() { T value = super.get(); if (disableIgnoreNullValueSemantics || value != null) addThisToHolder(); return value; } /** * {@inheritDoc} */ @Override public final void set(T value) { if (!disableIgnoreNullValueSemantics && value == null) { // may set null to remove value remove(); } else { super.set(value); addThisToHolder(); } } /** * {@inheritDoc} */ @Override public final void remove() { removeThisFromHolder(); super.remove(); } private void superRemove() { super.remove(); } private T copyValue() { return copy(get()); } // Note about the holder: // 1. holder self is a InheritableThreadLocal(a *ThreadLocal*). // 2. The type of value in the holder is WeakHashMap, ?>. // 2.1 but the WeakHashMap is used as a *Set*: // the value of WeakHashMap is *always* null, and never used. // 2.2 WeakHashMap support *null* value. private static final InheritableThreadLocal, ?>> holder = new InheritableThreadLocal, ?>>() { @Override protected WeakHashMap, ?> initialValue() { return new WeakHashMap<>(); } @Override protected WeakHashMap, ?> childValue(WeakHashMap, ?> parentValue) { return new WeakHashMap<>(parentValue); } }; @SuppressWarnings("unchecked") private void addThisToHolder() { if (!holder.get().containsKey(this)) { holder.get().put((TransmittableThreadLocal) this, null); // WeakHashMap supports null value. } } private void removeThisFromHolder() { holder.get().remove(this); } private static void doExecuteCallback(boolean isBefore) { // copy TTL Instances to avoid `ConcurrentModificationException` // even adjust TTL instances in biz lifecycle callbacks(beforeExecute/afterExecute) WeakHashMap, ?> ttlInstances = new WeakHashMap, Object>(holder.get()); for (TransmittableThreadLocal threadLocal : ttlInstances.keySet()) { try { if (isBefore) threadLocal.beforeExecute(); else threadLocal.afterExecute(); } catch (Throwable t) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "TTL exception when " + (isBefore ? "beforeExecute" : "afterExecute") + ", cause: " + t, t); } } } } /** * Debug only method! */ @TestOnly static void dump(@Nullable String title) { if (title != null && title.length() > 0) { System.out.printf("Start TransmittableThreadLocal[%s] Dump...%n", title); } else { System.out.println("Start TransmittableThreadLocal Dump..."); } for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { System.out.println(threadLocal.get()); } System.out.println("TransmittableThreadLocal Dump end!"); } /** * Debug only method! */ @TestOnly static void dump() { dump(null); } /** * {@link Transmitter Transmitter} transmit all {@link TransmittableThreadLocal} * and registered {@link ThreadLocal} values of the current thread to other thread. *

* Transmittance is completed by static methods {@link #capture()} => * {@link #replay(Object)} => {@link #restore(Object)} (aka {@code CRR} operations); * {@link ThreadLocal} instances are registered by method {@link Transmitter#registerThreadLocal Transmitter#registerThreadLocal}. *

* {@link Transmitter Transmitter} is internal manipulation api for framework/middleware integration; * In general, you will never use it in the biz/application codes! * *

Framework/Middleware integration to TTL transmittance

* Below is the example code: * *
{@code
     * ///////////////////////////////////////////////////////////////////////////
     * // in thread A, capture all TransmittableThreadLocal values of thread A
     * ///////////////////////////////////////////////////////////////////////////
     *
     * Object captured = Transmitter.capture(); // (1)
     *
     * ///////////////////////////////////////////////////////////////////////////
     * // in thread B
     * ///////////////////////////////////////////////////////////////////////////
     *
     * // replay all TransmittableThreadLocal values from thread A
     * Object backup = Transmitter.replay(captured); // (2)
     * try {
     *     // your biz logic, run with the TransmittableThreadLocal values of thread B
     *     System.out.println("Hello");
     *     // ...
     *     return "World";
     * } finally {
     *     // restore the TransmittableThreadLocal of thread B when replay
     *     Transmitter.restore(backup); // (3)
     * }}
*

* see the implementation code of {@link TtlRunnable} and {@link TtlCallable} for more actual code samples. *

* Of course, {@link #replay(Object)} and {@link #restore(Object)} operations can be simplified by util methods * {@link #runCallableWithCaptured(Object, Callable)} or {@link #runSupplierWithCaptured(Object, Supplier)} * and the adorable {@code Java 8 lambda syntax}. *

* Below is the example code: * *

{@code
     * ///////////////////////////////////////////////////////////////////////////
     * // in thread A, capture all TransmittableThreadLocal values of thread A
     * ///////////////////////////////////////////////////////////////////////////
     *
     * Object captured = Transmitter.capture(); // (1)
     *
     * ///////////////////////////////////////////////////////////////////////////
     * // in thread B
     * ///////////////////////////////////////////////////////////////////////////
     *
     * String result = runSupplierWithCaptured(captured, () -> {
     *      // your biz logic, run with the TransmittableThreadLocal values of thread A
     *      System.out.println("Hello");
     *      ...
     *      return "World";
     * }); // (2) + (3)}
*

* The reason of providing 2 util methods is the different {@code throws Exception} type * to satisfy your biz logic({@code lambda}): *

    *
  1. {@link #runCallableWithCaptured(Object, Callable)}: {@code throws Exception}
  2. *
  3. {@link #runSupplierWithCaptured(Object, Supplier)}: No {@code throws}
  4. *
*

* If you need the different {@code throws Exception} type, * you can define your own util method(function interface({@code lambda})) * with your own {@code throws Exception} type. * *

ThreadLocal Integration

* If you can not rewrite the existed code which use {@link ThreadLocal} to {@link TransmittableThreadLocal}, * register the {@link ThreadLocal} instances via the methods * {@link #registerThreadLocal(ThreadLocal, TtlCopier)}/{@link #registerThreadLocalWithShadowCopier(ThreadLocal)} * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* Below is the example code: * *

{@code
     * // the value of this ThreadLocal instance will be transmitted after registered
     * Transmitter.registerThreadLocal(aThreadLocal, copyLambda);
     *
     * // Then the value of this ThreadLocal instance will not be transmitted after unregistered
     * Transmitter.unregisterThreadLocal(aThreadLocal);}
*

* The fields stored the {@code ThreadLocal} instances are generally {@code private static}, * so the {@code ThreadLocal} instances need be got by reflection, for example: * *

     * Field field = TheClassStoredThreadLocal.class.getDeclaredField(staticFieldName);
     * field.setAccessible(true);
     * {@code @SuppressWarnings("unchecked")}
     * {@code ThreadLocal} threadLocal = {@code (ThreadLocal)} field.get(null);
* * Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @author Yang Fang (snoop dot fy at gmail dot com) * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlRunnable * @see TtlCallable * @since 2.3.0 */ public static class Transmitter { /** * Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread. * * @return the captured {@link TransmittableThreadLocal} values * @since 2.3.0 */ @NonNull public static Object capture() { final HashMap, Object> transmittee2Value = new HashMap<>(transmitteeSet.size()); for (Transmittee transmittee : transmitteeSet) { try { transmittee2Value.put(transmittee, transmittee.capture()); } catch (Throwable t) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t); } } } return new Snapshot(transmittee2Value); } /** * Replay the captured {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values from {@link #capture()}, * and return the backup {@link TransmittableThreadLocal} values in the current thread before replay. * * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} * @return the backup {@link TransmittableThreadLocal} values before replay * @see #capture() * @since 2.3.0 */ @NonNull public static Object replay(@NonNull Object captured) { final Snapshot capturedSnapshot = (Snapshot) captured; final HashMap, Object> transmittee2Value = new HashMap<>(capturedSnapshot.transmittee2Value.size()); for (Map.Entry, Object> entry : capturedSnapshot.transmittee2Value.entrySet()) { Transmittee transmittee = entry.getKey(); try { Object transmitteeCaptured = entry.getValue(); transmittee2Value.put(transmittee, transmittee.replay(transmitteeCaptured)); } catch (Throwable t) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when Transmitter.replay for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t); } } } return new Snapshot(transmittee2Value); } /** * Clear all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread, * and return the backup {@link TransmittableThreadLocal} values in the current thread before clear. *

* Semantically, the code {@code `Object backup = clear();`} is same as {@code `Object backup = replay(EMPTY_CAPTURE);`}. *

* The reason for providing this method is: * *

    *
  1. lead to more readable code
  2. *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. *
* * @return the backup {@link TransmittableThreadLocal} values before clear * @see #replay(Object) * @since 2.9.0 */ @NonNull public static Object clear() { final HashMap, Object> transmittee2Value = new HashMap<>(transmitteeSet.size()); for (Transmittee transmittee : transmitteeSet) { try { transmittee2Value.put(transmittee, transmittee.clear()); } catch (Throwable t) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when Transmitter.clear for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t); } } } return new Snapshot(transmittee2Value); } /** * Restore the backup {@link TransmittableThreadLocal} and * registered {@link ThreadLocal} values from {@link #replay(Object)}/{@link #clear()}. * * @param backup the backup {@link TransmittableThreadLocal} values from {@link #replay(Object)}/{@link #clear()} * @see #replay(Object) * @see #clear() * @since 2.3.0 */ public static void restore(@NonNull Object backup) { for (Map.Entry, Object> entry : ((Snapshot) backup).transmittee2Value.entrySet()) { Transmittee transmittee = entry.getKey(); try { Object transmitteeBackup = entry.getValue(); transmittee.restore(transmitteeBackup); } catch (Throwable t) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "exception when Transmitter.restore for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t); } } } } private static class Snapshot { final HashMap, Object> transmittee2Value; public Snapshot(HashMap, Object> transmittee2Value) { this.transmittee2Value = transmittee2Value; } } /** * Register the transmittee({@code CRR}), the extension point for other {@code ThreadLocal}. * * @param the transmittee capture data type * @param the transmittee backup data type * @return true if the input transmittee is not registered * @see #unregisterTransmittee(Transmittee) * @since 2.14.0 */ @SuppressWarnings("unchecked") public static boolean registerTransmittee(@NonNull Transmittee transmittee) { return transmitteeSet.add((Transmittee) transmittee); } /** * Unregister the transmittee({@code CRR}), the extension point for other {@code ThreadLocal}. * * @param the transmittee capture data type * @param the transmittee backup data type * @return true if the input transmittee is registered * @see #registerTransmittee(Transmittee) * @since 2.14.0 */ @SuppressWarnings("unchecked") public static boolean unregisterTransmittee(@NonNull Transmittee transmittee) { return transmitteeSet.remove((Transmittee) transmittee); } /** * The transmittee is the extension point for other {@code ThreadLocal}s * which are registered by {@link #registerTransmittee(Transmittee) registerTransmittee} method. * Transmittance is completed by methods {@link #capture() capture()} => * {@link #replay(Object) replay(Object)} => {@link #restore(Object) restore(Object)} (aka {@code CRR} operations), * * @param the transmittee capture data type * @param the transmittee backup data type * @see #registerTransmittee(Transmittee) * @see #unregisterTransmittee(Transmittee) * @since 2.14.0 */ public interface Transmittee { /** * Capture. *

* NOTE: *

    *
  • do NOT return {@code null}.
  • *
  • do NOT throw any exceptions, just ignored.
  • *
* * @return the capture data of transmittee * @since 2.14.0 */ @NonNull C capture(); /** * Replay. *

* NOTE: *

    *
  • do NOT return {@code null}.
  • *
  • do NOT throw any exceptions, just ignored.
  • *
* * @param captured the capture data of transmittee, the return value of method {@link #capture()} * @return the backup data of transmittee * @since 2.14.0 */ @NonNull B replay(@NonNull C captured); /** * Clear. *

* NOTE: *

    *
  • do NOT return {@code null}.
  • *
  • do NOT throw any exceptions, just ignored.
  • *
*

* Semantically, the code {@code `B backup = clear();`} is same as {@code `B backup = replay(EMPTY_CAPTURE);`}. *

* The reason for providing this method is: *

    *
  1. lead to more readable code
  2. *
  3. need not provide the constant {@code EMPTY_CAPTURE}.
  4. *
* * @return the backup data of transmittee * @since 2.14.0 */ @NonNull B clear(); /** * Restore. *

* NOTE:
* do NOT throw any exceptions, just ignored. * * @param backup the backup data of transmittee, the return value of methods {@link #replay(Object)} or {@link #clear()} * @see #replay(Object) * @see #clear() * @since 2.14.0 */ void restore(@NonNull B backup); } private static final Transmittee, Object>, HashMap, Object>> ttlTransmittee = new Transmittee, Object>, HashMap, Object>>() { @NonNull @Override public HashMap, Object> capture() { final HashMap, Object> ttl2Value = new HashMap<>(holder.get().size()); for (TransmittableThreadLocal threadLocal : holder.get().keySet()) { ttl2Value.put(threadLocal, threadLocal.copyValue()); } return ttl2Value; } @NonNull @Override public HashMap, Object> replay(@NonNull HashMap, Object> captured) { final HashMap, Object> backup = new HashMap<>(holder.get().size()); for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal threadLocal = iterator.next(); // backup backup.put(threadLocal, threadLocal.get()); // clear the TTL values that is not in captured // avoid the extra TTL values after replay when run task if (!captured.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // set TTL values to captured setTtlValuesTo(captured); // call beforeExecute callback doExecuteCallback(true); return backup; } @NonNull @Override public HashMap, Object> clear() { return replay(new HashMap<>(0)); } @Override public void restore(@NonNull HashMap, Object> backup) { // call afterExecute callback doExecuteCallback(false); for (final Iterator> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal threadLocal = iterator.next(); // clear the TTL values that is not in backup // avoid the extra TTL values after restore if (!backup.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } // restore TTL values setTtlValuesTo(backup); } }; private static void setTtlValuesTo(@NonNull HashMap, Object> ttlValues) { for (Map.Entry, Object> entry : ttlValues.entrySet()) { TransmittableThreadLocal threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } private static final Transmittee, Object>, HashMap, Object>> threadLocalTransmittee = new Transmittee, Object>, HashMap, Object>>() { @NonNull @Override public HashMap, Object> capture() { final HashMap, Object> threadLocal2Value = new HashMap<>(threadLocalHolder.size()); for (Map.Entry, TtlCopier> entry : threadLocalHolder.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); final TtlCopier copier = entry.getValue(); threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get())); } return threadLocal2Value; } @NonNull @Override public HashMap, Object> replay(@NonNull HashMap, Object> captured) { final HashMap, Object> backup = new HashMap<>(captured.size()); for (Map.Entry, Object> entry : captured.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); backup.put(threadLocal, threadLocal.get()); final Object value = entry.getValue(); if (value == threadLocalClearMark) threadLocal.remove(); else threadLocal.set(value); } return backup; } @NonNull @Override public HashMap, Object> clear() { final HashMap, Object> threadLocal2Value = new HashMap<>(threadLocalHolder.size()); for (Map.Entry, TtlCopier> entry : threadLocalHolder.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); threadLocal2Value.put(threadLocal, threadLocalClearMark); } return replay(threadLocal2Value); } @Override public void restore(@NonNull HashMap, Object> backup) { for (Map.Entry, Object> entry : backup.entrySet()) { final ThreadLocal threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } }; private static final Set> transmitteeSet = new CopyOnWriteArraySet<>(); static { registerTransmittee(ttlTransmittee); registerTransmittee(threadLocalTransmittee); } /** * Util method for simplifying {@link #replay(Object)} and {@link #restore(Object)} operations. * * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @see #capture() * @see #replay(Object) * @see #restore(Object) * @since 2.3.1 */ public static R runSupplierWithCaptured(@NonNull Object captured, @NonNull Supplier bizLogic) { final Object backup = replay(captured); try { return bizLogic.get(); } finally { restore(backup); } } /** * Util method for simplifying {@link #clear()} and {@link #restore(Object)} operations. * * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @see #clear() * @see #restore(Object) * @since 2.9.0 */ public static R runSupplierWithClear(@NonNull Supplier bizLogic) { final Object backup = clear(); try { return bizLogic.get(); } finally { restore(backup); } } /** * Util method for simplifying {@link #replay(Object)} and {@link #restore(Object)} operations. * * @param captured captured {@link TransmittableThreadLocal} values from other thread from {@link #capture()} * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @throws Exception the exception threw by biz logic * @see #capture() * @see #replay(Object) * @see #restore(Object) * @since 2.3.1 */ @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public static R runCallableWithCaptured(@NonNull Object captured, @NonNull Callable bizLogic) throws Exception { final Object backup = replay(captured); try { return bizLogic.call(); } finally { restore(backup); } } /** * Util method for simplifying {@link #clear()} and {@link #restore(Object)} operations. * * @param bizLogic biz logic * @param the return type of biz logic * @return the return value of biz logic * @throws Exception the exception threw by biz logic * @see #clear() * @see #restore(Object) * @since 2.9.0 */ @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public static R runCallableWithClear(@NonNull Callable bizLogic) throws Exception { final Object backup = clear(); try { return bizLogic.call(); } finally { restore(backup); } } private static volatile WeakHashMap, TtlCopier> threadLocalHolder = new WeakHashMap<>(); private static final Object threadLocalHolderUpdateLock = new Object(); private static final Object threadLocalClearMark = new Object(); /** * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, * it is unnecessary to register a {@link TransmittableThreadLocal} instance. *

* Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability * @param copier the {@link TtlCopier} * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) * @since 2.11.0 */ public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull TtlCopier copier) { return registerThreadLocal(threadLocal, copier, false); } /** * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* Use the shadow copier(transmit the reference directly), * and should use method {@link #registerThreadLocal(ThreadLocal, TtlCopier)} to pass a customized {@link TtlCopier} explicitly * if a different behavior is desired. *

* If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, * it is unnecessary to register a {@link TransmittableThreadLocal} instance. *

* Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} * @see #registerThreadLocal(ThreadLocal, TtlCopier) * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) * @since 2.11.0 */ @SuppressWarnings("unchecked") public static boolean registerThreadLocalWithShadowCopier(@NonNull ThreadLocal threadLocal) { return registerThreadLocal(threadLocal, (TtlCopier) shadowCopier, false); } /** * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, * it is unnecessary to register a {@link TransmittableThreadLocal} instance. *

* Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability * @param copier the {@link TtlCopier} * @param force if {@code true}, update {@code copier} to {@link ThreadLocal} instance * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} * @see #registerThreadLocal(ThreadLocal, TtlCopier) * @since 2.11.0 */ @SuppressWarnings("unchecked") public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull TtlCopier copier, boolean force) { if (threadLocal instanceof TransmittableThreadLocal) { logger.warning("register a TransmittableThreadLocal instance, this is unnecessary!"); return true; } synchronized (threadLocalHolderUpdateLock) { if (!force && threadLocalHolder.containsKey(threadLocal)) return false; WeakHashMap, TtlCopier> newHolder = new WeakHashMap<>(threadLocalHolder); newHolder.put((ThreadLocal) threadLocal, (TtlCopier) copier); threadLocalHolder = newHolder; return true; } } /** * Register the {@link ThreadLocal}(including subclass {@link InheritableThreadLocal}) instances * to enhance the Transmittable ability for the existed {@link ThreadLocal} instances. *

* Use the shadow copier(transmit the reference directly), * and should use method {@link #registerThreadLocal(ThreadLocal, TtlCopier, boolean)} to pass a customized {@link TtlCopier} explicitly * if a different behavior is desired. *

* If the registered {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * since a {@link TransmittableThreadLocal} instance itself has the {@code Transmittable} ability, * it is unnecessary to register a {@link TransmittableThreadLocal} instance. *

* Caution:
* If the registered {@link ThreadLocal} instance is not {@link InheritableThreadLocal}, * the instance can NOT {@code inherit} value from parent thread(aka. the inheritable ability)! * * @param threadLocal the {@link ThreadLocal} instance that to enhance the Transmittable ability * @param force if {@code true}, update {@code copier} to {@link ThreadLocal} instance * when a {@link ThreadLocal} instance is already registered; otherwise, ignore. * @return {@code true} if register the {@link ThreadLocal} instance and set {@code copier}, otherwise {@code false} * @see #registerThreadLocal(ThreadLocal, TtlCopier) * @see #registerThreadLocal(ThreadLocal, TtlCopier, boolean) * @since 2.11.0 */ @SuppressWarnings("unchecked") public static boolean registerThreadLocalWithShadowCopier(@NonNull ThreadLocal threadLocal, boolean force) { return registerThreadLocal(threadLocal, (TtlCopier) shadowCopier, force); } /** * Unregister the {@link ThreadLocal} instances * to remove the Transmittable ability for the {@link ThreadLocal} instances. *

* If the {@link ThreadLocal} instance is {@link TransmittableThreadLocal} just ignores and return {@code true}. * * @see #registerThreadLocal(ThreadLocal, TtlCopier) * @see #registerThreadLocalWithShadowCopier(ThreadLocal) * @since 2.11.0 */ public static boolean unregisterThreadLocal(@NonNull ThreadLocal threadLocal) { if (threadLocal instanceof TransmittableThreadLocal) { logger.warning("unregister a TransmittableThreadLocal instance, this is unnecessary!"); return true; } synchronized (threadLocalHolderUpdateLock) { if (!threadLocalHolder.containsKey(threadLocal)) return false; WeakHashMap, TtlCopier> newHolder = new WeakHashMap<>(threadLocalHolder); newHolder.remove(threadLocal); threadLocalHolder = newHolder; return true; } } private static final TtlCopier shadowCopier = parentValue -> parentValue; @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private Transmitter() { throw new InstantiationError("Must not instantiate this class"); } } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlCallable.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlAttachments; import com.alibaba.ttl.spi.TtlAttachmentsDelegate; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * {@link TtlCallable} decorate {@link Callable} to get {@link TransmittableThreadLocal} value * and transmit it to the time of {@link Callable} execution, needed when use {@link Callable} to thread pool. *

* Use factory method {@link #get(Callable)} to get decorated instance. *

* Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.TtlExecutors * @see TtlWrappers * @see java.util.concurrent.Executor * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.Executors * @see java.util.concurrent.CompletionService * @see java.util.concurrent.ExecutorCompletionService * @since 0.9.0 */ public final class TtlCallable implements Callable, TtlWrapper>, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Callable callable; private final boolean releaseTtlValueReferenceAfterCall; private TtlCallable(@NonNull Callable callable, boolean releaseTtlValueReferenceAfterCall) { this.capturedRef = new AtomicReference<>(capture()); this.callable = callable; this.releaseTtlValueReferenceAfterCall = releaseTtlValueReferenceAfterCall; } /** * wrap method {@link Callable#call()}. */ @Override @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION") public V call() throws Exception { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after call!"); } final Object backup = replay(captured); try { return callable.call(); } finally { restore(backup); } } /** * return the original/underneath {@link Callable}. */ @NonNull public Callable getCallable() { return unwrap(); } /** * unwrap to the original/underneath {@link Callable}. * * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 */ @NonNull @Override public Callable unwrap() { return callable; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlCallable that = (TtlCallable) o; return callable.equals(that.callable); } @Override public int hashCode() { return callable.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + callable.toString(); } /** * Factory method, wrap input {@link Callable} to {@link TtlCallable}. *

* This method is idempotent. * * @param callable input {@link Callable} * @return Wrapped {@link Callable} */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TtlCallable get(@Nullable Callable callable) { return get(callable, false, false); } /** * Factory method, wrap input {@link Callable} to {@link TtlCallable}. *

* This method is idempotent. * * @param callable input {@link Callable} * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Callable} */ @Nullable @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) public static TtlCallable get(@Nullable Callable callable, boolean releaseTtlValueReferenceAfterCall) { return get(callable, releaseTtlValueReferenceAfterCall, false); } /** * Factory method, wrap input {@link Callable} to {@link TtlCallable}. *

* This method is idempotent. * * @param callable input {@link Callable} * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Callable} */ @Nullable @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) public static TtlCallable get(@Nullable Callable callable, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { if (callable == null) return null; if (callable instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlCallable) callable; else throw new IllegalStateException("Already TtlCallable!"); } return new TtlCallable<>(callable, releaseTtlValueReferenceAfterCall); } /** * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. * * @param tasks task to be wrapped * @return Wrapped {@link Callable} */ @NonNull public static List> gets(@Nullable Collection> tasks) { return gets(tasks, false, false); } /** * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. * * @param tasks task to be wrapped * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Callable} */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterCall) { return gets(tasks, releaseTtlValueReferenceAfterCall, false); } /** * wrap input {@link Callable} Collection to {@link TtlCallable} Collection. * * @param tasks task to be wrapped * @param releaseTtlValueReferenceAfterCall release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Callable} */ @NonNull public static List> gets(@Nullable Collection> tasks, boolean releaseTtlValueReferenceAfterCall, boolean idempotent) { if (tasks == null) return Collections.emptyList(); List> copy = new ArrayList<>(); for (Callable task : tasks) { copy.add(TtlCallable.get(task, releaseTtlValueReferenceAfterCall, idempotent)); } return copy; } /** * Unwrap {@link TtlCallable} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code Callable} parameter is {@code null}, return {@code null}; * if input {@code Callable} parameter is not a {@link TtlCallable} just return input {@code Callable}. *

* so {@code TtlCallable.unwrap(TtlCallable.get(callable))} will always return the same input {@code callable} object. * * @see #get(Callable) * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @since 2.10.2 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Callable unwrap(@Nullable Callable callable) { if (!(callable instanceof TtlCallable)) return callable; else return ((TtlCallable) callable).getCallable(); } /** * Unwrap {@link TtlCallable} to the original/underneath one. *

* Invoke {@link #unwrap(Callable)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code Callable} collection parameter is {@code null}, return an empty list. * * @see #gets(Collection) * @see #unwrap(Callable) * @since 2.10.2 */ @NonNull public static List> unwraps(@Nullable Collection> tasks) { if (tasks == null) return Collections.emptyList(); List> copy = new ArrayList<>(); for (Callable task : tasks) { if (!(task instanceof TtlCallable)) copy.add(task); else copy.add(((TtlCallable) task).getCallable()); } return copy; } private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); /** * see {@link TtlAttachments#setTtlAttachment(String, Object)} * * @since 2.11.0 */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} * * @since 2.11.0 */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlCopier.java ================================================ package com.alibaba.ttl; /** * {@code TtlCopier} copies the value when {@link TransmittableThreadLocal.Transmitter#capture() Transmitter#capture()}, * use the copied value when {@link TransmittableThreadLocal.Transmitter#replay(Object) Transmitter#replay(Object)}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see TransmittableThreadLocal.Transmitter * @see TransmittableThreadLocal.Transmitter#capture() * @since 2.11.0 */ @FunctionalInterface public interface TtlCopier { /** * Computes the value for {@link TransmittableThreadLocal} * or registered {@link ThreadLocal}(registered by method {@link TransmittableThreadLocal.Transmitter#registerThreadLocal Transmitter#registerThreadLocal}) * as a function of the source thread's value at the time the task * Object is created. *

* This method is called from {@link TtlRunnable} or * {@link TtlCallable} when it create, before the task is started * (aka. called when {@link TransmittableThreadLocal.Transmitter#capture() Transmitter#capture()}). * * @see TransmittableThreadLocal.Transmitter#registerThreadLocal(ThreadLocal, TtlCopier) * @see TransmittableThreadLocal.Transmitter#registerThreadLocalWithShadowCopier(ThreadLocal) * @see TransmittableThreadLocal.Transmitter#unregisterThreadLocal */ T copy(T parentValue); } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlEnhanced.java ================================================ package com.alibaba.ttl; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * @see com.alibaba.ttl.spi.TtlAttachments * @deprecated Use {@link com.alibaba.ttl.spi.TtlEnhanced} instead. */ @Deprecated @SuppressFBWarnings({"NM_SAME_SIMPLE_NAME_AS_INTERFACE"}) // [ERROR] The class name com.alibaba.ttl.TtlEnhanced shadows // the simple name of implemented interface com.alibaba.ttl.spi.TtlEnhanced [com.alibaba.ttl.TtlEnhanced] public interface TtlEnhanced extends com.alibaba.ttl.spi.TtlEnhanced { } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlRecursiveAction.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlEnhanced; import java.util.concurrent.ForkJoinTask; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * A recursive resultless {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. *

* Recommend to use {@link com.alibaba.ttl.threadpool.agent.TtlAgent}; * Specially for {@code Java 8} {@link java.util.stream.Stream} and {@link java.util.concurrent.CompletableFuture}, * these async task are executed by {@link java.util.concurrent.ForkJoinPool} via {@link ForkJoinTask} at the bottom. * * @author LNAmp * @see java.util.concurrent.RecursiveAction * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper * @see com.alibaba.ttl.threadpool.agent.TtlAgent * @since 2.4.0 */ public abstract class TtlRecursiveAction extends ForkJoinTask implements TtlEnhanced { private static final long serialVersionUID = -5753568484583412377L; private final Object captured = capture(); protected TtlRecursiveAction() { } /** * The main computation performed by this task. */ protected abstract void compute(); /** * see {@link ForkJoinTask#getRawResult()} */ public final Void getRawResult() { return null; } /** * see {@link ForkJoinTask#setRawResult(Object)} */ protected final void setRawResult(Void mustBeNull) { } /** * Implements execution conventions for RecursiveActions. */ protected final boolean exec() { final Object backup = replay(captured); try { compute(); return true; } finally { restore(backup); } } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlRecursiveTask.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlEnhanced; import java.util.concurrent.ForkJoinTask; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * A recursive result-bearing {@link ForkJoinTask} enhanced by {@link TransmittableThreadLocal}. *

* Recommend to use {@link com.alibaba.ttl.threadpool.agent.TtlAgent}; * Specially for {@code Java 8} {@link java.util.stream.Stream} and {@link java.util.concurrent.CompletableFuture}, * these async task are executed by {@link java.util.concurrent.ForkJoinPool} via {@link ForkJoinTask} at the bottom. * * @author LNAmp * @see java.util.concurrent.RecursiveTask * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper * @see com.alibaba.ttl.threadpool.agent.TtlAgent * @since 2.4.0 */ public abstract class TtlRecursiveTask extends ForkJoinTask implements TtlEnhanced { private static final long serialVersionUID = 1814679366926362436L; private final Object captured = capture(); protected TtlRecursiveTask() { } /** * The result of the computation. */ V result; /** * The main computation performed by this task. * * @return the result of the computation */ protected abstract V compute(); /** * see {@link ForkJoinTask#getRawResult()} */ public final V getRawResult() { return result; } /** * see {@link ForkJoinTask#setRawResult(Object)} */ protected final void setRawResult(V value) { result = value; } /** * Implements execution conventions for RecursiveTask. */ protected final boolean exec() { final Object backup = replay(captured); try { result = compute(); return true; } finally { restore(backup); } } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlRunnable.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlAttachments; import com.alibaba.ttl.spi.TtlAttachmentsDelegate; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import org.jetbrains.annotations.Contract; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * {@link TtlRunnable} decorate {@link Runnable} to get {@link TransmittableThreadLocal} value * and transmit it to the time of {@link Runnable} execution, needed when use {@link Runnable} to thread pool. *

* Use factory methods {@link #get} / {@link #gets} to create instance. *

* Other TTL Wrapper for common {@code Functional Interface} see {@link TtlWrappers}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.TtlExecutors * @see TtlWrappers * @see java.util.concurrent.Executor * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.Executors * @since 0.9.0 */ public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments { private final AtomicReference capturedRef; private final Runnable runnable; private final boolean releaseTtlValueReferenceAfterRun; private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<>(capture()); this.runnable = runnable; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link Runnable#run()}. */ @Override public void run() { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Object backup = replay(captured); try { runnable.run(); } finally { restore(backup); } } /** * return original/unwrapped {@link Runnable}. */ @NonNull public Runnable getRunnable() { return unwrap(); } /** * unwrap to original/unwrapped {@link Runnable}. * * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 */ @NonNull @Override public Runnable unwrap() { return runnable; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlRunnable that = (TtlRunnable) o; return runnable.equals(that.runnable); } @Override public int hashCode() { return runnable.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + runnable.toString(); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TtlRunnable get(@Nullable Runnable runnable) { return get(runnable, false, false); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @Nullable @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { return get(runnable, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link Runnable} to {@link TtlRunnable}. * * @param runnable input {@link Runnable}. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link Runnable} * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. */ @Nullable @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (runnable == null) return null; if (runnable instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlRunnable) runnable; else throw new IllegalStateException("Already TtlRunnable!"); } return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @NonNull public static List gets(@Nullable Collection tasks) { return gets(tasks, false, false); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already. */ @NonNull public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun) { return gets(tasks, releaseTtlValueReferenceAfterRun, false); } /** * wrap input {@link Runnable} Collection to {@link TtlRunnable} Collection. * * @param tasks task to be wrapped. if input is {@code null}, return {@code null}. * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlRunnable} is referred. * @param idempotent is idempotent mode or not. if {@code true}, just return input {@link Runnable} when it's {@link TtlRunnable}, * otherwise throw {@link IllegalStateException}. * Caution: {@code true} will cover up bugs! DO NOT set, only when you know why. * @return wrapped tasks * @throws IllegalStateException when input is {@link TtlRunnable} already and not idempotent. */ @NonNull public static List gets(@Nullable Collection tasks, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (tasks == null) return Collections.emptyList(); List copy = new ArrayList<>(); for (Runnable task : tasks) { copy.add(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent)); } return copy; } /** * Unwrap {@link TtlRunnable} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code Runnable} parameter is {@code null}, return {@code null}; * if input {@code Runnable} parameter is not a {@link TtlRunnable} just return input {@code Runnable}. *

* so {@code TtlRunnable.unwrap(TtlRunnable.get(runnable))} will always return the same input {@code runnable} object. * * @see #get(Runnable) * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @since 2.10.2 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Runnable unwrap(@Nullable Runnable runnable) { if (!(runnable instanceof TtlRunnable)) return runnable; else return ((TtlRunnable) runnable).getRunnable(); } /** * Unwrap {@link TtlRunnable} to the original/underneath one for collection. *

* Invoke {@link #unwrap(Runnable)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code Runnable} parameter collection is {@code null}, return a empty list. * * @see #gets(Collection) * @see #unwrap(Runnable) * @since 2.10.2 */ @NonNull public static List unwraps(@Nullable Collection tasks) { if (tasks == null) return Collections.emptyList(); List copy = new ArrayList<>(); for (Runnable task : tasks) { if (!(task instanceof TtlRunnable)) copy.add(task); else copy.add(((TtlRunnable) task).getRunnable()); } return copy; } private final TtlAttachmentsDelegate ttlAttachment = new TtlAttachmentsDelegate(); /** * see {@link TtlAttachments#setTtlAttachment(String, Object)} * * @since 2.11.0 */ @Override public void setTtlAttachment(@NonNull String key, Object value) { ttlAttachment.setTtlAttachment(key, value); } /** * see {@link TtlAttachments#getTtlAttachment(String)} * * @since 2.11.0 */ @Override public T getTtlAttachment(@NonNull String key) { return ttlAttachment.getTtlAttachment(key); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlTimerTask.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import org.jetbrains.annotations.Contract; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * {@link TtlTimerTask} decorate {@link TimerTask} to get {@link TransmittableThreadLocal} value * and transmit it to the time of {@link TtlTimerTask} execution, needed when use {@link TtlTimerTask} to {@link java.util.TimerTask}. *

* Use factory method {@link #get(TimerTask)} to create instance. *

* NOTE: * The {@link TtlTimerTask} make the method {@link TimerTask#scheduledExecutionTime()} in * the origin {@link TimerTask} lose effectiveness! Use {@link com.alibaba.ttl.threadpool.agent.TtlAgent} instead. * * @author Jerry Lee (oldratlee at gmail dot com) * @see java.util.Timer * @see TimerTask * @see Alibaba Java Coding Guidelines - Concurrency - Item 10: [Mandatory] Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. * @see com.alibaba.ttl.threadpool.agent.TtlAgent * @since 0.9.1 * @deprecated Use {@link TtlRunnable}, {@link java.util.concurrent.ScheduledExecutorService} instead of {@link java.util.Timer}, {@link java.util.TimerTask}. */ @Deprecated public final class TtlTimerTask extends TimerTask implements TtlWrapper, TtlEnhanced { private final AtomicReference capturedRef; private final TimerTask timerTask; private final boolean releaseTtlValueReferenceAfterRun; private TtlTimerTask(@NonNull TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { this.capturedRef = new AtomicReference<>(capture()); this.timerTask = timerTask; this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } /** * wrap method {@link TimerTask#run()}. */ @Override public void run() { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Object backup = replay(captured); try { timerTask.run(); } finally { restore(backup); } } @Override public boolean cancel() { timerTask.cancel(); return super.cancel(); } /** * return original/unwrapped {@link TimerTask}. */ @NonNull public TimerTask getTimerTask() { return unwrap(); } /** * unwrap to original/unwrapped {@link TimerTask}. * * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 */ @NonNull @Override public TimerTask unwrap() { return timerTask; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlTimerTask that = (TtlTimerTask) o; return timerTask.equals(that.timerTask); } @Override public int hashCode() { return timerTask != null ? timerTask.hashCode() : 0; } @Override public String toString() { return this.getClass().getName() + " - " + timerTask.toString(); } /** * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. *

* This method is idempotent. * * @param timerTask input {@link TimerTask} * @return Wrapped {@link TimerTask} */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TtlTimerTask get(@Nullable TimerTask timerTask) { return get(timerTask, false, false); } /** * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. *

* This method is idempotent. * * @param timerTask input {@link TimerTask} * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlTimerTask} is referred. * @return Wrapped {@link TimerTask} */ @Nullable @Contract(value = "null, _ -> null; !null, _ -> !null", pure = true) public static TtlTimerTask get(@Nullable TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun) { return get(timerTask, releaseTtlValueReferenceAfterRun, false); } /** * Factory method, wrap input {@link TimerTask} to {@link TtlTimerTask}. *

* This method is idempotent. * * @param timerTask input {@link TimerTask} * @param releaseTtlValueReferenceAfterRun release TTL value reference after run, avoid memory leak even if {@link TtlTimerTask} is referred. * @param idempotent is idempotent or not. {@code true} will cover up bugs! DO NOT set, only when you know why. * @return Wrapped {@link TimerTask} */ @Nullable @Contract(value = "null, _, _ -> null; !null, _, _ -> !null", pure = true) public static TtlTimerTask get(@Nullable TimerTask timerTask, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (timerTask == null) return null; if (timerTask instanceof TtlEnhanced) { // avoid redundant decoration, and ensure idempotency if (idempotent) return (TtlTimerTask) timerTask; else throw new IllegalStateException("Already TtlTimerTask!"); } return new TtlTimerTask(timerTask, releaseTtlValueReferenceAfterRun); } /** * Unwrap {@link TtlTimerTask} to the original/underneath one. *

* this method is {@code null}-safe, when input {@code TimerTask} parameter is {@code null}, return {@code null}; * if input {@code TimerTask} parameter is not a {@link TtlTimerTask} just return input {@code TimerTask}. * * @see #get(TimerTask) * @since 2.10.2 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static TimerTask unwrap(@Nullable TimerTask timerTask) { if (!(timerTask instanceof TtlTimerTask)) return timerTask; else return ((TtlTimerTask) timerTask).getTimerTask(); } /** * Unwrap {@link TtlTimerTask} to the original/underneath one. *

* Invoke {@link #unwrap(TimerTask)} for each element in input collection. *

* This method is {@code null}-safe, when input {@code TimerTask} parameter is {@code null}, return a empty list. * * @see #unwrap(TimerTask) * @since 2.10.2 */ @NonNull public static List unwraps(@Nullable Collection tasks) { if (tasks == null) return Collections.emptyList(); List copy = new ArrayList<>(); for (TimerTask task : tasks) { if (!(task instanceof TtlTimerTask)) copy.add(task); else copy.add(((TtlTimerTask) task).getTimerTask()); } return copy; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlUnwrap.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; /** * Util methods for TTL Wrapper: unwrap TTL Wrapper and check TTL Wrapper. *

* Note:
* all methods are {@code null}-safe, when input parameter is {@code null}, return {@code null}. *

* Implementation Note:
* The util methods in this class should have been inside {@link TtlWrappers}.
* But for {@code Java 6} support, it's required splitting the util methods * which involved {@code Java 8} from {@link TtlWrappers}. * In order to avoid loading {@code Java 8} class (eg: {@link java.util.function.Consumer}, {@link java.util.function.Supplier}), * when invoking any methods of {@link TtlWrappers}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlRunnable * @see TtlCallable * @see com.alibaba.ttl.threadpool.TtlExecutors * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper * @see TtlWrappers * @since 2.11.4 */ public final class TtlUnwrap { /** * Generic unwrap method, unwrap {@link TtlWrapper} to the original/underneath one. *

* this method is {@code null}-safe, when input parameter is {@code null}, return {@code null}; * if input parameter is not a {@link TtlWrapper} just return input. * * @see TtlRunnable#unwrap(Runnable) * @see TtlCallable#unwrap(java.util.concurrent.Callable) * @see com.alibaba.ttl.threadpool.TtlExecutors#unwrap(java.util.concurrent.Executor) * @see com.alibaba.ttl.threadpool.TtlExecutors#unwrap(java.util.concurrent.ThreadFactory) * @see com.alibaba.ttl.threadpool.TtlExecutors#unwrap(java.util.Comparator) * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#unwrap(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) * @see TtlWrappers#wrapSupplier(java.util.function.Supplier) * @see TtlWrappers#wrapConsumer(java.util.function.Consumer) * @see TtlWrappers#wrapBiConsumer(java.util.function.BiConsumer) * @see TtlWrappers#wrapFunction(java.util.function.Function) * @see TtlWrappers#wrapBiFunction(java.util.function.BiFunction) * @see #isWrapper(Object) * @since 2.11.4 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) @SuppressWarnings("unchecked") public static T unwrap(@Nullable T obj) { if (!isWrapper(obj)) return obj; else return ((TtlWrapper) obj).unwrap(); } /** * check the input object is a {@code TtlWrapper} or not. * * @see #unwrap(Object) * @since 2.11.4 */ public static boolean isWrapper(@Nullable T obj) { return obj instanceof TtlWrapper; } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlUnwrap() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/TtlWrappers.java ================================================ package com.alibaba.ttl; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.function.*; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.*; /** * Util methods for TTL Wrapper: wrap common {@code Functional Interface}. *

* Note: *

    *
  • all methods is {@code null}-safe, when input parameter is {@code null}, return {@code null}.
  • *
  • all wrap method skip wrap (aka. just return input parameter), when input parameter is already wrapped.
  • *
* * @author Jerry Lee (oldratlee at gmail dot com) * @author huangfei1101 (fei.hf at alibaba-inc dot com) * @see TtlRunnable * @see TtlCallable * @see TtlUnwrap * @see TtlWrapper * @since 2.11.4 */ public final class TtlWrappers { /** * wrap {@link Supplier} to TTL wrapper. * * @param supplier input {@link Supplier} * @return Wrapped {@link Supplier} * @see TtlUnwrap#unwrap(Object) * @since 2.12.4 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Supplier wrapSupplier(@Nullable Supplier supplier) { if (supplier == null) return null; else if (supplier instanceof TtlEnhanced) return supplier; else return new TtlSupplier<>(supplier); } /** * wrap {@link Supplier} to TTL wrapper. * * @param supplier input {@link Supplier} * @return Wrapped {@link Supplier} * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 * @deprecated overload methods using the same name {@code wrap} is not readable * and have the type inference problems in some case; * so use {@link TtlWrappers#wrapSupplier(Supplier)} instead. */ @Deprecated @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Supplier wrap(@Nullable Supplier supplier) { return wrapSupplier(supplier); } private static class TtlSupplier implements Supplier, TtlWrapper>, TtlEnhanced { final Supplier supplier; final Object captured; TtlSupplier(@NonNull Supplier supplier) { this.supplier = supplier; this.captured = capture(); } @Override public T get() { final Object backup = replay(captured); try { return supplier.get(); } finally { restore(backup); } } @NonNull @Override public Supplier unwrap() { return supplier; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlSupplier that = (TtlSupplier) o; return supplier.equals(that.supplier); } @Override public int hashCode() { return supplier.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + supplier.toString(); } } /** * wrap {@link Consumer} to TTL wrapper. * * @param consumer input {@link Consumer} * @return Wrapped {@link Consumer} * @see TtlUnwrap#unwrap(Object) * @since 2.12.4 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Consumer wrapConsumer(@Nullable Consumer consumer) { if (consumer == null) return null; else if (consumer instanceof TtlEnhanced) return consumer; else return new TtlConsumer<>(consumer); } /** * wrap {@link Consumer} to TTL wrapper. * * @param consumer input {@link Consumer} * @return Wrapped {@link Consumer} * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 * @deprecated overload methods using the same name {@code wrap} is not readable * and have the type inference problems in some case; * so use {@link TtlWrappers#wrapConsumer(java.util.function.Consumer)} instead. */ @Deprecated @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Consumer wrap(@Nullable Consumer consumer) { return wrapConsumer(consumer); } private static class TtlConsumer implements Consumer, TtlWrapper>, TtlEnhanced { final Consumer consumer; final Object captured; TtlConsumer(@NonNull Consumer consumer) { this.consumer = consumer; this.captured = capture(); } @Override public void accept(T t) { final Object backup = replay(captured); try { consumer.accept(t); } finally { restore(backup); } } @NonNull @Override public Consumer unwrap() { return consumer; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlConsumer that = (TtlConsumer) o; return consumer.equals(that.consumer); } @Override public int hashCode() { return consumer.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + consumer.toString(); } } /** * wrap {@link BiConsumer} to TTL wrapper. * * @param consumer input {@link BiConsumer} * @return Wrapped {@link BiConsumer} * @see TtlUnwrap#unwrap(Object) * @since 2.12.4 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static BiConsumer wrapBiConsumer(@Nullable BiConsumer consumer) { if (consumer == null) return null; else if (consumer instanceof TtlEnhanced) return consumer; else return new TtlBiConsumer<>(consumer); } /** * wrap input {@link BiConsumer} to TTL wrapper. * * @param consumer input {@link BiConsumer} * @return Wrapped {@link BiConsumer} * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 * @deprecated overload methods using the same name {@code wrap} is not readable * and have the type inference problems in some case; * so use {@link TtlWrappers#wrapBiConsumer(BiConsumer)} instead. */ @Deprecated @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static BiConsumer wrap(@Nullable BiConsumer consumer) { return wrapBiConsumer(consumer); } private static class TtlBiConsumer implements BiConsumer, TtlWrapper>, TtlEnhanced { final BiConsumer consumer; final Object captured; TtlBiConsumer(@NonNull BiConsumer consumer) { this.consumer = consumer; this.captured = capture(); } @Override public void accept(T t, U u) { final Object backup = replay(captured); try { consumer.accept(t, u); } finally { restore(backup); } } @NonNull @Override public BiConsumer unwrap() { return consumer; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlBiConsumer that = (TtlBiConsumer) o; return consumer.equals(that.consumer); } @Override public int hashCode() { return consumer.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + consumer.toString(); } } /** * wrap {@link Function} to TTL wrapper. * * @param fn input {@link Function} * @return Wrapped {@link Function} * @see TtlUnwrap#unwrap(Object) * @since 2.12.4 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Function wrapFunction(@Nullable Function fn) { if (fn == null) return null; else if (fn instanceof TtlEnhanced) return fn; else return new TtlFunction<>(fn); } /** * wrap {@link Function} to TTL wrapper. * * @param fn input {@link Function} * @return Wrapped {@link Function} * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 * @deprecated overload methods using the same name {@code wrap} is not readable * and have the type inference problems in some case; * so use {@link TtlWrappers#wrapFunction(Function)} instead. */ @Deprecated @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Function wrap(@Nullable Function fn) { return wrapFunction(fn); } private static class TtlFunction implements Function, TtlWrapper>, TtlEnhanced { final Function fn; final Object captured; TtlFunction(@NonNull Function fn) { this.fn = fn; this.captured = capture(); } @Override public R apply(T t) { final Object backup = replay(captured); try { return fn.apply(t); } finally { restore(backup); } } @NonNull @Override public Function unwrap() { return fn; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlFunction that = (TtlFunction) o; return fn.equals(that.fn); } @Override public int hashCode() { return fn.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + fn.toString(); } } /** * wrap {@link BiFunction} to TTL wrapper. * * @param fn input {@link BiFunction} * @return Wrapped {@link BiFunction} * @see TtlUnwrap#unwrap(Object) * @since 2.12.4 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static BiFunction wrapBiFunction(@Nullable BiFunction fn) { if (fn == null) return null; else if (fn instanceof TtlEnhanced) return fn; else return new TtlBiFunction<>(fn); } /** * wrap {@link BiFunction} to TTL wrapper. * * @param fn input {@link BiFunction} * @return Wrapped {@link BiFunction} * @see TtlUnwrap#unwrap(Object) * @since 2.11.4 * @deprecated overload methods using the same name {@code wrap} is not readable * and have the type inference problems in some case; * so use {@link TtlWrappers#wrapBiFunction(BiFunction)} instead. */ @Deprecated @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static BiFunction wrap(@Nullable BiFunction fn) { return wrapBiFunction(fn); } private static class TtlBiFunction implements BiFunction, TtlWrapper>, TtlEnhanced { final BiFunction fn; final Object captured; TtlBiFunction(@NonNull BiFunction fn) { this.fn = fn; this.captured = capture(); } @Override public R apply(T t, U u) { final Object backup = replay(captured); try { return fn.apply(t, u); } finally { restore(backup); } } @NonNull @Override public BiFunction unwrap() { return fn; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlBiFunction that = (TtlBiFunction) o; return fn.equals(that.fn); } @Override public int hashCode() { return fn.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + fn.toString(); } } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlWrappers() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/package-info.java ================================================ /** * TTL API. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.TransmittableThreadLocal * @see com.alibaba.ttl.TtlRunnable * @see com.alibaba.ttl.TtlCallable * @see com.alibaba.ttl.TtlUnwrap * @see com.alibaba.ttl.TtlWrappers */ package com.alibaba.ttl; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/spi/TtlAttachments.java ================================================ package com.alibaba.ttl.spi; import edu.umd.cs.findbugs.annotations.NonNull; /** * The TTL attachments for TTL tasks, * eg: {@link com.alibaba.ttl.TtlRunnable}, {@link com.alibaba.ttl.TtlCallable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 2.11.0 */ public interface TtlAttachments extends TtlEnhanced { /** * set the TTL attachments for TTL tasks * * @param key attachment key * @param value attachment value * @since 2.11.0 */ void setTtlAttachment(@NonNull String key, Object value); /** * get the TTL attachment for TTL tasks * * @param key attachment key * @since 2.11.0 */ T getTtlAttachment(@NonNull String key); /** * The attachment key of TTL task, weather this task is a auto wrapper task. *

* so the value of this attachment is a {@code boolean}. * * @since 2.11.0 */ String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper"; } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/spi/TtlAttachmentsDelegate.java ================================================ package com.alibaba.ttl.spi; import com.alibaba.ttl.TtlUnwrap; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * {@link TtlAttachments} delegate/implementation. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.TtlRunnable * @see com.alibaba.ttl.TtlCallable * @since 2.11.0 */ public class TtlAttachmentsDelegate implements TtlAttachments { private final ConcurrentMap attachments = new ConcurrentHashMap<>(); @Override public void setTtlAttachment(@NonNull String key, Object value) { attachments.put(key, value); } @Override @SuppressWarnings("unchecked") public T getTtlAttachment(@NonNull String key) { return (T) attachments.get(key); } // ======== AutoWrapper Util Methods ======== /** * @see TtlAttachments#KEY_IS_AUTO_WRAPPER * @since 3.0.0 */ public static boolean isAutoWrapper(@Nullable Object ttlAttachments) { if (!(ttlAttachments instanceof TtlAttachments)) return false; final Boolean value = ((TtlAttachments) ttlAttachments).getTtlAttachment(KEY_IS_AUTO_WRAPPER); if (value == null) return false; return value; } /** * @see TtlAttachments#KEY_IS_AUTO_WRAPPER * @since 3.0.0 */ public static void setAutoWrapperAttachment(@Nullable Object ttlAttachment) { if (!(ttlAttachment instanceof TtlAttachments)) return; ((TtlAttachments) ttlAttachment).setTtlAttachment(TtlAttachments.KEY_IS_AUTO_WRAPPER, true); } /** * @see TtlAttachments#KEY_IS_AUTO_WRAPPER * @since 3.0.0 */ @Nullable public static T unwrapIfIsAutoWrapper(@Nullable T obj) { if (isAutoWrapper(obj)) return TtlUnwrap.unwrap(obj); else return obj; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/spi/TtlEnhanced.java ================================================ package com.alibaba.ttl.spi; /** * a Ttl marker/tag interface, for ttl enhanced class, for example {@code TTL wrapper} * like {@link com.alibaba.ttl.TtlRunnable}, {@link com.alibaba.ttl.TtlCallable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.TtlRunnable * @see com.alibaba.ttl.TtlCallable * @see com.alibaba.ttl.TtlRecursiveAction * @see com.alibaba.ttl.TtlRecursiveTask * @see TtlAttachments * @since 2.11.0 */ public interface TtlEnhanced { } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/spi/TtlWrapper.java ================================================ package com.alibaba.ttl.spi; import com.alibaba.ttl.TtlUnwrap; import edu.umd.cs.findbugs.annotations.NonNull; /** * Ttl Wrapper interface. *

* Used to mark wrapper types, for example: *

    *
  • {@link com.alibaba.ttl.TtlCallable}
  • *
  • {@link com.alibaba.ttl.threadpool.TtlExecutors}
  • *
  • {@link com.alibaba.ttl.threadpool.DisableInheritableThreadFactory}
  • *
* * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlUnwrap#unwrap * @see com.alibaba.ttl.TtlCallable * @see com.alibaba.ttl.TtlRunnable * @see com.alibaba.ttl.threadpool.TtlExecutors * @see com.alibaba.ttl.threadpool.DisableInheritableThreadFactory * @see com.alibaba.ttl.threadpool.DisableInheritableForkJoinWorkerThreadFactory * @since 2.11.4 */ public interface TtlWrapper extends TtlEnhanced { /** * unwrap {@link TtlWrapper} to the original/underneath one. * * @see TtlUnwrap#unwrap(Object) */ @NonNull T unwrap(); } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/spi/package-info.java ================================================ /** * TTL SPI * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.spi.TtlEnhanced * @see com.alibaba.ttl.spi.TtlAttachments * @since 2.11.0 */ package com.alibaba.ttl.spi; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/ComparableComparator.java ================================================ /* * 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. */ package com.alibaba.ttl.threadpool; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This source code file is copied and small adopted from commons-collections4 4.4: // // https://github.com/apache/commons-collections/blob/commons-commons-collections-4.4/src/main/java/org/apache/commons/collections4/comparators/ComparableComparator.java // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// import java.io.Serializable; import java.util.Comparator; /** * A {@link Comparator Comparator} that compares {@link Comparable Comparable} * objects. *

* This Comparator is useful, for example, for enforcing the natural order in * custom implementations of {@link java.util.SortedSet SortedSet} and * {@link java.util.SortedMap SortedMap}. *

*

* Note: In the 2.0 and 2.1 releases of Commons Collections, this class would * throw a {@link ClassCastException} if either of the arguments to * {@link #compare compare} were null, not * {@link Comparable Comparable}, or for which * {@link Comparable#compareTo(Object) compareTo} gave inconsistent results. * This is no longer the case. See {@link #compare compare} for * details. *

* * @param the type of objects compared by this comparator * * @since 2.0 * @see java.util.Collections#reverseOrder() */ class ComparableComparator> implements Comparator, Serializable { /** Serialization version. */ private static final long serialVersionUID=-291439688585137865L; /** The singleton instance. */ @SuppressWarnings("rawtypes") public static final ComparableComparator INSTANCE = new ComparableComparator(); //----------------------------------------------------------------------- /** * Gets the singleton instance of a ComparableComparator. *

* Developers are encouraged to use the comparator returned from this method * instead of constructing a new instance to reduce allocation and GC overhead * when multiple comparable comparators may be used in the same VM. * * @param the element type * @return the singleton ComparableComparator * @since 4.0 */ @SuppressWarnings("unchecked") public static > ComparableComparator comparableComparator() { return INSTANCE; } //----------------------------------------------------------------------- /** * Constructor whose use should be avoided. *

* Please use the {@link #comparableComparator()} method whenever possible. */ public ComparableComparator() { super(); } //----------------------------------------------------------------------- /** * Compare the two {@link Comparable Comparable} arguments. * This method is equivalent to: *

((Comparable)obj1).compareTo(obj2)
* * @param obj1 the first object to compare * @param obj2 the second object to compare * @return negative if obj1 is less, positive if greater, zero if equal * @throws NullPointerException if obj1 is null, * or when ((Comparable)obj1).compareTo(obj2) does * @throws ClassCastException if obj1 is not a Comparable, * or when ((Comparable)obj1).compareTo(obj2) does */ @Override public int compare(final E obj1, final E obj2) { return obj1.compareTo(obj2); } //----------------------------------------------------------------------- /** * Implement a hash code for this comparator that is consistent with * {@link #equals(Object) equals}. * * @return a hash code for this comparator. * @since 3.0 */ @Override public int hashCode() { return "ComparableComparator".hashCode(); } /** * Returns {@code true} iff that Object is a {@link Comparator Comparator} * whose ordering is known to be equivalent to mine. *

* This implementation returns {@code true} iff * object.{@link Object#getClass() getClass()} equals * this.getClass(). Subclasses may want to override this behavior to remain * consistent with the {@link Comparator#equals(Object)} contract. * * @param object the object to compare with * @return {@code true} if equal * @since 3.0 */ @Override public boolean equals(final Object object) { return this == object || null != object && object.getClass().equals(this.getClass()); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactory.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; /** * Disable inheritable {@link ForkJoinWorkerThreadFactory}. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 2.10.1 */ public interface DisableInheritableForkJoinWorkerThreadFactory extends ForkJoinWorkerThreadFactory, TtlWrapper { /** * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. */ @NonNull @Override ForkJoinWorkerThreadFactory unwrap(); } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/DisableInheritableForkJoinWorkerThreadFactoryWrapper.java ================================================ package com.alibaba.ttl.threadpool; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; import java.util.concurrent.ForkJoinWorkerThread; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.clear; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore; /** * @author Jerry Lee (oldratlee at gmail dot com) * @since 2.10.1 */ class DisableInheritableForkJoinWorkerThreadFactoryWrapper implements DisableInheritableForkJoinWorkerThreadFactory { private final ForkJoinWorkerThreadFactory threadFactory; DisableInheritableForkJoinWorkerThreadFactoryWrapper(@NonNull ForkJoinWorkerThreadFactory threadFactory) { this.threadFactory = threadFactory; } @Override public ForkJoinWorkerThread newThread(ForkJoinPool pool) { final Object backup = clear(); try { return threadFactory.newThread(pool); } finally { restore(backup); } } @Override @NonNull public ForkJoinWorkerThreadFactory unwrap() { return threadFactory; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DisableInheritableForkJoinWorkerThreadFactoryWrapper that = (DisableInheritableForkJoinWorkerThreadFactoryWrapper) o; return threadFactory.equals(that.threadFactory); } @Override public int hashCode() { return threadFactory.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + threadFactory.toString(); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/DisableInheritableThreadFactory.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ThreadFactory; /** * Disable inheritable {@link ThreadFactory}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see ThreadFactory * @since 2.10.0 */ public interface DisableInheritableThreadFactory extends ThreadFactory, TtlWrapper { /** * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. */ @NonNull @Override ThreadFactory unwrap(); } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/DisableInheritableThreadFactoryWrapper.java ================================================ package com.alibaba.ttl.threadpool; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ThreadFactory; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.clear; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore; /** * @author Jerry Lee (oldratlee at gmail dot com) * @since 2.10.0 */ class DisableInheritableThreadFactoryWrapper implements DisableInheritableThreadFactory { private final ThreadFactory threadFactory; DisableInheritableThreadFactoryWrapper(@NonNull ThreadFactory threadFactory) { this.threadFactory = threadFactory; } @Override public Thread newThread(@NonNull Runnable r) { final Object backup = clear(); try { return threadFactory.newThread(r); } finally { restore(backup); } } @NonNull @Override public ThreadFactory unwrap() { return threadFactory; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DisableInheritableThreadFactoryWrapper that = (DisableInheritableThreadFactoryWrapper) o; return threadFactory.equals(that.threadFactory); } @Override public int hashCode() { return threadFactory.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + threadFactory.toString(); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/ExecutorServiceTtlWrapper.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TtlCallable; import com.alibaba.ttl.TtlRunnable; import com.alibaba.ttl.spi.TtlEnhanced; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Collection; import java.util.List; import java.util.concurrent.*; /** * {@link TransmittableThreadLocal} Wrapper of {@link ExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} * to the execution time of {@link Runnable} or {@link Callable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 0.9.0 */ @SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"}) class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced { private final ExecutorService executorService; ExecutorServiceTtlWrapper(@NonNull ExecutorService executorService, boolean idempotent) { super(executorService, idempotent); this.executorService = executorService; } @Override public void shutdown() { executorService.shutdown(); } @NonNull @Override public List shutdownNow() { return executorService.shutdownNow(); } @Override public boolean isShutdown() { return executorService.isShutdown(); } @Override public boolean isTerminated() { return executorService.isTerminated(); } @Override public boolean awaitTermination(long timeout, @NonNull TimeUnit unit) throws InterruptedException { return executorService.awaitTermination(timeout, unit); } @NonNull @Override public Future submit(@NonNull Callable task) { return executorService.submit(TtlCallable.get(task, false, idempotent)); } @NonNull @Override public Future submit(@NonNull Runnable task, T result) { return executorService.submit(TtlRunnable.get(task, false, idempotent), result); } @NonNull @Override public Future submit(@NonNull Runnable task) { return executorService.submit(TtlRunnable.get(task, false, idempotent)); } @NonNull @Override public List> invokeAll(@NonNull Collection> tasks) throws InterruptedException { return executorService.invokeAll(TtlCallable.gets(tasks, false, idempotent)); } @NonNull @Override public List> invokeAll(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException { return executorService.invokeAll(TtlCallable.gets(tasks, false, idempotent), timeout, unit); } @NonNull @Override public T invokeAny(@NonNull Collection> tasks) throws InterruptedException, ExecutionException { return executorService.invokeAny(TtlCallable.gets(tasks, false, idempotent)); } @Override public T invokeAny(@NonNull Collection> tasks, long timeout, @NonNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return executorService.invokeAny(TtlCallable.gets(tasks, false, idempotent), timeout, unit); } @NonNull @Override public ExecutorService unwrap() { return executorService; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/ExecutorTtlWrapper.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TtlRunnable; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.Executor; /** * {@link TransmittableThreadLocal} Wrapper of {@link Executor}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} * to the execution time of {@link Runnable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 0.9.0 */ class ExecutorTtlWrapper implements Executor, TtlWrapper, TtlEnhanced { private final Executor executor; protected final boolean idempotent; ExecutorTtlWrapper(@NonNull Executor executor, boolean idempotent) { this.executor = executor; this.idempotent = idempotent; } @Override public void execute(@NonNull Runnable command) { executor.execute(TtlRunnable.get(command, false, idempotent)); } @NonNull @Override public Executor unwrap() { return executor; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ExecutorTtlWrapper that = (ExecutorTtlWrapper) o; return executor.equals(that.executor); } @Override public int hashCode() { return executor.hashCode(); } @Override public String toString() { return this.getClass().getName() + " - " + executor; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/ScheduledExecutorServiceTtlWrapper.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.TtlCallable; import com.alibaba.ttl.TtlRunnable; import com.alibaba.ttl.spi.TtlEnhanced; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** * {@link TransmittableThreadLocal} Wrapper of {@link ScheduledExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link Callable} * to the execution time of {@link Runnable} or {@link Callable}. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 0.9.0 */ @SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"}) class ScheduledExecutorServiceTtlWrapper extends ExecutorServiceTtlWrapper implements ScheduledExecutorService, TtlEnhanced { final ScheduledExecutorService scheduledExecutorService; public ScheduledExecutorServiceTtlWrapper(@NonNull ScheduledExecutorService scheduledExecutorService, boolean idempotent) { super(scheduledExecutorService, idempotent); this.scheduledExecutorService = scheduledExecutorService; } @NonNull @Override public ScheduledFuture schedule(@NonNull Runnable command, long delay, @NonNull TimeUnit unit) { return scheduledExecutorService.schedule(TtlRunnable.get(command, false, idempotent), delay, unit); } @NonNull @Override public ScheduledFuture schedule(@NonNull Callable callable, long delay, @NonNull TimeUnit unit) { return scheduledExecutorService.schedule(TtlCallable.get(callable, false, idempotent), delay, unit); } @NonNull @Override public ScheduledFuture scheduleAtFixedRate(@NonNull Runnable command, long initialDelay, long period, @NonNull TimeUnit unit) { return scheduledExecutorService.scheduleAtFixedRate(TtlRunnable.get(command, false, idempotent), initialDelay, period, unit); } @NonNull @Override public ScheduledFuture scheduleWithFixedDelay(@NonNull Runnable command, long initialDelay, long delay, @NonNull TimeUnit unit) { return scheduledExecutorService.scheduleWithFixedDelay(TtlRunnable.get(command, false, idempotent), initialDelay, delay, unit); } @NonNull @Override public ScheduledExecutorService unwrap() { return scheduledExecutorService; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/TtlExecutors.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.TransmittableThreadLocal; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.spi.TtlWrapper; import com.alibaba.ttl.threadpool.agent.TtlAgent; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.Comparator; import java.util.concurrent.*; /** * Util methods for TTL wrapper of jdk executors. * *

    *
  1. wrap/check/unwrap methods for TTL wrapper of jdk executors({@link Executor}, {@link ExecutorService}, {@link ScheduledExecutorService}).
  2. *
  3. wrap/check/unwrap methods for disable Inheritable wrapper of {@link ThreadFactory}.
  4. *
  5. wrap/check/unwrap methods for {@code TtlRunnableUnwrapComparator} wrapper of {@link PriorityBlockingQueue}.
  6. *
*

* Note: *

    *
  • all method is {@code null}-safe. * for wrap/unwrap methods when input parameter is {@code null}, return {@code null}. * for check methods when input parameter is {@code null}, return {@code false}.
  • *
  • skip wrap/decoration thread pool/{@code executor}(aka. just return input {@code executor}) * when ttl agent is loaded, Or when input {@code executor} is already wrapped/decorated.
  • *
* * @author Jerry Lee (oldratlee at gmail dot com) * @see Executor * @see ExecutorService * @see ScheduledExecutorService * @see ThreadPoolExecutor * @see ScheduledThreadPoolExecutor * @see Executors * @see java.util.concurrent.CompletionService * @see java.util.concurrent.ExecutorCompletionService * @see ThreadFactory * @see Executors#defaultThreadFactory() * @see PriorityBlockingQueue * @see TtlForkJoinPoolHelper * @since 0.9.0 */ public final class TtlExecutors { /** * {@link TransmittableThreadLocal} Wrapper of {@link Executor}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} * to the execution time of {@link Runnable}. *

* NOTE: sine v2.12.0 the idempotency of return wrapped Executor is changed to true, * so the wrapped Executor can be cooperated with the usage of "Decorate Runnable and Callable". *

* About idempotency: if is idempotent, * it's allowed to submit the {@link com.alibaba.ttl.TtlRunnable}/{@link com.alibaba.ttl.TtlCallable} to the wrapped Executor; * otherwise throw {@link IllegalStateException}. * * @param executor input Executor * @return wrapped Executor * @see com.alibaba.ttl.TtlRunnable#get(Runnable, boolean, boolean) * @see com.alibaba.ttl.TtlCallable#get(Callable, boolean, boolean) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Executor getTtlExecutor(@Nullable Executor executor) { if (TtlAgent.isTtlAgentLoaded() || executor == null || executor instanceof TtlEnhanced) { return executor; } return new ExecutorTtlWrapper(executor, true); } /** * {@link TransmittableThreadLocal} Wrapper of {@link ExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link java.util.concurrent.Callable} * to the execution time of {@link Runnable} or {@link java.util.concurrent.Callable}. *

* NOTE: sine v2.12.0 the idempotency of return wrapped ExecutorService is changed to true, * so the wrapped ExecutorService can be cooperated with the usage of "Decorate Runnable and Callable". *

* About idempotency: if is idempotent, * it's allowed to submit the {@link com.alibaba.ttl.TtlRunnable}/{@link com.alibaba.ttl.TtlCallable} to the wrapped ExecutorService; * otherwise throw {@link IllegalStateException}. * * @param executorService input ExecutorService * @return wrapped ExecutorService * @see com.alibaba.ttl.TtlRunnable#get(Runnable, boolean, boolean) * @see com.alibaba.ttl.TtlCallable#get(Callable, boolean, boolean) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) { if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced) { return executorService; } return new ExecutorServiceTtlWrapper(executorService, true); } /** * {@link TransmittableThreadLocal} Wrapper of {@link ScheduledExecutorService}, * transmit the {@link TransmittableThreadLocal} from the task submit time of {@link Runnable} or {@link java.util.concurrent.Callable} * to the execution time of {@link Runnable} or {@link java.util.concurrent.Callable}. *

* NOTE: sine v2.12.0 the idempotency of return wrapped ScheduledExecutorService is changed to true, * so the wrapped ScheduledExecutorService can be cooperated with the usage of "Decorate Runnable and Callable". *

* About idempotency: if is idempotent, * it's allowed to submit the {@link com.alibaba.ttl.TtlRunnable}/{@link com.alibaba.ttl.TtlCallable} to the wrapped ScheduledExecutorService; * otherwise throw {@link IllegalStateException}. * * @param scheduledExecutorService input scheduledExecutorService * @return wrapped scheduledExecutorService * @see com.alibaba.ttl.TtlRunnable#get(Runnable, boolean, boolean) * @see com.alibaba.ttl.TtlCallable#get(Callable, boolean, boolean) */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ScheduledExecutorService getTtlScheduledExecutorService(@Nullable ScheduledExecutorService scheduledExecutorService) { if (TtlAgent.isTtlAgentLoaded() || scheduledExecutorService == null || scheduledExecutorService instanceof TtlEnhanced) { return scheduledExecutorService; } return new ScheduledExecutorServiceTtlWrapper(scheduledExecutorService, true); } /** * check the executor is a TTL executor wrapper or not. *

* if the parameter executor is TTL wrapper, return {@code true}, otherwise {@code false}. *

* NOTE: if input executor is {@code null}, return {@code false}. * * @param executor input executor * @param Executor type * @see #getTtlExecutor(Executor) * @see #getTtlExecutorService(ExecutorService) * @see #getTtlScheduledExecutorService(ScheduledExecutorService) * @see #unwrap(Executor) * @since 2.8.0 */ public static boolean isTtlWrapper(@Nullable T executor) { return executor instanceof TtlWrapper; } /** * Unwrap TTL executor wrapper to the original/underneath one. *

* if the parameter executor is TTL wrapper, return the original/underneath executor; * otherwise, just return the input parameter executor. *

* NOTE: if input executor is {@code null}, return {@code null}. * * @param executor input executor * @param Executor type * @see #getTtlExecutor(Executor) * @see #getTtlExecutorService(ExecutorService) * @see #getTtlScheduledExecutorService(ScheduledExecutorService) * @see #isTtlWrapper(Executor) * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @since 2.8.0 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) @SuppressWarnings("unchecked") public static T unwrap(@Nullable T executor) { if (!isTtlWrapper(executor)) return executor; return (T) ((ExecutorTtlWrapper) executor).unwrap(); } /** * Wrapper of {@link ThreadFactory}, disable inheritable. * * @param threadFactory input thread factory * @see DisableInheritableThreadFactory * @see TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory * @since 2.10.0 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ThreadFactory getDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { if (threadFactory == null || isDisableInheritableThreadFactory(threadFactory)) return threadFactory; return new DisableInheritableThreadFactoryWrapper(threadFactory); } /** * Wrapper of {@link Executors#defaultThreadFactory()}, disable inheritable. * * @see #getDisableInheritableThreadFactory(ThreadFactory) * @see TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @since 2.10.0 */ @NonNull public static ThreadFactory getDefaultDisableInheritableThreadFactory() { return getDisableInheritableThreadFactory(Executors.defaultThreadFactory()); } /** * check the {@link ThreadFactory} is {@link DisableInheritableThreadFactory} or not. * * @see TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory * @see #getDefaultDisableInheritableThreadFactory() * @see DisableInheritableThreadFactory * @since 2.10.0 */ public static boolean isDisableInheritableThreadFactory(@Nullable ThreadFactory threadFactory) { return threadFactory instanceof DisableInheritableThreadFactory; } /** * Unwrap {@link DisableInheritableThreadFactory} to the original/underneath one. * * @see #getDisableInheritableThreadFactory(ThreadFactory) * @see #getDefaultDisableInheritableThreadFactory() * @see #isDisableInheritableThreadFactory(ThreadFactory) * @see TtlForkJoinPoolHelper#unwrap(ForkJoinPool.ForkJoinWorkerThreadFactory) * @see DisableInheritableThreadFactory * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @since 2.10.0 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ThreadFactory unwrap(@Nullable ThreadFactory threadFactory) { if (!isDisableInheritableThreadFactory(threadFactory)) return threadFactory; return ((DisableInheritableThreadFactory) threadFactory).unwrap(); } /** * Wrapper of {@code Comparator} which unwrap {@link com.alibaba.ttl.TtlRunnable} before compare, * aka {@code TtlRunnableUnwrapComparator}. *

* Prepared for {@code comparator} parameter of constructor * {@link PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator)}. *

* {@link PriorityBlockingQueue} can be used by constructor * {@link ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue)}. * * @param comparator input comparator * @return wrapped comparator * @see ThreadPoolExecutor * @see ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue) * @see PriorityBlockingQueue * @see PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator) * @since 2.12.3 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Comparator getTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { if (comparator == null || isTtlRunnableUnwrapComparator(comparator)) return comparator; return new TtlUnwrapComparator<>(comparator); } @SuppressWarnings({"unchecked", "rawtypes"}) private static final Comparator INSTANCE = new TtlUnwrapComparator(ComparableComparator.INSTANCE); /** * {@code TtlRunnableUnwrapComparator} that compares {@link Comparable Comparable} objects. * * @see #getTtlRunnableUnwrapComparator(Comparator) * @since 2.12.3 */ @NonNull @SuppressWarnings("unchecked") public static Comparator getTtlRunnableUnwrapComparatorForComparableRunnable() { return (Comparator) INSTANCE; } /** * check the {@code Comparator} is a wrapper {@code TtlRunnableUnwrapComparator} or not. * * @see #getTtlRunnableUnwrapComparator(Comparator) * @see #getTtlRunnableUnwrapComparatorForComparableRunnable() * @since 2.12.3 */ public static boolean isTtlRunnableUnwrapComparator(@Nullable Comparator comparator) { return comparator instanceof TtlUnwrapComparator; } /** * Unwrap {@code TtlRunnableUnwrapComparator} to the original/underneath {@code Comparator}. * * @see #getTtlRunnableUnwrapComparator(Comparator) * @see #getTtlRunnableUnwrapComparatorForComparableRunnable() * @see #isTtlRunnableUnwrapComparator(Comparator) * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @since 2.12.3 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static Comparator unwrap(@Nullable Comparator comparator) { if (!isTtlRunnableUnwrapComparator(comparator)) return comparator; return ((TtlUnwrapComparator) comparator).unwrap(); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlExecutors() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/TtlForkJoinPoolHelper.java ================================================ package com.alibaba.ttl.threadpool; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.jetbrains.annotations.Contract; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory; import java.util.concurrent.ThreadFactory; /** * Util methods to wrap/check/unwrap methods for disable Inheritable wrapper of {@link ForkJoinWorkerThreadFactory}. *

* Note: *

* all method is {@code null}-safe. * for wrap/unwrap methods when input parameter is {@code null}, return {@code null}. * for check methods when input parameter is {@code null}, return {@code false}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see ForkJoinPool * @see ForkJoinWorkerThreadFactory * @see ForkJoinPool#defaultForkJoinWorkerThreadFactory * @see java.util.stream.Stream * @see TtlExecutors * @since 2.10.1 */ public final class TtlForkJoinPoolHelper { /** * Wrapper of {@link ForkJoinWorkerThreadFactory}, disable inheritable. * * @param threadFactory input thread factory * @see DisableInheritableForkJoinWorkerThreadFactory * @see TtlExecutors#getDisableInheritableThreadFactory(ThreadFactory) * @since 2.10.1 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ForkJoinWorkerThreadFactory getDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { if (threadFactory == null || isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) return threadFactory; return new DisableInheritableForkJoinWorkerThreadFactoryWrapper(threadFactory); } /** * Wrapper of {@link ForkJoinPool#defaultForkJoinWorkerThreadFactory}, disable inheritable. * * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see TtlExecutors#getDefaultDisableInheritableThreadFactory() * @since 2.10.1 */ @NonNull public static ForkJoinWorkerThreadFactory getDefaultDisableInheritableForkJoinWorkerThreadFactory() { return getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinPool.defaultForkJoinWorkerThreadFactory); } /** * check the {@link ForkJoinWorkerThreadFactory} is {@link DisableInheritableForkJoinWorkerThreadFactory} or not. * * @see #getDisableInheritableForkJoinWorkerThreadFactory(ForkJoinWorkerThreadFactory) * @see #getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @see DisableInheritableForkJoinWorkerThreadFactory * @since 2.10.1 */ public static boolean isDisableInheritableForkJoinWorkerThreadFactory(@Nullable ForkJoinWorkerThreadFactory threadFactory) { return threadFactory instanceof DisableInheritableForkJoinWorkerThreadFactory; } /** * Unwrap {@link DisableInheritableForkJoinWorkerThreadFactory} to the original/underneath one. * * @see com.alibaba.ttl.TtlUnwrap#unwrap(Object) * @see DisableInheritableForkJoinWorkerThreadFactory * @see TtlExecutors#unwrap(ThreadFactory) * @since 2.10.1 */ @Nullable @Contract(value = "null -> null; !null -> !null", pure = true) public static ForkJoinWorkerThreadFactory unwrap(@Nullable ForkJoinWorkerThreadFactory threadFactory) { if (!isDisableInheritableForkJoinWorkerThreadFactory(threadFactory)) return threadFactory; return ((DisableInheritableForkJoinWorkerThreadFactory) threadFactory).unwrap(); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlForkJoinPoolHelper() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/TtlUnwrapComparator.java ================================================ package com.alibaba.ttl.threadpool; import com.alibaba.ttl.TtlUnwrap; import com.alibaba.ttl.spi.TtlWrapper; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Comparator; /** * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) * @see TtlExecutors#isTtlRunnableUnwrapComparator(Comparator) * @see TtlExecutors#unwrap(Comparator) * @since 2.12.3 */ final class TtlUnwrapComparator implements Comparator, TtlWrapper> { private final Comparator comparator; public TtlUnwrapComparator(@NonNull Comparator comparator) { this.comparator = comparator; } @Override public int compare(T o1, T o2) { return comparator.compare(TtlUnwrap.unwrap(o1), TtlUnwrap.unwrap(o2)); } @NonNull @Override public Comparator unwrap() { return comparator; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TtlUnwrapComparator that = (TtlUnwrapComparator) o; return comparator.equals(that.comparator); } @Override public int hashCode() { return comparator.hashCode(); } @Override public String toString() { return "TtlUnwrapComparator{comparator=" + comparator + '}'; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/TtlAgent.java ================================================ package com.alibaba.ttl.threadpool.agent; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.internal.ForkJoinTtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.internal.JdkExecutorTtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.internal.TimerTaskTtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.internal.PriorityBlockingQueueTtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * TTL Java Agent. * *

The configuration for TTL agent

*

* Configure TTL agent via {@code -D property}({@link System#getProperties()}) or TTL agent arguments. *

    *
  1. {@code -D property} config format is: {@code -Dkey1=v2 -Dkey2=v2}
  2. *
  3. TTL agent arguments config format is {@code key1:v1,key2:v2}.
    * separate key-value pairs by {@code char ,}, and separate key-value by {@code char :}.
  4. *
* NOTE about the config sources and the precedence:
*
    *
  1. Read {@code -D property}({@link System#getProperties()}) first.
  2. *
  3. if no {@code -D property} configured(including empty property value configured by {@code -Dkey1}/{@code -Dkey1=}), read TTL Agent argument configuration.
  4. *
* Below is available TTL agent configuration keys. * *

Configuration key: Log Type

*

* The log type of TTL Java Agent is configured by key {@code ttl.agent.logger}. Since version {@code 2.6.0}. * *

    *
  • {@code ttl.agent.logger : STDERR}
    * only log to {@code stderr} when error. * This is default, when no/unrecognized configuration for key {@code ttl.agent.logger}.
  • *
  • {@code ttl.agent.logger : STDOUT}
    * Log to {@code stdout}, more info than {@code ttl.agent.logger:STDERR}; This is needed when developing.
  • *
*

* Configuration example: * *

    *
  1. {@code -Dttl.agent.logger=STDOUT}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT}
  4. *
* *

Configuration key: Disable inheritable for thread pool

*

* Enable "disable inheritable" for thread pool, configured by key {@code ttl.agent.disable.inheritable.for.thread.pool}. * When no configuration for this key, default is {@code false}(aka. do NOT disable inheritable). Since version {@code 2.10.1}. * *

    *
  • rewrite the {@link java.util.concurrent.ThreadFactory} constructor parameter * of {@link java.util.concurrent.ThreadPoolExecutor} * to {@link com.alibaba.ttl.threadpool.DisableInheritableThreadFactory} * by util method {@link com.alibaba.ttl.threadpool.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) getDisableInheritableThreadFactory}. *
  • *
  • rewrite the {@link java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory} constructor parameter * of {@link java.util.concurrent.ForkJoinPool} * to {@link com.alibaba.ttl.threadpool.DisableInheritableForkJoinWorkerThreadFactory} * by util method {@link com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) getDisableInheritableForkJoinWorkerThreadFactory}. *
  • *
* More info about "disable inheritable" see {@link com.alibaba.ttl.TransmittableThreadLocal}. *

* Configuration example: * *

    *
  1. {@code -Dttl.agent.disable.inheritable.for.thread.pool=true}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.disable.inheritable.for.thread.pool:true}
  4. *
* *

Configuration key: Enable TimerTask class decoration

*

* Enable TimerTask class decoration is configured by key {@code ttl.agent.enable.timer.task}. * Since version {@code 2.7.0}. *

* When no configuration for this key, default is {@code true}(aka. enabled).
* Note: Since version {@code 2.11.2} the default value is {@code true}(enable TimerTask class decoration); * Before version {@code 2.11.1} default value is {@code false}. *

* Configuration example: * *

    *
  1. {@code -Dttl.agent.enable.timer.task=false}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.enable.timer.task:false}
  4. *
* *

Configuration key: logging the transform class received by TTL Agent

*

* Enable logging the transform class received by TTL Agent by key {@code ttl.agent.log.class.transform}, * default is {@code false}(aka. do NOT logging the transform class received by TTL Agent). * Since version {@code 3.0.0}. *

* Configuration example: * *

    *
  1. {@code -Dttl.agent.log.class.transform=true}
  2. *
  3. {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.log.class.transform:true}
  4. *
* *

Multi key configuration example

*

* For {@code -D property} config, simply specify multiply {@code -D property}, example:
* {@code -Dttl.agent.logger=STDOUT -Dttl.agent.disable.inheritable.for.thread.pool=true} *

* For TTL agent arguments config, example:
* {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT,ttl.agent.disable.inheritable.for.thread.pool:true} * *

About boot classpath for TTL agent

*

* NOTE: Since {@code v2.6.0}, TTL agent jar will auto add self to {@code boot classpath}.
* But you should NOT modify the downloaded TTL jar file name in the maven repo(eg: {@code transmittable-thread-local-2.x.y.jar}).
* if you modified the downloaded TTL agent jar file name(eg: {@code ttl-foo-name-changed.jar}), * you must add TTL agent jar to {@code boot classpath} manually * by java option {@code -Xbootclasspath/a:path/to/ttl-foo-name-changed.jar}. *

* The implementation of auto adding self agent jar to {@code boot classpath} use * the {@code Boot-Class-Path} property of manifest file({@code META-INF/MANIFEST.MF}) in the TTL Java Agent Jar: * *

*
*
Boot-Class-Path
*
* A list of paths to be searched by the bootstrap class loader. Paths represent directories or libraries (commonly referred to as JAR or zip libraries on many platforms). * These paths are searched by the bootstrap class loader after the platform specific mechanisms of locating a class have failed. Paths are searched in the order listed. *
*
*
*

* More info about {@code Boot-Class-Path} see * The mechanism for instrumentation. * * @author Jerry Lee (oldratlee at gmail dot com) * @see Instrumentation * @see The mechanism for instrumentation * @see JAR File Specification - JAR Manifest * @see Working with Manifest Files - The Java™ Tutorials * @see com.alibaba.ttl.TransmittableThreadLocal * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.ForkJoinPool * @see java.util.TimerTask * @since 0.9.0 */ public final class TtlAgent { /** * the TTL agent configuration key: Log Type * * @see TtlAgent * @since 3.0.0 */ public static final String TTL_AGENT_LOGGER_KEY = "ttl.agent.logger"; /** * the TTL agent configuration key: Disable inheritable for thread pool * * @see TtlAgent * @since 3.0.0 */ public static final String TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY = "ttl.agent.disable.inheritable.for.thread.pool"; /** * the TTL agent configuration key: Enable TimerTask class decoration * * @see TtlAgent * @since 3.0.0 */ public static final String TTL_AGENT_ENABLE_TIMER_TASK_KEY = "ttl.agent.enable.timer.task"; /** * the TTL agent configuration key: logging the transform class received by TTL Agent * * @see TtlAgent * @since 3.0.0 */ public static final String TTL_AGENT_LOG_CLASS_TRANSFORM_KEY = "ttl.agent.log.class.transform"; // ======== TTL Agent internal States ======== private static volatile Map kvs; private static volatile boolean ttlAgentLoaded = false; /** * Entrance method of TTL Java Agent. * * @see TtlAgent */ public static void premain(final String agentArgs, @NonNull final Instrumentation inst) { kvs = TtlAgentHelper.splitCommaColonStringToKV(agentArgs); Logger.setLoggerImplType(getLoggerType()); final Logger logger = Logger.getLogger(TtlAgent.class); try { logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst); logger.info(logTtlAgentConfig()); final List transformletList = new ArrayList<>(); transformletList.add(new JdkExecutorTtlTransformlet()); transformletList.add(new PriorityBlockingQueueTtlTransformlet()); transformletList.add(new ForkJoinTtlTransformlet()); if (isEnableTimerTask()) transformletList.add(new TimerTaskTtlTransformlet()); final ClassFileTransformer transformer = new TtlTransformer(transformletList, isLogClassTransform()); inst.addTransformer(transformer, true); logger.info("[TtlAgent.premain] add Transformer " + transformer.getClass().getName() + " success"); logger.info("[TtlAgent.premain] end"); ttlAgentLoaded = true; } catch (Exception e) { String msg = "Fail to load TtlAgent , cause: " + e.toString(); logger.error(msg, e); throw new IllegalStateException(msg, e); } } private static String logTtlAgentConfig() { return "TTL Agent configurations:" + "\n " + TTL_AGENT_LOGGER_KEY + "=" + getLoggerType() + "\n " + TTL_AGENT_LOG_CLASS_TRANSFORM_KEY + "=" + isLogClassTransform() + "\n " + TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY + "=" + isDisableInheritableForThreadPool() + "\n " + TTL_AGENT_ENABLE_TIMER_TASK_KEY + "=" + isEnableTimerTask(); } /** * Whether TTL agent is loaded. * * @since 2.9.0 */ public static boolean isTtlAgentLoaded() { return ttlAgentLoaded; } /** * Whether disable inheritable for thread pool is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. *

* Same as {@code isBooleanOptionSet(TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY)}. * * @see com.alibaba.ttl.threadpool.TtlExecutors#getDefaultDisableInheritableThreadFactory() * @see com.alibaba.ttl.threadpool.TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) * @see com.alibaba.ttl.threadpool.DisableInheritableThreadFactory * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#getDefaultDisableInheritableForkJoinWorkerThreadFactory() * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper#getDisableInheritableForkJoinWorkerThreadFactory(java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory) * @see com.alibaba.ttl.threadpool.DisableInheritableForkJoinWorkerThreadFactory * @see com.alibaba.ttl.TransmittableThreadLocal * @see TtlAgent * @see #isBooleanOptionSet(String) * @see #TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY * @since 2.10.1 */ public static boolean isDisableInheritableForThreadPool() { return isBooleanOptionSet(TTL_AGENT_DISABLE_INHERITABLE_FOR_THREAD_POOL_KEY); } /** * Whether timer task is enhanced by ttl agent, check {@link #isTtlAgentLoaded()} first. *

* Same as {@code isBooleanOptionSet(TTL_AGENT_ENABLE_TIMER_TASK_KEY, true)}. * * @see java.util.Timer * @see java.util.TimerTask * @see TtlAgent * @see #isBooleanOptionSet(String, boolean) * @see #TTL_AGENT_ENABLE_TIMER_TASK_KEY * @since 2.10.1 */ public static boolean isEnableTimerTask() { return isBooleanOptionSet(TTL_AGENT_ENABLE_TIMER_TASK_KEY, true); } /** * Whether logging the transform class received by {@link TtlAgent}. *

* Same as {@code isBooleanOptionSet(TTL_AGENT_LOG_CLASS_TRANSFORM_KEY)}. * * @see TtlAgent * @see #isBooleanOptionSet(String) * @see #TTL_AGENT_LOG_CLASS_TRANSFORM_KEY * @since 3.0.0 */ public static boolean isLogClassTransform() { return isBooleanOptionSet(TTL_AGENT_LOG_CLASS_TRANSFORM_KEY); } /** * Get the TTL Agent Log type. *

* Same as {@code getStringOptionValue(TTL_AGENT_LOGGER_KEY, Logger.STDERR)}. * * @see Logger * @see Logger#STDERR * @see Logger#STDOUT * @see TtlAgent * @see #getStringOptionValue(String, String) * @see #TTL_AGENT_LOGGER_KEY * @since 3.0.0 */ @NonNull public static String getLoggerType() { return getStringOptionValue(TTL_AGENT_LOGGER_KEY, Logger.STDERR); } // ======== Generic Option Getters ======== /** * Generic Option Getters for {@code boolean type} option. *

* Same as {@code isBooleanOptionSet(key, false)}. * * @see #isBooleanOptionSet(String, boolean) * @see TtlAgent * @since 3.0.0 */ public static boolean isBooleanOptionSet(@NonNull String key) { return isBooleanOptionSet(key, false); } /** * Generic Option Getters for {@code boolean type} option. * * @see TtlAgent * @since 3.0.0 */ public static boolean isBooleanOptionSet(@NonNull String key, boolean defaultValueIfKeyAbsent) { return TtlAgentHelper.isBooleanOptionSet(kvs, key, defaultValueIfKeyAbsent); } /** * Generic Option Getters for {@code string type} option. *

* usage example: *

* if {@code -Dttl.agent.logger=STDOUT} or * TTL Agent configuration is {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=ttl.agent.logger:STDOUT}, * {@code getOptionValue("ttl.agent.logger")} return {@code STDOUT}. * * @see TtlAgent * @since 3.0.0 */ @NonNull public static String getStringOptionValue(@NonNull String key, @NonNull String defaultValue) { return TtlAgentHelper.getStringOptionValue(kvs, key, defaultValue); } /** * Generic Option Getters for {@code string list type} option. *

* TTL configuration use {@code |} to separate items. *

* usage example:
* if {@code -Dfoo.list=v1|v2|v3} or * TTL Agent configuration is {@code -javaagent:/path/to/transmittable-thread-local-2.x.y.jar=foo.list:v1|v2|v3}, * {@code getOptionValue("foo.list")} return {@code [v1, v2, v3]}. * * @see TtlAgent */ @NonNull static List getOptionStringListValues(@NonNull String key) { return TtlAgentHelper.getOptionStringListValues(kvs, key); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlAgent() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/TtlAgentHelper.java ================================================ package com.alibaba.ttl.threadpool.agent; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.*; /** * @author Jerry Lee (oldratlee at gmail dot com) * @since 3.0.0 */ final class TtlAgentHelper { // ======== Option Getter Methods ======== static boolean isBooleanOptionSet( @Nullable final Map kvs, @NonNull String key, boolean defaultValueIfKeyAbsent ) { return isBooleanOptionSet(kvs, key, defaultValueIfKeyAbsent, true); } static boolean isBooleanOptionSet( @Nullable final Map kvs, @NonNull String key, boolean defaultValueIfKeyAbsent, boolean defaultValueIfValueAbsent ) { final String value; final Properties properties = System.getProperties(); if (properties.containsKey(key)) { value = properties.getProperty(key).trim(); } else { if (kvs == null) return defaultValueIfKeyAbsent; final boolean containsKey = kvs.containsKey(key); if (!containsKey) return defaultValueIfKeyAbsent; value = kvs.get(key).trim(); } // if value is blank if (value.isEmpty()) return defaultValueIfValueAbsent; return !"false".equalsIgnoreCase(value); } @NonNull static String getStringOptionValue( @Nullable final Map kvs, @NonNull String key, @NonNull String defaultValue ) { final String value; final Properties properties = System.getProperties(); if (properties.containsKey(key)) { value = properties.getProperty(key).trim(); } else { if (kvs == null) return defaultValue; final boolean containsKey = kvs.containsKey(key); if (!containsKey) return defaultValue; value = kvs.get(key).trim(); } // if value is blank if (value.isEmpty()) return defaultValue; return value; } @NonNull @SuppressWarnings("unchecked") static List getOptionStringListValues(@Nullable final Map kvs, @NonNull String key) { final String value; final Properties properties = System.getProperties(); if (properties.containsKey(key)) { value = properties.getProperty(key); } else { if (kvs == null) return Collections.EMPTY_LIST; value = kvs.get(key); } return splitListStringToStringList(value); } // ======== Simple Parse Util Methods ======== /** * Split {@code json} like String({@code "k1:v1,k2:v2"}) to KV map({@code "k1"->"v1", "k2"->"v2"}). */ @NonNull static Map splitCommaColonStringToKV(@Nullable final String commaColonString) { final Map ret = new HashMap<>(); if (commaColonString == null || commaColonString.trim().length() == 0) return ret; final String[] splitKvArray = commaColonString.trim().split("\\s*,\\s*"); for (String kvString : splitKvArray) { final String[] kv = kvString.trim().split("\\s*:\\s*"); if (kv.length == 0) continue; if (kv.length == 1) ret.put(kv[0], ""); else ret.put(kv[0], kv[1]); } return ret; } /** * Split String {@code "v1|v2|v3"} to String List({@code [v1, v2, v3]}). */ @NonNull static List splitListStringToStringList(@Nullable String listString) { final List ret = new ArrayList<>(); if (listString == null || listString.trim().length() == 0) return ret; final String[] split = listString.trim().split("\\s*\\|\\s*"); for (String s : split) { if (s.length() == 0) continue; ret.add(s); } return ret; } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlAgentHelper() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/TtlExtensionTransformletManager.java ================================================ package com.alibaba.ttl.threadpool.agent; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.CannotCompileException; import javassist.NotFoundException; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.*; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.getLocationUrlOfClass; import static java.nio.charset.StandardCharsets.UTF_8; /** * @author Jerry Lee (oldratlee at gmail dot com) * @since 3.0.0 */ final class TtlExtensionTransformletManager { private static final Logger logger = Logger.getLogger(TtlExtensionTransformletManager.class); private static final String TTL_AGENT_EXTENSION_TRANSFORMLET_FILE = "META-INF/ttl.agent.transformlets"; public TtlExtensionTransformletManager() { } public String extensionTransformletDoTransform(@NonNull final ClassInfo classInfo) throws NotFoundException, CannotCompileException, IOException { final Map transformlets = classLoader2ExtensionTransformletsIncludeParentCL.get(classInfo.getClassLoader()); if (transformlets == null) return null; for (Map.Entry entry : transformlets.entrySet()) { final String className = entry.getKey(); final TtlTransformlet transformlet = entry.getValue(); transformlet.doTransform(classInfo); if (classInfo.isModified()) { return className; } } return null; } // NOTE: use WeakHashMap as a Set collection, value is always null. private final WeakHashMap collectedClassLoaderHistory = new WeakHashMap<>(512); // Map: ExtensionTransformlet ClassLoader -> ExtensionTransformlet ClassName -> ExtensionTransformlet instance(not include from parent classloader) private final WeakHashMap> classLoader2ExtensionTransformlets = new WeakHashMap<>(512); // Map: ExtensionTransformlet ClassLoader -> ExtensionTransformlet ClassName -> ExtensionTransformlet instance(include from parent classloader) private final WeakHashMap> classLoader2ExtensionTransformletsIncludeParentCL = new WeakHashMap<>(512); public void collectExtensionTransformlet(@NonNull final ClassInfo classInfo) throws IOException { final ClassLoader classLoader = classInfo.getClassLoader(); // classloader may null be if the bootstrap loader, // which classloader must contains NO Ttl Agent Extension Transformlet, so just safe skip if (classLoader == null) return; // this classLoader is collected, so skip collection if (collectedClassLoaderHistory.containsKey(classLoader)) return; collectedClassLoaderHistory.put(classLoader, null); logger.info("[TtlExtensionTransformletCollector] collecting TTL Extension Transformlets from classloader " + classLoader); final LinkedHashSet extensionTransformletClassNames = readExtensionTransformletClassNames(classLoader); final String foundMsgHead = "[TtlExtensionTransformletCollector] found TTL Extension Transformlet class "; final String failLoadMsgHead = "[TtlExtensionTransformletCollector] fail to load TTL Extension Transformlet "; final Map> loadedTransformlet = loadExtensionInstances(classLoader, extensionTransformletClassNames, TtlTransformlet.class, foundMsgHead, failLoadMsgHead); mergeToClassLoader2ExtensionTransformlet(classLoader2ExtensionTransformlets, loadedTransformlet); updateClassLoader2ExtensionTransformletsIncludeParentCL( classLoader2ExtensionTransformlets, classLoader2ExtensionTransformletsIncludeParentCL); } // extension transformlet configuration file URL location string -> URL contained extension transformlet class names private final Map> redExtensionTransformletFileHistory = new HashMap<>(); private LinkedHashSet readExtensionTransformletClassNames(ClassLoader classLoader) throws IOException { final Enumeration extensionFiles = classLoader.getResources(TTL_AGENT_EXTENSION_TRANSFORMLET_FILE); final Pair, Set> pair = readLinesFromExtensionFiles(extensionFiles, redExtensionTransformletFileHistory); final LinkedHashSet extensionTransformletClassNames = pair.first; final Set stringUrls = pair.second; if (!stringUrls.isEmpty()) logger.info("[TtlExtensionTransformletCollector] found TTL Extension Transformlet configuration files from classloader " + classLoader + " : " + stringUrls); return extensionTransformletClassNames; } private static void mergeToClassLoader2ExtensionTransformlet( Map> destination, Map> loadedTransformlets ) { for (Map.Entry> entry : loadedTransformlets.entrySet()) { final ClassLoader classLoader = entry.getKey(); final Set transformlets = entry.getValue(); Map className2Transformlets = destination.computeIfAbsent(classLoader, k -> new HashMap<>()); for (TtlTransformlet t : transformlets) { final String className = t.getClass().getName(); if (className2Transformlets.containsKey(className)) continue; className2Transformlets.put(className, t); logger.info("[TtlExtensionTransformletCollector] add TTL Extension Transformlet " + className + " success"); } } } static void updateClassLoader2ExtensionTransformletsIncludeParentCL( Map> classLoader2ExtensionTransformlets, Map> classLoader2ExtensionTransformletsIncludeParentCL ) { for (Map.Entry> entry : classLoader2ExtensionTransformlets.entrySet()) { final ClassLoader classLoader = entry.getKey(); final Map merged = childClassLoaderFirstMergeTransformlets(classLoader2ExtensionTransformlets, classLoader); classLoader2ExtensionTransformletsIncludeParentCL.put(classLoader, merged); } } static Map childClassLoaderFirstMergeTransformlets( Map> classLoader2Transformlet, ClassLoader classLoader ) { Map ret = new HashMap<>(); final ArrayDeque chain = new ArrayDeque<>(); chain.add(classLoader); while (classLoader.getParent() != null) { classLoader = classLoader.getParent(); chain.addFirst(classLoader); } for (ClassLoader loader : chain) { final Map m = classLoader2Transformlet.get(loader); if (m == null) continue; ret.putAll(m); } return ret; } // ======== Extension load util methods ======== static Map> loadExtensionInstances( ClassLoader classLoader, LinkedHashSet instanceClassNames, Class superType, String foundMsgHead, String failLoadMsgHead ) { Map> ret = new HashMap<>(); for (final String className : instanceClassNames) { try { final Class clazz = classLoader.loadClass(className); if (!superType.isAssignableFrom(clazz)) { final String msg = foundMsgHead + className + " from classloader " + classLoader + " at location " + getLocationUrlOfClass(clazz) + ", but NOT subtype of " + superType.getName() + ", ignored!"; logger.error(msg); continue; } Object instance = clazz.getDeclaredConstructor().newInstance(); final ClassLoader actualClassLoader = instance.getClass().getClassLoader(); Set set = ret.computeIfAbsent(actualClassLoader, k -> new HashSet<>()); set.add(superType.cast(instance)); final String msg = foundMsgHead + className + ", and loaded from classloader " + classLoader + " at location " + getLocationUrlOfClass(clazz); logger.info(msg); } catch (ClassNotFoundException e) { final String msg = failLoadMsgHead + className + " from classloader " + classLoader + ", cause: " + e.toString(); logger.warn(msg, e); } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { final String msg = failLoadMsgHead + className + " from classloader " + classLoader + ", cause: " + e.toString(); logger.error(msg, e); } } return ret; } // return: read lines from URL, url strings @NonNull static Pair, Set> readLinesFromExtensionFiles( /* input */ @NonNull Enumeration extensionFiles, /* input/output, map url string -> content lines */ @NonNull Map> redExtensionFilesHistory ) { final LinkedHashSet mergedLines = new LinkedHashSet<>(); final Set stringUrls = new HashSet<>(); while (extensionFiles.hasMoreElements()) { final URL url = extensionFiles.nextElement(); final String urlString = url.toString(); stringUrls.add(urlString); LinkedHashSet lines; if (redExtensionFilesHistory.containsKey(urlString)) { lines = redExtensionFilesHistory.get(urlString); } else { lines = readLines(url); redExtensionFilesHistory.put(urlString, lines); } mergedLines.addAll(lines); } return new Pair<>(mergedLines, stringUrls); } /** * this method is modified based on {@link java.util.ServiceLoader} */ @SuppressWarnings("StatementWithEmptyBody") static LinkedHashSet readLines(URL extensionFile) { InputStream inputStream = null; BufferedReader reader = null; LinkedHashSet names = new LinkedHashSet<>(); try { inputStream = extensionFile.openStream(); reader = new BufferedReader(new InputStreamReader(inputStream, UTF_8)); int lineNum = 1; while ((lineNum = parseLine(extensionFile, reader, lineNum, names)) >= 0) ; } catch (IOException x) { logger.error("Error reading configuration file " + extensionFile, x); } finally { try { if (reader != null) reader.close(); if (inputStream != null) inputStream.close(); } catch (IOException y) { logger.warn("Error closing configuration file " + extensionFile, y); } } return names; } /** * this method is modified based on {@link java.util.ServiceLoader} */ private static int parseLine(URL url, BufferedReader reader, int lineNum, LinkedHashSet names) throws IOException { String line = reader.readLine(); if (line == null) { return -1; } // remove comments that start with `#` int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); int n = line.length(); if (n != 0) { if ((line.indexOf(' ') >= 0) || (line.indexOf('\t') >= 0)) { logger.error("Illegal syntax " + line + "in configuration file" + url + ", contains space or tab; ignore this line!"); return lineNum + 1; } int cp = line.codePointAt(0); if (!Character.isJavaIdentifierStart(cp)) { logger.error("Illegal extension class name " + line + " in configuration file " + url + "; ignore this line!"); return lineNum + 1; } for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = line.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) { logger.error("Illegal extension class name: " + line + " in configuration file " + url + "; ignore this line!"); return lineNum + 1; } } names.add(line); } return lineNum + 1; } static class Pair { T first; U second; public Pair(T first, U second) { this.first = first; this.second = second; } } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/TtlTransformer.java ================================================ package com.alibaba.ttl.threadpool.agent; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.isClassUnderPackage; /** * TTL {@link ClassFileTransformer} of Java Agent * * @author Jerry Lee (oldratlee at gmail dot com) * @see ClassFileTransformer * @see The mechanism for instrumentation * @since 0.9.0 */ public class TtlTransformer implements ClassFileTransformer { private static final Logger logger = Logger.getLogger(TtlTransformer.class); /** * "null if no transform is performed", * see {@code @return} of {@link ClassFileTransformer#transform(ClassLoader, String, Class, ProtectionDomain, byte[])} */ @SuppressFBWarnings({"EI_EXPOSE_REP"}) // [ERROR] com.alibaba.ttl.threadpool.agent.TtlTransformer.transform(ClassLoader, String, Class, ProtectionDomain, byte[]) // may expose internal representation by returning TtlTransformer.NO_TRANSFORM // the value is null, so there is NO "EI_EXPOSE_REP" problem actually. private static final byte[] NO_TRANSFORM = null; private final TtlExtensionTransformletManager extensionTransformletManager; private final List transformletList = new ArrayList<>(); private final boolean logClassTransform; TtlTransformer(List transformletList, boolean logClassTransform) { extensionTransformletManager = new TtlExtensionTransformletManager(); this.logClassTransform = logClassTransform; for (TtlTransformlet ttlTransformlet : transformletList) { this.transformletList.add(ttlTransformlet); logger.info("[TtlTransformer] add Transformlet " + ttlTransformlet.getClass().getName()); } } /** * info about class loader: may be null if the bootstrap loader. *

* more info see {@link ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[])} */ @Override public final byte[] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class classBeingRedefined, final ProtectionDomain protectionDomain, @NonNull final byte[] classFileBuffer) { try { // Lambda has no class file, no need to transform, just return. if (classFile == null) return NO_TRANSFORM; final ClassInfo classInfo = new ClassInfo(classFile, classFileBuffer, loader); if (isClassUnderPackage(classInfo.getClassName(), "com.alibaba.ttl")) return NO_TRANSFORM; if (isClassUnderPackage(classInfo.getClassName(), "java.lang")) return NO_TRANSFORM; if (logClassTransform) logger.info("[TtlTransformer] transforming " + classInfo.getClassName() + " from classloader " + classInfo.getClassLoader() + " at location " + classInfo.getLocationUrl()); extensionTransformletManager.collectExtensionTransformlet(classInfo); for (TtlTransformlet transformlet : transformletList) { transformlet.doTransform(classInfo); if (classInfo.isModified()) { logger.info("[TtlTransformer] " + transformlet.getClass().getName() + " transformed " + classInfo.getClassName() + " from classloader " + classInfo.getClassLoader() + " at location " + classInfo.getLocationUrl()); return classInfo.getCtClass().toBytecode(); } } final String transformlet = extensionTransformletManager.extensionTransformletDoTransform(classInfo); if (classInfo.isModified()) { logger.info("[TtlTransformer] " + transformlet + " transformed " + classInfo.getClassName() + " from classloader " + classInfo.getClassLoader() + " at location " + classInfo.getLocationUrl()); return classInfo.getCtClass().toBytecode(); } } catch (Throwable t) { String msg = "[TtlTransformer] fail to transform class " + classFile + ", cause: " + t.toString(); logger.error(msg, t); throw new IllegalStateException(msg, t); } return NO_TRANSFORM; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/logging/Logger.java ================================================ package com.alibaba.ttl.threadpool.agent.logging; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Level; /** * Logger adaptor for ttl TTL agent/transformlet. Only use for TTL agent/transformlet! * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet * @see com.alibaba.ttl.threadpool.agent.TtlAgent * @since 3.0.0 */ public abstract class Logger { public static final String STDOUT = "STDOUT"; public static final String STDERR = "STDERR"; private static volatile int loggerImplType = -1; public static void setLoggerImplType(String type) { if (loggerImplType != -1) { throw new IllegalStateException("TTL logger implementation type is already set! type = " + loggerImplType); } if (STDERR.equalsIgnoreCase(type)) loggerImplType = 0; else if (STDOUT.equalsIgnoreCase(type)) loggerImplType = 1; else loggerImplType = 0; } /** * Only for test code */ public static void setLoggerImplTypeIfNotSetYet(String type) { if (loggerImplType == -1) setLoggerImplType(type); } public static Logger getLogger(Class clazz) { if (loggerImplType == -1) throw new IllegalStateException("TTL logger implementation type is NOT set!"); switch (loggerImplType) { case 1: return new StdOutLogger(clazz); default: return new StdErrorLogger(clazz); } } final Class logClass; private Logger(Class logClass) { this.logClass = logClass; } public void info(String msg) { log(Level.INFO, msg, null); } public void warn(String msg) { log(Level.WARNING, msg, null); } public void warn(String msg, Throwable thrown) { log(Level.WARNING, msg, thrown); } public void error(String msg) { log(Level.SEVERE, msg, null); } public void error(String msg, Throwable thrown) { log(Level.SEVERE, msg, thrown); } protected abstract void log(Level level, String msg, Throwable thrown); private static class StdErrorLogger extends Logger { StdErrorLogger(Class clazz) { super(clazz); } @Override public void log(Level level, String msg, Throwable thrown) { if (level == Level.SEVERE) { final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); System.err.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); if (thrown != null) thrown.printStackTrace(); } } } private static class StdOutLogger extends Logger { StdOutLogger(Class clazz) { super(clazz); } @Override public void log(Level level, String msg, Throwable thrown) { final String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()); System.out.printf("%s %s [%s] %s: %s%n", time, level, Thread.currentThread().getName(), logClass.getSimpleName(), msg); if (thrown != null) thrown.printStackTrace(System.out); } } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/logging/package-info.java ================================================ /** * TTL Agent Logger. Only use for TTL agent/transformlet. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.logging.Logger * @since 3.0.0 */ package com.alibaba.ttl.threadpool.agent.logging; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/package-info.java ================================================ /** * TTL Agent. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.TtlAgent * @see The mechanism for instrumentation */ package com.alibaba.ttl.threadpool.agent; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/ClassInfo.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javassist.ClassPool; import javassist.CtClass; import javassist.LoaderClassPath; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URL; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.getLocationUrlOfClass; /** * Class Info for {@link TtlTransformlet}. * * Caution:
* Do NOT load {@link Class} which is transforming, or the transform will lose effectiveness. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 3.0.0 */ public class ClassInfo { private final String transformerClassFile; private final String className; private final byte[] classFileBuffer; private final ClassLoader loader; // SuppressFBWarnings for classFileBuffer/loader parameter: // [ERROR] new com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo(String, byte[], ClassLoader) // may expose internal representation by storing an externally mutable object // into ClassInfo.classFileBuffer/loader public ClassInfo(@NonNull String transformerClassFile, @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP2"}) byte[] classFileBuffer, @Nullable @SuppressFBWarnings({"EI_EXPOSE_REP2"}) ClassLoader loader) { this.transformerClassFile = transformerClassFile; this.className = toClassName(transformerClassFile); this.classFileBuffer = classFileBuffer; this.loader = loader; } @NonNull public String getClassName() { return className; } private CtClass ctClass; public URL getLocationUrl() throws IOException { return getLocationUrlOfClass(getCtClass()); } @NonNull @SuppressFBWarnings({"EI_EXPOSE_REP"}) // [ERROR] Medium: com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo.getCtClass() // may expose internal representation // by returning ClassInfo.ctClass [com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo] public CtClass getCtClass() throws IOException { if (ctClass != null) return ctClass; final ClassPool classPool = new ClassPool(true); if (loader == null) { classPool.appendClassPath(new LoaderClassPath(ClassLoader.getSystemClassLoader())); } else { classPool.appendClassPath(new LoaderClassPath(loader)); } final CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classFileBuffer), false); clazz.defrost(); this.ctClass = clazz; return clazz; } private boolean modified = false; public boolean isModified() { return modified; } public void setModified() { this.modified = true; } @SuppressFBWarnings({"EI_EXPOSE_REP"}) // [ERROR] Medium: com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo.getClassLoader() // may expose internal representation // by returning ClassInfo.loader [com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo] public ClassLoader getClassLoader() { return loader; } private static String toClassName(@NonNull final String classFile) { return classFile.replace('/', '.'); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/TtlTransformlet.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.CannotCompileException; import javassist.NotFoundException; import java.io.IOException; /** * TTL {@code Transformlet}. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 3.0.0 */ public interface TtlTransformlet { /** * info about class loader: may be null if the bootstrap loader. *

* more info see {@link java.lang.instrument.ClassFileTransformer#transform(java.lang.ClassLoader, java.lang.String, java.lang.Class, java.security.ProtectionDomain, byte[])} * * @see com.alibaba.ttl.threadpool.agent.TtlTransformer#transform(ClassLoader, String, Class, java.security.ProtectionDomain, byte[]) * @see java.lang.instrument.ClassFileTransformer#transform */ void doTransform(@NonNull ClassInfo classInfo) throws CannotCompileException, NotFoundException, IOException; } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/helper/AbstractExecutorTtlTransformlet.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.helper; import com.alibaba.ttl.threadpool.TtlExecutors; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javassist.*; import java.io.IOException; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.isClassAtPackageJavaUtil; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; /** * Abstract {@link TtlTransformlet} for {@link java.util.concurrent.Executor} and its subclass. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.concurrent.Executor * @see java.util.concurrent.ExecutorService * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @see java.util.concurrent.Executors * @see com.alibaba.ttl.threadpool.agent.transformlet.internal.PriorityBlockingQueueTtlTransformlet * @since 3.0.0 */ public abstract class AbstractExecutorTtlTransformlet implements TtlTransformlet { protected static final String RUNNABLE_CLASS_NAME = "java.lang.Runnable"; protected static final String CALLABLE_CLASS_NAME = "java.util.concurrent.Callable"; protected static final String TTL_RUNNABLE_CLASS_NAME = "com.alibaba.ttl.TtlRunnable"; protected static final String TTL_CALLABLE_CLASS_NAME = "com.alibaba.ttl.TtlCallable"; protected static final String THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ThreadFactory"; protected static final String THREAD_POOL_EXECUTOR_CLASS_NAME = "java.util.concurrent.ThreadPoolExecutor"; protected final Logger logger = Logger.getLogger(getClass()); protected final Set executorClassNames; protected final boolean disableInheritableForThreadPool; private final Map paramTypeNameToDecorateMethodClass = new HashMap<>(); /** * @param executorClassNames the executor class names to be transformed */ public AbstractExecutorTtlTransformlet(Set executorClassNames, boolean disableInheritableForThreadPool) { this.executorClassNames = Collections.unmodifiableSet(executorClassNames); this.disableInheritableForThreadPool = disableInheritableForThreadPool; paramTypeNameToDecorateMethodClass.put(RUNNABLE_CLASS_NAME, TTL_RUNNABLE_CLASS_NAME); paramTypeNameToDecorateMethodClass.put(CALLABLE_CLASS_NAME, TTL_CALLABLE_CLASS_NAME); } @Override public final void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { // work-around ClassCircularityError: // https://github.com/alibaba/transmittable-thread-local/issues/278 // https://github.com/alibaba/transmittable-thread-local/issues/234 if (isClassAtPackageJavaUtil(classInfo.getClassName())) return; final CtClass clazz = classInfo.getCtClass(); if (executorClassNames.contains(classInfo.getClassName())) { for (CtMethod method : clazz.getDeclaredMethods()) { updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); } if (disableInheritableForThreadPool) updateConstructorDisableInheritable(clazz); classInfo.setModified(); } else { if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || clazz.isAnnotation()) { return; } if (!clazz.subclassOf(clazz.getClassPool().get(THREAD_POOL_EXECUTOR_CLASS_NAME))) return; logger.info("Transforming class " + classInfo.getClassName()); final boolean modified = updateBeforeAndAfterExecuteMethodOfExecutorSubclass(clazz); if (modified) classInfo.setModified(); } } /** * @see TtlTransformletHelper#doAutoWrap(Runnable) * @see TtlTransformletHelper#doAutoWrap(Callable) */ @SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE") // [ERROR] Format string should use %n rather than \n private void updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(@NonNull final CtMethod method) throws NotFoundException, CannotCompileException { final int modifiers = method.getModifiers(); if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) return; CtClass[] parameterTypes = method.getParameterTypes(); StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (paramTypeNameToDecorateMethodClass.containsKey(paramTypeName)) { String code = String.format( // auto decorate to TTL wrapper "$%d = com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.doAutoWrap($% 0) { logger.info("insert code before method " + signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ":\n" + insertCode); method.insertBefore(insertCode.toString()); } } /** * @see TtlExecutors#getDisableInheritableThreadFactory(java.util.concurrent.ThreadFactory) */ private void updateConstructorDisableInheritable(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException { for (CtConstructor constructor : clazz.getDeclaredConstructors()) { final CtClass[] parameterTypes = constructor.getParameterTypes(); final StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (THREAD_FACTORY_CLASS_NAME.equals(paramTypeName)) { String code = String.format("$%d = com.alibaba.ttl.threadpool.TtlExecutors.getDisableInheritableThreadFactory($% 0) { logger.info("insert code before constructor " + signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + insertCode); constructor.insertBefore(insertCode.toString()); } } } /** * @see com.alibaba.ttl.spi.TtlAttachmentsDelegate#unwrapIfIsAutoWrapper(Object) */ private boolean updateBeforeAndAfterExecuteMethodOfExecutorSubclass(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException { final CtClass runnableClass = clazz.getClassPool().get(RUNNABLE_CLASS_NAME); final CtClass threadClass = clazz.getClassPool().get("java.lang.Thread"); final CtClass throwableClass = clazz.getClassPool().get("java.lang.Throwable"); boolean modified = false; try { final CtMethod beforeExecute = clazz.getDeclaredMethod("beforeExecute", new CtClass[]{threadClass, runnableClass}); // unwrap runnable if IsAutoWrapper String code = "$2 = com.alibaba.ttl.spi.TtlAttachmentsDelegate.unwrapIfIsAutoWrapper($2);"; logger.info("insert code before method " + signatureOfMethod(beforeExecute) + " of class " + beforeExecute.getDeclaringClass().getName() + ": " + code); beforeExecute.insertBefore(code); modified = true; } catch (NotFoundException e) { // clazz does not override beforeExecute method, do nothing. } try { final CtMethod afterExecute = clazz.getDeclaredMethod("afterExecute", new CtClass[]{runnableClass, throwableClass}); // unwrap runnable if IsAutoWrapper String code = "$1 = com.alibaba.ttl.spi.TtlAttachmentsDelegate.unwrapIfIsAutoWrapper($1);"; logger.info("insert code before method " + signatureOfMethod(afterExecute) + " of class " + afterExecute.getDeclaringClass().getName() + ": " + code); afterExecute.insertBefore(code); modified = true; } catch (NotFoundException e) { // clazz does not override afterExecute method, do nothing. } return modified; } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/helper/TtlTransformletHelper.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.helper; import com.alibaba.ttl.TtlCallable; import com.alibaba.ttl.TtlRunnable; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javassist.*; import java.lang.reflect.Modifier; import java.net.URL; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.concurrent.Callable; import static com.alibaba.ttl.TransmittableThreadLocal.Transmitter.capture; import static com.alibaba.ttl.spi.TtlAttachmentsDelegate.setAutoWrapperAttachment; /** * Helper methods for {@link TtlTransformlet} implementation. * * @author Jerry Lee (oldratlee at gmail dot com) * @since 3.0.0 */ public final class TtlTransformletHelper { private static final Logger logger = Logger.getLogger(TtlTransformletHelper.class); // ======== Javassist/Class Helper ======== /** * Output string like {@code public ScheduledFuture scheduleAtFixedRate(Runnable, long, long, TimeUnit)} * for {@link java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate}. * * @param method method object * @return method signature string */ @NonNull public static String signatureOfMethod(@NonNull final CtBehavior method) throws NotFoundException { final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(Modifier.toString(method.getModifiers())); if (method instanceof CtMethod) { final String returnType = ((CtMethod) method).getReturnType().getSimpleName(); stringBuilder.append(" ").append(returnType); } stringBuilder.append(" ").append(method.getName()).append("("); final CtClass[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { CtClass parameterType = parameterTypes[i]; if (i != 0) stringBuilder.append(", "); stringBuilder.append(parameterType.getSimpleName()); } stringBuilder.append(")"); return stringBuilder.toString(); } public static URL getLocationUrlOfClass(CtClass clazz) { try { // proxy classes is dynamic, no class file if (clazz.getName().startsWith("com.sun.proxy.")) return null; return clazz.getURL(); } catch (Exception e) { logger.warn("Fail to getLocationUrlOfClass " + clazz.getName() + ", cause: " + e.toString()); return null; } } public static String getLocationFileOfClass(CtClass clazz) { final URL location = getLocationUrlOfClass(clazz); if (location == null) return null; return location.getFile(); } public static URL getLocationUrlOfClass(Class clazz) { try { // proxy classes is dynamic, no class file if (clazz.getName().startsWith("com.sun.proxy.")) return null; final ProtectionDomain protectionDomain = clazz.getProtectionDomain(); if (protectionDomain == null) return null; final CodeSource codeSource = protectionDomain.getCodeSource(); if (codeSource == null) return null; return codeSource.getLocation(); } catch (Exception e) { logger.warn("Fail to getLocationUrlOfClass " + clazz.getName() + ", cause: " + e.toString()); return null; } } public static String getLocationFileOfClass(Class clazz) { final URL location = getLocationUrlOfClass(clazz); if (location == null) return null; return location.getFile(); } // ======== Method Transform Helper ======== @NonNull public static String renamedMethodNameByTtl(@NonNull CtMethod method) { return "original$" + method.getName() + "$method$renamed$by$ttl"; } public static String addTryFinallyToMethod(@NonNull CtMethod method, @NonNull String beforeCode, @NonNull String finallyCode) throws CannotCompileException, NotFoundException { return addTryFinallyToMethod(method, renamedMethodNameByTtl(method), beforeCode, finallyCode); } /** * Add {@code try-finally} logic to method. * * @return the body code of method rewritten */ public static String addTryFinallyToMethod(@NonNull CtMethod method, @NonNull String nameForOriginalMethod, @NonNull String beforeCode, @NonNull String finallyCode) throws CannotCompileException, NotFoundException { final CtClass clazz = method.getDeclaringClass(); final CtMethod newMethod = CtNewMethod.copy(method, clazz, null); // rename original method, and set to private method(avoid reflect out renamed method unexpectedly) newMethod.setName(nameForOriginalMethod); newMethod.setModifiers(newMethod.getModifiers() & ~Modifier.PUBLIC /* remove public */ & ~Modifier.PROTECTED /* remove protected */ | Modifier.PRIVATE /* add private */); clazz.addMethod(newMethod); final String returnOp; if (method.getReturnType() == CtClass.voidType) { returnOp = ""; } else { returnOp = "return "; } // set new method implementation final String code = "{\n" + beforeCode + "\n" + "try {\n" + " " + returnOp + nameForOriginalMethod + "($$);\n" + "} finally {\n" + " " + finallyCode + "\n" + "} }"; method.setBody(code); return code; } // ======== CRR Helper ======== @Nullable public static Object doCaptureIfNotTtlEnhanced(@Nullable Object obj) { if (obj instanceof TtlEnhanced) return null; else return capture(); } // FIXME hard-coded for type Runnable, not generic! @Nullable public static Runnable doAutoWrap(@Nullable final Runnable runnable) { if (runnable == null) return null; final TtlRunnable ret = TtlRunnable.get(runnable, false, true); // have been auto wrapped? if (ret != runnable) setAutoWrapperAttachment(ret); return ret; } // FIXME hard-coded for type Callable, not generic! @Nullable public static Callable doAutoWrap(@Nullable final Callable callable) { if (callable == null) return null; final TtlCallable ret = TtlCallable.get(callable, false, true); // have been auto wrapped? if (ret != callable) setAutoWrapperAttachment(ret); return ret; } // ======== class/package info Helper ======== @NonNull public static String getPackageName(@NonNull String className) { final int idx = className.lastIndexOf('.'); if (-1 == idx) return ""; return className.substring(0, idx); } public static boolean isClassAtPackage(@NonNull String className, @NonNull String packageName) { return packageName.equals(getPackageName(className)); } public static boolean isClassUnderPackage(@NonNull String className, @NonNull String packageName) { String packageOfClass = getPackageName(className); return packageOfClass.equals(packageName) || packageOfClass.startsWith(packageName + "."); } public static boolean isClassAtPackageJavaUtil(@NonNull String className) { return isClassAtPackage(className, "java.util"); } @SuppressFBWarnings("CT_CONSTRUCTOR_THROW") private TtlTransformletHelper() { throw new InstantiationError("Must not instantiate this class"); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/helper/package-info.java ================================================ /** * Helper API for TTL Agent extension {@code Transformlet} development. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet * @since 3.0.0 */ package com.alibaba.ttl.threadpool.agent.transformlet.helper; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/internal/ForkJoinTtlTransformlet.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.internal; import com.alibaba.ttl.spi.TtlEnhanced; import com.alibaba.ttl.threadpool.agent.TtlAgent; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.*; import java.io.IOException; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.*; /** * {@link TtlTransformlet} for {@link java.util.concurrent.ForkJoinTask}. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.concurrent.ForkJoinPool * @see java.util.concurrent.ForkJoinTask * @since 2.5.1 */ public final class ForkJoinTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(ForkJoinTtlTransformlet.class); private static final String FORK_JOIN_TASK_CLASS_NAME = "java.util.concurrent.ForkJoinTask"; private static final String FORK_JOIN_POOL_CLASS_NAME = "java.util.concurrent.ForkJoinPool"; private static final String FORK_JOIN_WORKER_THREAD_FACTORY_CLASS_NAME = "java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory"; private final boolean disableInheritableForThreadPool; public ForkJoinTtlTransformlet() { this.disableInheritableForThreadPool = TtlAgent.isDisableInheritableForThreadPool(); } @Override public void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { if (FORK_JOIN_TASK_CLASS_NAME.equals(classInfo.getClassName())) { updateForkJoinTaskClass(classInfo.getCtClass()); classInfo.setModified(); } else if (disableInheritableForThreadPool && FORK_JOIN_POOL_CLASS_NAME.equals(classInfo.getClassName())) { updateConstructorDisableInheritable(classInfo.getCtClass()); classInfo.setModified(); } } /** * @see com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper#doCaptureIfNotTtlEnhanced(Object) */ private void updateForkJoinTaskClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { final String className = clazz.getName(); // add new field final String capturedFieldName = "captured$field$added$by$ttl"; final CtField capturedField = CtField.make("private final Object " + capturedFieldName + ";", clazz); clazz.addField(capturedField, "com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.doCaptureIfNotTtlEnhanced(this);"); logger.info("add new field " + capturedFieldName + " to class " + className); final CtMethod doExecMethod = clazz.getDeclaredMethod("doExec", new CtClass[0]); final String doExec_renamed_method_name = renamedMethodNameByTtl(doExecMethod); final String beforeCode = "if (this instanceof " + TtlEnhanced.class.getName() + ") {\n" + // if the class is already TTL enhanced(eg: com.alibaba.ttl.TtlRecursiveTask) " return " + doExec_renamed_method_name + "($$);\n" + // return directly/do nothing "}\n" + "Object backup = com.alibaba.ttl.TransmittableThreadLocal.Transmitter.replay(" + capturedFieldName + ");"; final String finallyCode = "com.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore(backup);"; final String code = addTryFinallyToMethod(doExecMethod, doExec_renamed_method_name, beforeCode, finallyCode); logger.info("insert code around method " + signatureOfMethod(doExecMethod) + " of class " + clazz.getName() + ": " + code); } private void updateConstructorDisableInheritable(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException { for (CtConstructor constructor : clazz.getDeclaredConstructors()) { final CtClass[] parameterTypes = constructor.getParameterTypes(); final StringBuilder insertCode = new StringBuilder(); for (int i = 0; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (FORK_JOIN_WORKER_THREAD_FACTORY_CLASS_NAME.equals(paramTypeName)) { String code = String.format("$%d = com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper.getDisableInheritableForkJoinWorkerThreadFactory($% 0) { logger.info("insert code before method " + signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + insertCode); constructor.insertBefore(insertCode.toString()); } } } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/internal/JdkExecutorTtlTransformlet.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.internal; import com.alibaba.ttl.threadpool.agent.TtlAgent; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import com.alibaba.ttl.threadpool.agent.transformlet.helper.AbstractExecutorTtlTransformlet; import java.util.HashSet; import java.util.Set; /** * {@link TtlTransformlet} for {@link java.util.concurrent.ThreadPoolExecutor} * and {@link java.util.concurrent.ScheduledThreadPoolExecutor}. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ScheduledThreadPoolExecutor * @since 2.5.1 */ public final class JdkExecutorTtlTransformlet extends AbstractExecutorTtlTransformlet implements TtlTransformlet { private static Set getExecutorClassNames() { Set executorClassNames = new HashSet<>(); executorClassNames.add(THREAD_POOL_EXECUTOR_CLASS_NAME); executorClassNames.add("java.util.concurrent.ScheduledThreadPoolExecutor"); return executorClassNames; } public JdkExecutorTtlTransformlet() { super(getExecutorClassNames(), TtlAgent.isDisableInheritableForThreadPool()); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/internal/PriorityBlockingQueueTtlTransformlet.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.internal; import com.alibaba.ttl.threadpool.TtlExecutors; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.CannotCompileException; import javassist.CtClass; import javassist.CtConstructor; import javassist.NotFoundException; import java.io.IOException; import java.util.Comparator; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.signatureOfMethod; /** * TTL {@link TtlTransformlet} for {@link java.util.concurrent.PriorityBlockingQueue PriorityBlockingQueue}. *

* Avoid {@code ClassCastException(TtlRunnable cannot be cast to Comparable)} problem * for combination usage: *

    *
  • use {@link java.util.concurrent.PriorityBlockingQueue PriorityBlockingQueue} for {@link java.util.concurrent.ThreadPoolExecutor ThreadPoolExecutor}
  • *
  • use {@code TTL Agent} {@link JdkExecutorTtlTransformlet}
  • *
* More info see issue #330 * * @author Jerry Lee (oldratlee at gmail dot com) * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) * @see TtlExecutors#getTtlRunnableUnwrapComparatorForComparableRunnable() * @see java.util.concurrent.ThreadPoolExecutor * @see java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, java.util.concurrent.TimeUnit, java.util.concurrent.BlockingQueue) * @see java.util.concurrent.PriorityBlockingQueue * @see java.util.concurrent.PriorityBlockingQueue#PriorityBlockingQueue(int, Comparator) * @see java.util.PriorityQueue * @see java.util.PriorityQueue#PriorityQueue(int, Comparator) * @see JdkExecutorTtlTransformlet * @since 2.12.3 */ public class PriorityBlockingQueueTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(PriorityBlockingQueueTtlTransformlet.class); private static final String PRIORITY_BLOCKING_QUEUE_CLASS_NAME = "java.util.concurrent.PriorityBlockingQueue"; private static final String PRIORITY_QUEUE_CLASS_NAME = "java.util.PriorityQueue"; private static final String COMPARATOR_FIELD_NAME = "comparator"; @Override public void doTransform(@NonNull ClassInfo classInfo) throws IOException, CannotCompileException, NotFoundException { final String className = classInfo.getClassName(); if (PRIORITY_BLOCKING_QUEUE_CLASS_NAME.equals(className)) { updatePriorityBlockingQueueClass(classInfo.getCtClass()); classInfo.setModified(); } if (PRIORITY_QUEUE_CLASS_NAME.equals(className)) { updateBlockingQueueClass(classInfo.getCtClass()); classInfo.setModified(); } } private void updatePriorityBlockingQueueClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { if (!haveComparatorField(clazz)) { // In Java 6, PriorityBlockingQueue implementation do not have field comparator, // need transform more fundamental class PriorityQueue logger.info(PRIORITY_BLOCKING_QUEUE_CLASS_NAME + " do not have field " + COMPARATOR_FIELD_NAME + ", transform " + PRIORITY_QUEUE_CLASS_NAME + " instead."); return; } modifyConstructors(clazz); } private void updateBlockingQueueClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { final CtClass classPriorityBlockingQueue = clazz.getClassPool().getCtClass(PRIORITY_BLOCKING_QUEUE_CLASS_NAME); if (haveComparatorField(classPriorityBlockingQueue)) return; logger.info(PRIORITY_BLOCKING_QUEUE_CLASS_NAME + " do not have field " + COMPARATOR_FIELD_NAME + ", so need transform " + PRIORITY_QUEUE_CLASS_NAME); modifyConstructors(clazz); } private static boolean haveComparatorField(CtClass clazz) { try { clazz.getDeclaredField(COMPARATOR_FIELD_NAME); return true; } catch (NotFoundException e) { return false; } } /** * wrap comparator field in constructors */ private static final String CODE = "this." + COMPARATOR_FIELD_NAME + " = " + PriorityBlockingQueueTtlTransformlet.class.getName() + ".overwriteComparatorField$by$ttl(this." + COMPARATOR_FIELD_NAME + ");"; /** * @see #overwriteComparatorField$by$ttl(Comparator) */ private static void modifyConstructors(@NonNull CtClass clazz) throws NotFoundException, CannotCompileException { for (CtConstructor constructor : clazz.getDeclaredConstructors()) { logger.info("insert code after constructor " + signatureOfMethod(constructor) + " of class " + constructor.getDeclaringClass().getName() + ": " + CODE); constructor.insertAfter(CODE); } } /** * @see TtlExecutors#getTtlRunnableUnwrapComparator(Comparator) */ public static Comparator overwriteComparatorField$by$ttl(Comparator comparator) { if (comparator == null) return TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable(); return TtlExecutors.getTtlRunnableUnwrapComparator(comparator); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/internal/TimerTaskTtlTransformlet.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.internal; import com.alibaba.ttl.threadpool.agent.logging.Logger; import com.alibaba.ttl.threadpool.agent.transformlet.ClassInfo; import com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet; import edu.umd.cs.findbugs.annotations.NonNull; import javassist.*; import java.io.IOException; import static com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.*; /** * {@link TtlTransformlet} for {@link java.util.TimerTask}. * * @author Jerry Lee (oldratlee at gmail dot com) * @author wuwen5 (wuwen.55 at aliyun dot com) * @see java.util.TimerTask * @see java.util.Timer * @since 2.7.0 */ public final class TimerTaskTtlTransformlet implements TtlTransformlet { private static final Logger logger = Logger.getLogger(TimerTaskTtlTransformlet.class); private static final String TIMER_TASK_CLASS_NAME = "java.util.TimerTask"; private static final String RUN_METHOD_NAME = "run"; @Override public void doTransform(@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { // work-around ClassCircularityError: if (isClassAtPackageJavaUtil(classInfo.getClassName())) return; // TimerTask class is checked by above logic. // // if (TIMER_TASK_CLASS_NAME.equals(classInfo.getClassName())) return; // No need transform TimerTask class final CtClass clazz = classInfo.getCtClass(); if (clazz.isPrimitive() || clazz.isArray() || clazz.isInterface() || clazz.isAnnotation()) { return; } // class contains method `void run()` ? try { final CtMethod runMethod = clazz.getDeclaredMethod(RUN_METHOD_NAME, new CtClass[0]); if (!CtClass.voidType.equals(runMethod.getReturnType())) return; } catch (NotFoundException e) { return; } if (!clazz.subclassOf(clazz.getClassPool().get(TIMER_TASK_CLASS_NAME))) return; logger.info("Transforming class " + classInfo.getClassName()); updateTimerTaskClass(clazz); classInfo.setModified(); } /** * @see com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper#doCaptureIfNotTtlEnhanced(Object) */ private void updateTimerTaskClass(@NonNull final CtClass clazz) throws CannotCompileException, NotFoundException { final String className = clazz.getName(); // add new field final String capturedFieldName = "captured$field$added$by$ttl"; final CtField capturedField = CtField.make("private final Object " + capturedFieldName + ";", clazz); clazz.addField(capturedField, "com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.doCaptureIfNotTtlEnhanced(this);"); logger.info("add new field " + capturedFieldName + " to class " + className); final CtMethod runMethod = clazz.getDeclaredMethod(RUN_METHOD_NAME, new CtClass[0]); final String beforeCode = "Object backup = com.alibaba.ttl.TransmittableThreadLocal.Transmitter.replay(" + capturedFieldName + ");"; final String finallyCode = "com.alibaba.ttl.TransmittableThreadLocal.Transmitter.restore(backup);"; final String code = addTryFinallyToMethod(runMethod, beforeCode, finallyCode); logger.info("insert code around method " + signatureOfMethod(runMethod) + " of class " + clazz.getName() + ": " + code); } } ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/internal/package-info.java ================================================ /** * TTL built-in {@code Transformlet} implementations. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.transformlet.internal.JdkExecutorTtlTransformlet * @see com.alibaba.ttl.threadpool.agent.transformlet.internal.ForkJoinTtlTransformlet * @see com.alibaba.ttl.threadpool.agent.transformlet.internal.TimerTaskTtlTransformlet * @see com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet * @since 3.0.0 */ package com.alibaba.ttl.threadpool.agent.transformlet.internal; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/agent/transformlet/package-info.java ================================================ /** * TTL {@code Transformlet} API for TTL Agent extension {@code Transformlet} development. *

* TTL built-in {@code Transformlet} implementations is in the package {@link com.alibaba.ttl.threadpool.agent.transformlet.internal}. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.transformlet.TtlTransformlet * @since 3.0.0 */ package com.alibaba.ttl.threadpool.agent.transformlet; ================================================ FILE: ttl2-compatible/src/main/java/com/alibaba/ttl/threadpool/package-info.java ================================================ /** * Thread pool wrap/decoration utils. * * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.TtlExecutors * @see com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper */ package com.alibaba.ttl.threadpool; ================================================ FILE: ttl2-compatible/src/main/javadoc/overview.html ================================================

This is the API documentation for the 📌 TransmittableThreadLocal(TTL), The missing Java™ std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.

================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/Utils.kt ================================================ package com.alibaba import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.agent.TtlAgent import io.kotest.assertions.withClue import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import mu.KotlinLogging import java.lang.Thread.sleep import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import java.util.concurrent.ExecutorService import java.util.concurrent.ThreadPoolExecutor private val logger = KotlinLogging.logger {} /** * Expand thread pool, to pre-create and cache threads. */ fun expandThreadPool(executor: ExecutorService) { val cpuCountX2 = Runtime.getRuntime().availableProcessors() * 2 val count = if (executor is ThreadPoolExecutor) { (executor.maximumPoolSize * 2).coerceAtMost(cpuCountX2) } else cpuCountX2 (0 until count).map { executor.submit { sleep(10) } }.forEach { it.get() } } //////////////////////////////////////////////////////////////////////////////// // TTL Instances //////////////////////////////////////////////////////////////////////////////// internal const val PARENT_CREATE_MODIFIED_IN_CHILD = "parent-create-modified-in-child" internal const val PARENT_CREATE_UNMODIFIED_IN_CHILD = "parent-create-unmodified-in-child" internal const val PARENT_CREATE_AFTER_CREATE_CHILD = "parent-create-after-create-child" internal const val CHILD_CREATE = "child-create" typealias TtlInstances = ConcurrentMap> fun createParentTtlInstances(ttlInstances: TtlInstances = ConcurrentHashMap()): TtlInstances { listOf(PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD).forEach { newTtlInstanceAndPut(it, ttlInstances) } return ttlInstances } fun createParentTtlInstancesAfterCreateChild(ttlInstances: TtlInstances) { newTtlInstanceAndPut(PARENT_CREATE_AFTER_CREATE_CHILD, ttlInstances) } fun createChildTtlInstancesAndModifyParentTtlInstances( tag: String, ttlInstances: TtlInstances ): TtlValues { // 1. Add new val newChildKey = "$CHILD_CREATE$tag" newTtlInstanceAndPut(newChildKey, ttlInstances) // 2. modify the parent key val ttl: TransmittableThreadLocal? = ttlInstances[PARENT_CREATE_MODIFIED_IN_CHILD] ttl!!.set("${ttl.get()}$tag") return copyTtlValues(ttlInstances) } fun modifyParentTtlInstances(tag: String, ttlInstances: TtlInstances): TtlValues { // modify the parent key val ttl: TransmittableThreadLocal? = ttlInstances[PARENT_CREATE_MODIFIED_IN_CHILD] ttl!!.set("${ttl.get()}$tag") return copyTtlValues(ttlInstances) } fun newTtlInstanceAndPut(key: String, ttlInstances: TtlInstances): TransmittableThreadLocal { val ttl = object : TransmittableThreadLocal() { override fun toString(): String { return "${super.toString()}(${get()})" } } ttl.set(key) val old = ttlInstances.putIfAbsent(key, ttl) withClue("Already contains key $key") { old.shouldBeNull() } return ttl } fun printTtlInstances(ttlInstances: TtlInstances, title: String = "") { val headList = if (title.isBlank()) emptyList() else listOf("## $title [${Thread.currentThread().name}] ##") val valueString = ttlInstances.filterValues { it.get() != null } .map { (k, v) -> "$k: ${v.get()}" } .joinToString() val output = (headList + valueString + "^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^") .filter { it.isNotBlank() } .joinToString("\n") println(output) } //////////////////////////////////////////////////////////////////////////////// // TTL Values //////////////////////////////////////////////////////////////////////////////// typealias TtlValues = Map fun copyTtlValues(ttlInstances: TtlInstances): TtlValues = ttlInstances.filterValues { it.get() != null } .mapValues { (_, v) -> v.get() } fun assertTtlValues(expected: TtlValues, copied: TtlValues) { copied shouldBe expected } fun assertChildTtlValues(tag: String, values: TtlValues) { assertTtlValues( mapOf( PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD + tag, PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, CHILD_CREATE + tag to CHILD_CREATE + tag ), values ) } fun assertChildTtlValuesWithParentCreateAfterCreateChild(tag: String, values: TtlValues) { assertTtlValues( mapOf( PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD + tag, PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, CHILD_CREATE + tag to CHILD_CREATE + tag, PARENT_CREATE_AFTER_CREATE_CHILD to PARENT_CREATE_AFTER_CREATE_CHILD ), values ) } fun assertParentTtlValues(values: TtlValues) { assertTtlValues( mapOf( PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD, // restored after call! PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_AFTER_CREATE_CHILD to PARENT_CREATE_AFTER_CREATE_CHILD ), values ) } fun hasTtlAgentRun(): Boolean = TtlAgent.isTtlAgentLoaded().also { val key = "run-ttl-test-under-agent" if (it) { System.getProperties().containsKey(key).shouldBeTrue() System.getProperty(key) shouldBe "true" } else { System.getProperties().containsKey(key).shouldBeFalse() } } fun noTtlAgentRun(): Boolean = !hasTtlAgentRun() fun hasTtlAgentRunWithDisableInheritableForThreadPool(): Boolean = hasTtlAgentRun() and TtlAgent.isDisableInheritableForThreadPool().also { val key = "run-ttl-test-under-agent-with-disable-inheritable" if (System.getProperties().containsKey(key)) { System.getProperty(key) shouldBe "true" it.shouldBeTrue() hasTtlAgentRun().shouldBeTrue() } } fun hasTtlAgentRunWithEnableTimerTask(): Boolean = hasTtlAgentRun() and TtlAgent.isEnableTimerTask().also { val key = "run-ttl-test-under-agent-with-enable-timer-task" if (System.getProperties().containsKey(key)) { System.getProperty(key) shouldBe "true" it.shouldBeTrue() hasTtlAgentRun().shouldBeTrue() } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/CoroutineDemo.kt ================================================ package com.alibaba.demo.coroutine import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking /** * Kotlin coroutine related material: * * - Official docs: * - [Coroutines Guide - Kotlin Programming Language](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html) * - [Coroutine Context and Dispatchers - Kotlin Programming Language](https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html) * - [Structured concurrency – Roman Elizarov](https://medium.com/@elizarov/structured-concurrency-722d765aa952) * - [Kotlin/kotlin-coroutines-examples: Design documents and examples for coroutines in Kotlin - github.com](https://github.com/Kotlin/kotlin-coroutines-examples) * - others * - [Demystifying Kotlin Coroutines – ProAndroidDev](https://proandroiddev.com/demystifying-kotlin-coroutines-6fe1f410570b) * - [Kotlin coroutines in Spring - Code for glory](https://blog.alexnesterov.com/post/kotlin-coroutines-in-spring/) * - [Kotlin coroutines and Spring 5 - Code for glory](https://blog.alexnesterov.com/post/kotlin-coroutines-and-spring-5/) */ fun main(): Unit = runBlocking { println("[${Thread.currentThread().name}] main") val deferred = async(Dispatchers.IO) { println("[${Thread.currentThread().name}] async") "world" } println("[${Thread.currentThread().name}] Hello ${deferred.await()}!") } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/CoroutineThreadContextElementDemo.kt ================================================ package com.alibaba.demo.coroutine import kotlinx.coroutines.* private val threadLocal = ThreadLocal() // declare thread-local variable /** * [Thread-local data - Coroutine Context and Dispatchers - Kotlin Programming Language](https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html#thread-local-data) */ fun main() = runBlocking { threadLocal.set("main") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") val block: suspend CoroutineScope.() -> Unit = { println("Launch start, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") yield() println("After yield, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") } println() launch(block = block).join() println() launch(threadLocal.asContextElement(value = "launch"), block = block).join() println() launch(Dispatchers.Default, block = block).join() println() launch(Dispatchers.Default + threadLocal.asContextElement(value = "launch"), block = block).join() println() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/CoroutineThreadContextElementTest.kt ================================================ package com.alibaba.demo.coroutine import io.kotest.core.spec.style.AnnotationSpec import kotlinx.coroutines.* import mu.KotlinLogging import org.junit.Assert.* class CoroutineThreadContextElementTest : AnnotationSpec() { private val logger = KotlinLogging.logger {} @Test fun threadContextElement_passByValue(): Unit = runBlocking { val mainValue = "main-${System.currentTimeMillis()}" val launchValue = "launch-${System.currentTimeMillis()}" val testThread = Thread.currentThread() // String ThreadLocal, String is immutable value, can only be passed by value val threadLocal = ThreadLocal() threadLocal.set(mainValue) logger.info { "test thread - thread local value: ${threadLocal.get()}" } val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = launchValue)) { logger.info { "launch thread - launch start, thread local value: ${threadLocal.get()}" } assertEquals(launchValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()}" } assertEquals(launchValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val reset = "job-reset-${threadLocal.get()}" threadLocal.set(reset) assertEquals(reset, threadLocal.get()) delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()}" } // !!! After suspended delay function, reset ThreadLocal value is lost !!! // assertEquals(reset, threadLocal.get()) assertEquals(launchValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) } job.join() logger.info { "test thread - after launch, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) } @Test fun threadContextElement_passByReference(): Unit = runBlocking { data class Reference(var data: Int = 42) val mainValue = Reference() val launchValue = Reference(4242) val testThread = Thread.currentThread() // Reference ThreadLocal, mutable value, pass by reference val threadLocal = ThreadLocal() // declare thread-local variable threadLocal.set(mainValue) logger.info { "test thread - thread local value: ${threadLocal.get()}" } val job = launch(Dispatchers.Default + threadLocal.asContextElement(value = launchValue)) { logger.info { "launch thread - launch start, thread local value: ${threadLocal.get()}" } assertEquals(launchValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()}" } assertEquals(launchValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val reset = -42 threadLocal.get().data = reset delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()}" } assertEquals(Reference(reset), threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) } job.join() logger.info { "test thread - after launch in test thread, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) } @Test fun twoThreadContextElement(): Unit = runBlocking { val mainValue = "main-a-${System.currentTimeMillis()}" val testThread = Thread.currentThread() val anotherMainValue = "main-another-${System.currentTimeMillis()}" val threadLocal = ThreadLocal() // declare thread-local variable val anotherThreadLocal = ThreadLocal() // declare thread-local variable threadLocal.set(mainValue) anotherThreadLocal.set(anotherMainValue) logger.info { "test thread - thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } val launch1Value = "aLaunch1" launch(Dispatchers.Default + threadLocal.asContextElement(value = launch1Value)) { assertEquals(launch1Value, threadLocal.get()) assertNull(anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(launch1Value, threadLocal.get()) assertNull(anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val resetA = "job-reset-${threadLocal.get()}" threadLocal.set(resetA) val resetAnother = "job-reset-${anotherThreadLocal.get()}" anotherThreadLocal.set(resetAnother) logger.info { "launch thread - before delay set reset, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } // !!! After suspended delay function, reset ThreadLocal value is lost !!! // assertEquals(resetA, threadLocal.get()) assertEquals(launch1Value, threadLocal.get()) // !!! After suspended delay, ThreadLocal without ThreadContextElement is not clear !!! assertEquals(resetAnother, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) }.join() logger.info { "test thread - after launch1, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertEquals(anotherMainValue, anotherThreadLocal.get()) val launch2Value = "aLaunch2" val anotherLaunch2Value = "anotherLaunch2" launch( Dispatchers.Default + threadLocal.asContextElement(value = launch2Value) + anotherThreadLocal.asContextElement( value = anotherLaunch2Value ) ) { logger.info { "launch thread - launch start, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(launch2Value, threadLocal.get()) assertEquals(anotherLaunch2Value, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(launch2Value, threadLocal.get()) assertEquals(anotherLaunch2Value, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val resetA = "job-reset-${threadLocal.get()}" threadLocal.set(resetA) val resetAnother = "job-reset-${anotherThreadLocal.get()}" anotherThreadLocal.set(resetAnother) logger.info { "launch thread - before delay set reset, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } // !!! After suspended delay function, reset ThreadLocal value is lost !!! // assertEquals(resetA, threadLocal.get()) // assertEquals(resetAnother, anotherThreadLocal.get()) assertEquals(launch2Value, threadLocal.get()) assertEquals(anotherLaunch2Value, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) }.join() logger.info { "test thread - after launch2, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertEquals(anotherMainValue, anotherThreadLocal.get()) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/CoroutineThreadLocalContextContinuationInterceptorDemo.kt ================================================ package com.alibaba.demo.coroutine import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import kotlin.coroutines.AbstractCoroutineContextElement import kotlin.coroutines.Continuation import kotlin.coroutines.ContinuationInterceptor import kotlin.coroutines.CoroutineContext /** * related material: * * - [Work with ThreadLocal-sensitive Components #119 - Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines/issues/119) * - [How to use code that relies on ThreadLocal with Kotlin coroutines - Stack Overflow](https://stackoverflow.com/questions/46227462/how-to-use-code-that-relies-on-threadlocal-with-kotlin-coroutines/46227463) * - [README.md of Module kotlinx-coroutines-slf4j - Kotlin/kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines/blob/master/integration/kotlinx-coroutines-slf4j/README.md) */ fun main(): Unit = runBlocking { myThreadLocal.set(MyData("main value")) async(Dispatchers.IO) { "world(${myThreadLocal.get().data})" }.run { println("Hello ${await()}!") } async(MyThreadLocalContextContinuationInterceptor(myThreadLocal.get(), Dispatchers.IO)) { "world(${myThreadLocal.get().data})" }.run { println("Hello ${await()}!") } } private val myThreadLocal = object : ThreadLocal() { override fun initialValue(): MyData { return MyData("init value") } } private class MyThreadLocalContextContinuationInterceptor( private var myData: MyData, private val dispatcher: ContinuationInterceptor ) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { override fun interceptContinuation(continuation: Continuation): Continuation = dispatcher.interceptContinuation(Wrapper(continuation)) inner class Wrapper(private val continuation: Continuation) : Continuation { private inline fun wrap(block: () -> Unit) { try { myThreadLocal.set(myData) block() } finally { myData = myThreadLocal.get() } } override val context: CoroutineContext get() = continuation.context override fun resumeWith(result: Result) = wrap { continuation.resumeWith(result) } } } private data class MyData(val data: String) ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/ttl_intergration/TtlCoroutineContext.kt ================================================ package com.alibaba.demo.coroutine.ttl_intergration import com.alibaba.ttl.TransmittableThreadLocal.Transmitter.* import kotlinx.coroutines.ThreadContextElement import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * @see [kotlinx.coroutines.asContextElement] */ fun ttlContext(): CoroutineContext = // if (TtlAgent.isTtlAgentLoaded()) // FIXME Open the if when implement TtlAgent for koroutine // EmptyCoroutineContext // else TtlElement() /** * @see [kotlinx.coroutines.internal.ThreadLocalElement] */ internal class TtlElement : ThreadContextElement { companion object Key : CoroutineContext.Key override val key: CoroutineContext.Key<*> get() = Key private var captured: Any = capture() override fun updateThreadContext(context: CoroutineContext): Any = replay(captured) override fun restoreThreadContext(context: CoroutineContext, oldState: Any) { captured = capture() // FIXME This capture operation is a MUST, WHY? This operation is too expensive?! restore(oldState) } // this method is overridden to perform value comparison (==) on key override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = if (Key == key) EmptyCoroutineContext else this // this method is overridden to perform value comparison (==) on key override operator fun get(key: CoroutineContext.Key): E? = @Suppress("UNCHECKED_CAST") if (Key == key) this as E else null } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/ttl_intergration/usage/TtlCoroutineContextDemo.kt ================================================ package com.alibaba.demo.coroutine.ttl_intergration.usage import com.alibaba.demo.coroutine.ttl_intergration.ttlContext import com.alibaba.ttl.TransmittableThreadLocal import kotlinx.coroutines.* private val threadLocal = TransmittableThreadLocal() // declare thread-local variable /** * [Thread-local data - Coroutine Context and Dispatchers - Kotlin Programming Language](https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html#thread-local-data) */ fun main(): Unit = runBlocking { val block: suspend CoroutineScope.() -> Unit = { println("Launch start, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") threadLocal.set("!reset!") println("After reset, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") delay(5) println("After yield, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") } threadLocal.set("main") println("======================\nEmpty Coroutine Context\n======================") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") launch(block = block).join() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") threadLocal.set("main") println() println("======================\nTTL Coroutine Context\n======================") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") launch(ttlContext(), block = block).join() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") threadLocal.set("main") println() println("======================\nDispatchers.Default Coroutine Context\n======================") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") launch(Dispatchers.Default, block = block).join() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") threadLocal.set("main") println() println("======================\nDispatchers.Default + TTL Coroutine Context\n======================") println("Pre-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") launch(Dispatchers.Default + ttlContext(), block = block).join() println("Post-main, current thread: ${Thread.currentThread()}, thread local value: ${threadLocal.get()}") } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/coroutine/ttl_intergration/usage/TtlCoroutineContextTest.kt ================================================ package com.alibaba.demo.coroutine.ttl_intergration.usage import com.alibaba.demo.coroutine.ttl_intergration.ttlContext import com.alibaba.ttl.TransmittableThreadLocal import io.kotest.core.spec.style.AnnotationSpec import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mu.KotlinLogging import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals class TtlCoroutineContextTest : AnnotationSpec() { private val logger = KotlinLogging.logger {} @Test fun threadContextElement_passByValue(): Unit = runBlocking { val mainValue = "main-${System.currentTimeMillis()}" val testThread = Thread.currentThread() // String ThreadLocal, String is immutable value, can only be passed by value val threadLocal = TransmittableThreadLocal() threadLocal.set(mainValue) logger.info { "test thread - thread local value: ${threadLocal.get()}" } val job = launch(Dispatchers.Default + ttlContext()) { logger.info { "launch thread - launch start, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val reset = "job-reset-${threadLocal.get()}" threadLocal.set(reset) assertEquals(reset, threadLocal.get()) delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()}" } assertEquals(reset, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) } job.join() logger.info { "test thread - after launch, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) } @Test fun threadContextElement_passByReference(): Unit = runBlocking { data class Reference(var data: Int = 42) val mainValue = Reference() val testThread = Thread.currentThread() // Reference ThreadLocal, mutable value, pass by reference val threadLocal = TransmittableThreadLocal() // declare thread-local variable threadLocal.set(mainValue) logger.info { "test thread - thread local value: ${threadLocal.get()}" } val job = launch(Dispatchers.Default + ttlContext()) { logger.info { "launch thread - launch start, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val reset = -42 threadLocal.get().data = reset delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()}" } assertEquals(Reference(reset), threadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) } job.join() logger.info { "test thread - after launch thread local value: ${threadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) } @Test fun twoThreadContextElement(): Unit = runBlocking { val mainValue = "main-a-${System.currentTimeMillis()}" val anotherMainValue = "main-another-${System.currentTimeMillis()}" val testThread = Thread.currentThread() val threadLocal = TransmittableThreadLocal() // declare thread-local variable val anotherThreadLocal = TransmittableThreadLocal() // declare thread-local variable threadLocal.set(mainValue) anotherThreadLocal.set(anotherMainValue) logger.info { "test thread - thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } launch(Dispatchers.Default + ttlContext()) { logger.info { "launch thread - launch start, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertEquals(anotherMainValue, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) delay(5) logger.info { "launch thread - after delay, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertEquals(anotherMainValue, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) val resetA = "job-reset-${threadLocal.get()}" threadLocal.set(resetA) val resetAnother = "job-reset-${anotherThreadLocal.get()}" anotherThreadLocal.set(resetAnother) logger.info { "launch thread - before delay set reset, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } delay(5) logger.info { "launch thread - after delay set reset, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(resetA, threadLocal.get()) assertEquals(resetAnother, anotherThreadLocal.get()) assertNotEquals(testThread, Thread.currentThread()) }.join() logger.info { "test thread - after launch2, thread local value: ${threadLocal.get()} | ${anotherThreadLocal.get()}" } assertEquals(mainValue, threadLocal.get()) assertEquals(anotherMainValue, anotherThreadLocal.get()) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/cow/CowDemo.kt ================================================ package com.alibaba.demo.cow import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.TtlExecutors import java.util.concurrent.Executors import java.util.concurrent.TimeUnit fun main() { val threadPool = Executors.newCachedThreadPool().let { expandThreadPool(it) TtlExecutors.getTtlExecutorService(it) }!! val traceContext = object : TransmittableThreadLocal() { override fun initialValue(): Trace = Trace("init", Span("first", 0)) override fun copy(parentValue: Trace): Trace = parentValue.copy() // shadow copy Trace, this is fast override fun childValue(parentValue: Trace): Trace = parentValue.copy() // shadow copy Trace, this is fast fun increaseSpan() { get().run { // COW the Span object in Trace span = span.copy(id = "${span.id} + PONG", counter = span.counter + 1) } } override fun toString(): String { return "${get()}[${super.toString()}]" } } fun printTtlInfo() { println("${Thread.currentThread().name}: $traceContext") } printTtlInfo() threadPool.execute { printTtlInfo() traceContext.increaseSpan() printTtlInfo() threadPool.execute { printTtlInfo() traceContext.increaseSpan() printTtlInfo() } } Thread.sleep(100) threadPool.shutdown() threadPool.awaitTermination(1, TimeUnit.SECONDS) } private data class Trace(var name: String, var span: Span) private data class Span(val id: String, val counter: Int) ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/distributed_tracer/refcount/DistributedTracerUseDemo.kt ================================================ package com.alibaba.demo.distributed_tracer.refcount import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.TtlExecutors import java.lang.Thread.sleep import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread /** * DistributedTracer(DT) use demo. * * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { rpcInvokeIn() sleep(100) } private fun rpcInvokeIn() { //////////////////////////////////////////////// // DistributedTracer Framework Code //////////////////////////////////////////////// // Get Trace Id and Span Id from RPC Context val traceId = "traceId_XXXYYY" val baseSpanId = "1.1" transferInfo.set(DtTransferInfo(traceId, baseSpanId)) traceId2LeafSpanIdInfo[traceId] = LeafSpanIdInfo() increaseSpanIdRefCount() //////////////////////////////////////////////// // Biz Code //////////////////////////////////////////////// syncMethod() //////////////////////////////////////////////// // DistributedTracer Framework Code //////////////////////////////////////////////// decreaseSpanIdRefCount() } private val executorService = Executors.newFixedThreadPool(1) { r: Runnable -> Thread(r, "Executors").apply { isDaemon = true } }.let { TtlExecutors.getTtlExecutorService(it) }!! private fun syncMethod() { // async call by TTL Executor, Test OK! executorService.submit { asyncMethod() } // async call by new Thread // FIXME Bug!! 没有 Increase/Decrease reference counter操作! thread(name = "Thread-by-new") { syncMethod_ByNewThread() } invokeServerWithRpc("server 1") } private fun asyncMethod() { invokeServerWithRpc("server 2") } private fun syncMethod_ByNewThread() { invokeServerWithRpc("server 3") } // RPC invoke private fun invokeServerWithRpc(server: String) { //////////////////////////////////////////////// // DistributedTracer Framework Code //////////////////////////////////////////////// val leafSpanCurrent = increaseLeafSpanCurrentAndReturn() // Set RpcContext // Mocked, should use RPC util to get Rpc Context instead val rpcContext = ConcurrentHashMap() rpcContext["traceId"] = transferInfo.get()!!.traceId rpcContext["spanId"] = transferInfo.get()!!.baseSpanId + "." + leafSpanCurrent // Do Rpc // ... System.out.printf("Do Rpc invocation to server %s with %s%n", server, rpcContext) } ////////////////////////////////////////////////////////////////////////////////////////////////////////// internal class DtTransferInfo(val traceId: String, val baseSpanId: String) internal class LeafSpanIdInfo(val current: AtomicInteger = AtomicInteger(1), val refCounter: AtomicInteger = AtomicInteger(0)) private val transferInfo = object : TransmittableThreadLocal() { /* @Override protected DtTransferInfo childValue(DtTransferInfo parentValue) { // **注意**: // 新建线程时,从父线程继承值时,计数加1 // 对应线程结束时,没有回调以清理ThreadLocal中的Context!,Bug!! // InheritableThreadLocal 没有提供 对应的拦截方法。。。 计数不配对了。。。 // 但是一个线程就一个Context没清,线程数有限,Context占用内存一般很小,可以接受。 increaseSpanIdRefCount(); return super.childValue(parentValue); } */ override fun beforeExecute() { super.beforeExecute() increaseSpanIdRefCount() } override fun afterExecute() { decreaseSpanIdRefCount() } } private val traceId2LeafSpanIdInfo = ConcurrentHashMap() private fun increaseSpanIdRefCount() { val traceId = transferInfo.get().traceId val refCounter = traceId2LeafSpanIdInfo[traceId]!!.refCounter.incrementAndGet() System.out.printf("DEBUG: Increase reference counter(%s) for traceId %s in thread %s%n", refCounter, traceId, Thread.currentThread().name) } private fun decreaseSpanIdRefCount() { val traceId = transferInfo.get().traceId val leafSpanIdInfo = traceId2LeafSpanIdInfo[traceId] val refCounter = leafSpanIdInfo!!.refCounter.decrementAndGet() System.out.printf("DEBUG: Decrease reference counter(%s) for traceId %s in thread %s%n", refCounter, traceId, Thread.currentThread().name) if (refCounter == 0) { traceId2LeafSpanIdInfo.remove(traceId) System.out.printf("DEBUG: Clear traceId2LeafSpanIdInfo for traceId %s in thread %s%n", traceId, Thread.currentThread().name) } else if (refCounter < 0) { throw IllegalStateException("Leaf Span Id Info Reference counter has Bug!!") } } private fun increaseLeafSpanCurrentAndReturn(): Int { val traceId = transferInfo.get()!!.traceId return traceId2LeafSpanIdInfo[traceId]!!.current.getAndIncrement() } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/distributed_tracer/weakref/DistributedTracerUseDemo_WeakReferenceInsteadOfRefCounter.kt ================================================ package com.alibaba.demo.distributed_tracer.weakref import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.TtlExecutors import java.lang.Thread.sleep import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import kotlin.concurrent.thread private val executorService: ExecutorService = Executors.newFixedThreadPool(1) { r: Runnable -> Thread(r, "Executors").apply { isDaemon = true } }.let { // ensure threads in pool is pre-created. expandThreadPool(it) TtlExecutors.getTtlExecutorService(it) }!! /** * DistributedTracer(DT) use demo. * * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { for (i in 0..42) { rpcInvokeIn() } println("WARN: finished rpc invocation") // help to check GC status sleep(200) println("WARN: Call System.gc") System.gc() println("WARN: Called System.gc") sleep(100) println("Exit Main.") } /////////////////////////////////////////////////////////////////////// private fun rpcInvokeIn() { //////////////////////////////////////////////// // DistributedTracer Framework Code //////////////////////////////////////////////// // Get Trace Id and Span Id from RPC Context val traceId = "traceId_XXXYYY" + traceIdCounter.getAndIncrement() val baseSpanId = "1.1" val leafSpanIdInfo = LeafSpanIdInfo() transferInfo.set(DtTransferInfo(traceId, baseSpanId, leafSpanIdInfo)) //////////////////////////////////////////////// // Biz Code //////////////////////////////////////////////// syncMethod() //////////////////////////////////////////////// // DistributedTracer Framework Code //////////////////////////////////////////////// System.out.printf("Finished Rpc call %s with span %s.%n", traceId, leafSpanIdInfo) // release context in ThreadLocal, avoid to be hold by thread, GC friendly. transferInfo.remove() } private fun syncMethod() { // async call by TTL Executor, Test OK! executorService.submit { asyncMethod() } // async call by new Thread thread(name = "Thread-by-new") { syncMethod_ByNewThread() } invokeServerWithRpc("server 1") } private fun asyncMethod() { sleep(3) invokeServerWithRpc("server 2") } private fun syncMethod_ByNewThread() { sleep(2) invokeServerWithRpc("server 3") } // RPC invoke private fun invokeServerWithRpc(server: String) { //////////////////////////////////////////////// // DistributedTracer Framework Code //////////////////////////////////////////////// val leafSpanCurrent = increaseLeafSpanCurrentAndReturn() // Set RpcContext // Mocked, should use RPC util to get Rpc Context instead val rpcContext = ConcurrentHashMap() rpcContext["traceId"] = transferInfo.get()!!.traceId rpcContext["spanId"] = transferInfo.get()!!.baseSpanId + "." + leafSpanCurrent // Do Rpc // ... System.out.printf("Do Rpc invocation to server %s with %s%n", server, rpcContext) } /////////////////////////////////////////////////////////////////////// // Span id management /////////////////////////////////////////////////////////////////////// private val traceIdCounter = AtomicLong() internal data class LeafSpanIdInfo(val current: AtomicInteger = AtomicInteger(1)) internal data class DtTransferInfo(val traceId: String, val baseSpanId: String, val leafSpanIdInfo: LeafSpanIdInfo) { // Output GC operation // How to implement finalize() in kotlin? - https://stackoverflow.com/questions/43784161 @Suppress("unused", "ProtectedInFinal") protected fun finalize() { System.out.printf("DEBUG: gc DtTransferInfo traceId %s in thread %s: %s%n", traceId, Thread.currentThread().name, this) } } private val transferInfo = TransmittableThreadLocal() private fun increaseLeafSpanCurrentAndReturn(): Int = transferInfo.get()!!.leafSpanIdInfo.current.getAndIncrement() ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/forkjoinpool/ForkJoinPoolDemo.kt ================================================ package com.alibaba.demo.forkjoinpool import java.util.concurrent.ForkJoinPool import java.util.concurrent.RecursiveTask /** * ForkJoinPool use demo. * * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val pool = ForkJoinPool.commonPool() val result = pool.invoke(SumTask(1..1000)) println("computed result: $result") // result is 500500 } internal class SumTask(private val numbers: IntRange, private val forkLevel: Int = 0) : RecursiveTask() { override fun compute(): Int = if (numbers.count() <= 16) { println(String.format("direct compute %9s[%4s] at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // compute directly numbers.sum() } else { println(String.format("fork compute %9s[%4s] at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // split task val middle = numbers.first + numbers.count() / 2 val nextForkLevel = forkLevel + 1 val taskLeft = SumTask(numbers.first until middle, nextForkLevel) val taskRight = SumTask(middle..numbers.last, nextForkLevel) // fork-join compute taskLeft.fork() taskRight.fork() taskLeft.join() + taskRight.join() } } /* Output: fork compute 1..1000[1000] at fork level 0 @ thread main fork compute 1..500[ 500] at fork level 1 @ thread ForkJoinPool.commonPool-worker-19 fork compute 501..1000[ 500] at fork level 1 @ thread ForkJoinPool.commonPool-worker-5 fork compute 501..750[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-23 fork compute 751..1000[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-13 fork compute 251..500[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-27 fork compute 501..625[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-17 fork compute 1..250[ 250] at fork level 2 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..875[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-13 fork compute 876..1000[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-3 fork compute 251..375[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-27 fork compute 376..500[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-7 fork compute 751..812[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 1..125[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-9 fork compute 376..437[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 fork compute 876..937[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 626..750[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-31 fork compute 376..406[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 1..62[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..781[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 fork compute 501..562[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 fork compute 251..312[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-27 fork compute 626..687[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 direct compute 751..765[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 876..906[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 563..625[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-21 fork compute 501..531[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 1..31[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 876..890[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 376..390[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 563..593[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 1..15[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 766..781[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 626..656[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 391..406[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 251..281[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 direct compute 16..31[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 563..577[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 891..906[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 407..437[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 32..62[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 578..593[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 501..515[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 407..421[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 907..937[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 251..265[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 626..640[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 782..812[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 907..921[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 266..281[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 516..531[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 422..437[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 594..625[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 922..937[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 32..46[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 532..562[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 282..312[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 direct compute 782..796[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 47..62[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 641..656[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 797..812[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 63..125[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 direct compute 282..296[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 532..546[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 938..1000[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 63..93[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 594..609[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 438..500[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 direct compute 547..562[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 297..312[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 fork compute 813..875[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 657..687[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 fork compute 438..468[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 313..375[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-27 direct compute 610..625[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 63..77[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 438..452[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 938..968[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 344..375[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 78..93[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 688..750[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 fork compute 94..125[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 938..952[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 688..718[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 direct compute 94..109[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 313..343[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 direct compute 657..671[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 813..843[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 110..125[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 672..687[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 fork compute 719..750[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 813..827[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 688..702[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 953..968[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 453..468[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 344..359[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 703..718[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 828..843[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 719..734[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 126..250[ 125] at fork level 3 @ thread ForkJoinPool.commonPool-worker-9 direct compute 313..327[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 fork compute 844..875[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 735..750[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 360..375[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 469..500[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 969..1000[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 860..875[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 188..250[ 63] at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 direct compute 485..500[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 direct compute 969..984[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 844..859[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 126..187[ 62] at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 direct compute 328..343[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 fork compute 219..250[ 32] at fork level 5 @ thread ForkJoinPool.commonPool-worker-23 fork compute 188..218[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 126..156[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 985..1000[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 469..484[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 188..202[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 219..234[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 direct compute 203..218[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 157..187[ 31] at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 126..140[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 141..156[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 235..250[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-5 direct compute 172..187[ 16] at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 157..171[ 15] at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 computed result: 500500 */ ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/forkjoinpool/ParallelStreamDemo.kt ================================================ package com.alibaba.demo.forkjoinpool import java.util.concurrent.ConcurrentSkipListSet /** * Parallel Stream use demo. * * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { println("availableProcessors: ${Runtime.getRuntime().availableProcessors()}") val threadNames: MutableSet = ConcurrentSkipListSet() (0..100).toList().stream().parallel().mapToInt { threadNames.add(Thread.currentThread().name) Thread.sleep(10) println("map $it @ thread ${Thread.currentThread().name}") it }.sum().let { println("sum result: $it") } println(threadNames.joinToString( separator = "\n\t", prefix = "run threads(${threadNames.size}):\n\t" )) } /* Output: availableProcessors: 12 map 78 @ thread ForkJoinPool.commonPool-worker-7 map 76 @ thread ForkJoinPool.commonPool-worker-21 map 91 @ thread ForkJoinPool.commonPool-worker-19 map 71 @ thread ForkJoinPool.commonPool-worker-13 map 65 @ thread main map 97 @ thread ForkJoinPool.commonPool-worker-3 map 32 @ thread ForkJoinPool.commonPool-worker-5 map 15 @ thread ForkJoinPool.commonPool-worker-9 map 79 @ thread ForkJoinPool.commonPool-worker-17 map 82 @ thread ForkJoinPool.commonPool-worker-27 map 53 @ thread ForkJoinPool.commonPool-worker-31 map 57 @ thread ForkJoinPool.commonPool-worker-23 map 77 @ thread ForkJoinPool.commonPool-worker-21 map 86 @ thread ForkJoinPool.commonPool-worker-7 map 72 @ thread ForkJoinPool.commonPool-worker-13 map 92 @ thread ForkJoinPool.commonPool-worker-19 map 66 @ thread main map 83 @ thread ForkJoinPool.commonPool-worker-27 map 58 @ thread ForkJoinPool.commonPool-worker-23 map 54 @ thread ForkJoinPool.commonPool-worker-31 map 98 @ thread ForkJoinPool.commonPool-worker-3 map 33 @ thread ForkJoinPool.commonPool-worker-5 map 16 @ thread ForkJoinPool.commonPool-worker-9 map 80 @ thread ForkJoinPool.commonPool-worker-17 map 75 @ thread ForkJoinPool.commonPool-worker-21 map 87 @ thread ForkJoinPool.commonPool-worker-7 map 93 @ thread ForkJoinPool.commonPool-worker-19 map 73 @ thread ForkJoinPool.commonPool-worker-13 map 67 @ thread main map 81 @ thread ForkJoinPool.commonPool-worker-27 map 56 @ thread ForkJoinPool.commonPool-worker-23 map 55 @ thread ForkJoinPool.commonPool-worker-31 map 17 @ thread ForkJoinPool.commonPool-worker-9 map 99 @ thread ForkJoinPool.commonPool-worker-3 map 31 @ thread ForkJoinPool.commonPool-worker-5 map 84 @ thread ForkJoinPool.commonPool-worker-17 map 63 @ thread ForkJoinPool.commonPool-worker-19 map 89 @ thread ForkJoinPool.commonPool-worker-7 map 74 @ thread ForkJoinPool.commonPool-worker-13 map 95 @ thread ForkJoinPool.commonPool-worker-21 map 62 @ thread ForkJoinPool.commonPool-worker-27 map 60 @ thread ForkJoinPool.commonPool-worker-23 map 100 @ thread ForkJoinPool.commonPool-worker-3 map 51 @ thread ForkJoinPool.commonPool-worker-31 map 13 @ thread ForkJoinPool.commonPool-worker-9 map 35 @ thread ForkJoinPool.commonPool-worker-5 map 85 @ thread ForkJoinPool.commonPool-worker-17 map 64 @ thread ForkJoinPool.commonPool-worker-19 map 90 @ thread ForkJoinPool.commonPool-worker-7 map 69 @ thread ForkJoinPool.commonPool-worker-13 map 96 @ thread ForkJoinPool.commonPool-worker-21 map 94 @ thread ForkJoinPool.commonPool-worker-27 map 61 @ thread ForkJoinPool.commonPool-worker-23 map 14 @ thread ForkJoinPool.commonPool-worker-9 map 36 @ thread ForkJoinPool.commonPool-worker-5 map 44 @ thread ForkJoinPool.commonPool-worker-3 map 52 @ thread ForkJoinPool.commonPool-worker-31 map 88 @ thread ForkJoinPool.commonPool-worker-17 map 68 @ thread ForkJoinPool.commonPool-worker-19 map 40 @ thread ForkJoinPool.commonPool-worker-7 map 70 @ thread ForkJoinPool.commonPool-worker-13 map 48 @ thread ForkJoinPool.commonPool-worker-21 map 46 @ thread ForkJoinPool.commonPool-worker-27 map 59 @ thread ForkJoinPool.commonPool-worker-23 map 12 @ thread ForkJoinPool.commonPool-worker-9 map 34 @ thread ForkJoinPool.commonPool-worker-5 map 50 @ thread ForkJoinPool.commonPool-worker-31 map 38 @ thread ForkJoinPool.commonPool-worker-17 map 45 @ thread ForkJoinPool.commonPool-worker-3 map 41 @ thread ForkJoinPool.commonPool-worker-7 map 43 @ thread ForkJoinPool.commonPool-worker-19 map 28 @ thread ForkJoinPool.commonPool-worker-13 map 49 @ thread ForkJoinPool.commonPool-worker-21 map 47 @ thread ForkJoinPool.commonPool-worker-27 map 7 @ thread ForkJoinPool.commonPool-worker-23 map 21 @ thread ForkJoinPool.commonPool-worker-9 map 25 @ thread ForkJoinPool.commonPool-worker-3 map 3 @ thread ForkJoinPool.commonPool-worker-31 map 26 @ thread ForkJoinPool.commonPool-worker-5 map 39 @ thread ForkJoinPool.commonPool-worker-17 map 42 @ thread ForkJoinPool.commonPool-worker-7 map 19 @ thread ForkJoinPool.commonPool-worker-19 map 29 @ thread ForkJoinPool.commonPool-worker-13 map 1 @ thread ForkJoinPool.commonPool-worker-21 map 0 @ thread ForkJoinPool.commonPool-worker-27 map 8 @ thread ForkJoinPool.commonPool-worker-23 map 27 @ thread ForkJoinPool.commonPool-worker-5 map 22 @ thread ForkJoinPool.commonPool-worker-9 map 37 @ thread ForkJoinPool.commonPool-worker-17 map 4 @ thread ForkJoinPool.commonPool-worker-3 map 10 @ thread ForkJoinPool.commonPool-worker-31 map 20 @ thread ForkJoinPool.commonPool-worker-19 map 18 @ thread ForkJoinPool.commonPool-worker-7 map 30 @ thread ForkJoinPool.commonPool-worker-13 map 2 @ thread ForkJoinPool.commonPool-worker-21 map 23 @ thread ForkJoinPool.commonPool-worker-23 map 6 @ thread ForkJoinPool.commonPool-worker-27 map 9 @ thread ForkJoinPool.commonPool-worker-5 map 5 @ thread ForkJoinPool.commonPool-worker-3 map 11 @ thread ForkJoinPool.commonPool-worker-31 map 24 @ thread ForkJoinPool.commonPool-worker-23 sum result: 5050 run threads(12): ForkJoinPool.commonPool-worker-13 ForkJoinPool.commonPool-worker-17 ForkJoinPool.commonPool-worker-19 ForkJoinPool.commonPool-worker-21 ForkJoinPool.commonPool-worker-23 ForkJoinPool.commonPool-worker-27 ForkJoinPool.commonPool-worker-3 ForkJoinPool.commonPool-worker-31 ForkJoinPool.commonPool-worker-5 ForkJoinPool.commonPool-worker-7 ForkJoinPool.commonPool-worker-9 main */ ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/scheduled_thread_pool_executor/ScheduledFutureTaskDemo.kt ================================================ package com.alibaba.demo.scheduled_thread_pool_executor import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit /** * ScheduledThreadPoolExecutor usage demo for Issue 148 * https://github.com/alibaba/transmittable-thread-local/issues/148 */ fun main() { val scheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(10) val task = Runnable { println("I'm a Runnable task, I'm working...") } val scheduledFuture = scheduledThreadPoolExecutor.scheduleWithFixedDelay(task, 500, 500, TimeUnit.MILLISECONDS) Thread.sleep(2_000) println("cancel") val cancelResult = scheduledFuture.cancel(false) println("canceled: $cancelResult") // scheduled task cancel success! Thread.sleep(2_000) scheduledThreadPoolExecutor.shutdown() println("Bye") } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/session_cache/SessionCacheDemo.kt ================================================ package com.alibaba.demo.session_cache import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TtlRunnable import com.alibaba.ttl.threadpool.TtlExecutors import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.booleans.shouldBeTrue import io.reactivex.Flowable import io.reactivex.plugins.RxJavaPlugins import io.reactivex.schedulers.Schedulers import java.util.concurrent.* class SessionCacheDemo : AnnotationSpec() { @Test fun invokeInThreadOfThreadPool() { val bizService = BizService() val printer: () -> Unit = { System.out.printf("[%20s] cache: %s%n", Thread.currentThread().name, bizService.getCacheItem()) } executorService.submit(Callable { bizService.getItemByCache().also { printer() } }).get() printer() // here service invocation will use item cache bizService.getItemByCache() printer() } @Test fun invokeInThreadOfRxJava() { val bizService = BizService() val printer: (Item) -> Unit = { System.out.printf("[%30s] cache: %s%n", Thread.currentThread().name, bizService.getCacheItem()) } Flowable.just(bizService) .observeOn(Schedulers.io()) .map(BizService::getItemByCache) .doOnNext(printer) .blockingSubscribe(printer) // here service invocation will use item cache bizService.getItemByCache() .let(printer) } @BeforeAll fun beforeAll() { // expand Schedulers.io() (0 until Runtime.getRuntime().availableProcessors() * 2) .map { FutureTask { Thread.sleep(10) it }.apply { Schedulers.io().scheduleDirect(this) } } .forEach { it.get() } // TTL integration for RxJava RxJavaPlugins.setScheduleHandler(TtlRunnable::get) } @AfterAll fun afterAll() { executorService.shutdown() // Fail to shut down thread pool executorService.awaitTermination(1, TimeUnit.SECONDS).shouldBeTrue() } @After fun tearDown() { BizService.clearCache() } companion object { private val executorService = Executors.newFixedThreadPool(3).let { expandThreadPool(it) // TTL integration for thread pool TtlExecutors.getTtlExecutorService(it)!! } } } /** * Mock Service */ private class BizService { init { // NOTE: AVOID cache object lazy init getCache() } fun getItem(): Item = Item(ThreadLocalRandom.current().nextInt(0, 10_000)) /** * get biz data, usually use spring cache. here is simple implementation */ fun getItemByCache(): Item { return getCache().computeIfAbsent(ONLY_KEY) { getItem() } } fun getCacheItem(): Item? = getCache()[ONLY_KEY] companion object { private const val ONLY_KEY = "ONLY_KEY" private val cacheContext = object : TransmittableThreadLocal>() { // init cache override fun initialValue(): ConcurrentMap = ConcurrentHashMap() } private fun getCache() = cacheContext.get() fun clearCache() { getCache().clear() } } } /** * Mock Cache Data */ private data class Item(val id: Int) ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/timer/TimerTaskDemo.kt ================================================ package com.alibaba.demo.timer import java.text.SimpleDateFormat import java.util.* /** * @see [Java Timer TimerTask Example](https://www.journaldev.com/1050/java-timer-timertask-example) */ fun main() { val timerTask = MyTimerTask() // running timer task as daemon thread val timer = Timer(true) timer.scheduleAtFixedRate(timerTask, 0, 300) println("TimerTask scheduled") // cancel after sometime Thread.sleep(1_000) timer.cancel() println("TimerTask cancelled") Thread.sleep(300) } class MyTimerTask : TimerTask() { override fun run() { val format = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") println("Timer task started at: ${format.format(Date())}") Thread.sleep(200) println("Timer task finished at: ${format.format(Date())}") } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/CustomizedBlockingQueueWithTtlDemo.java ================================================ package com.alibaba.demo.ttl; import com.alibaba.ttl.TtlRunnable; import com.alibaba.ttl.threadpool.TtlExecutors; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Simple demo code for issue * https://github.com/alibaba/transmittable-thread-local/issues/340 */ public class CustomizedBlockingQueueWithTtlDemo { public static void main(String[] args) throws Exception { final MyBlockingQueue myBlockingQueue = new MyBlockingQueue(); final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 1, 1, 1L, TimeUnit.SECONDS, myBlockingQueue); final ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor); ttlExecutorService.execute(new MyTask("accept-1")); ttlExecutorService.execute(new MyTask("DISCARDED")); ttlExecutorService.execute(new MyTask("accept-2")); ttlExecutorService.shutdown(); if (!ttlExecutorService.awaitTermination(10, TimeUnit.SECONDS)) { throw new IllegalStateException("Fail to shutdown executor service"); } } private static class MyTask implements Runnable { private final String msg; private MyTask(String msg) { this.msg = msg; } @Override public void run() { System.out.println("MyTask: " + msg); } } private static class MyBlockingQueue extends ArrayBlockingQueue { public MyBlockingQueue() { super(16); } @Override public boolean offer(final Runnable runnable) { // unwrap TtlRunnable first final Runnable unwrap = TtlRunnable.unwrap(runnable); final MyTask myTask = (MyTask) unwrap; if (myTask.msg.startsWith("accept-")) { // ignore result/return value of offer, BAD!! // does not follow the contract of method offer // this is just a simple demo! super.offer(runnable); } // always return true even if discard the task, BAD!! // does not follow the contract of method offer // this is just a simple demo! return true; } @Override public void put(final Runnable runnable) throws InterruptedException { // unwrap TtlRunnable first final Runnable unwrap = TtlRunnable.unwrap(runnable); final MyTask myTask = (MyTask) unwrap; // discard task if not satisfied, BAD!! // does not follow the contract of method put // this is just a simple demo! if (myTask.msg.startsWith("accept-")) { super.put(runnable); } } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/SimpleDemo.kt ================================================ package com.alibaba.demo.ttl import com.alibaba.ttl.TransmittableThreadLocal import kotlin.concurrent.thread /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val context = TransmittableThreadLocal() context.set("value-set-in-parent") println("[parent thread] set ${context.get()}") ///////////////////////////////////// // create sub-thread ///////////////////////////////////// thread { val value = context.get() println("[child thread] get $value") }.join() println("[parent thread] get ${context.get()}") } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/TtlExecutorServiceWithPriorityBlockingQueueDemo.kt ================================================ package com.alibaba.demo.ttl import com.alibaba.ttl.threadpool.TtlExecutors import java.lang.Thread.sleep import java.util.concurrent.BlockingQueue import java.util.concurrent.PriorityBlockingQueue import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit fun main() { demoSubmitComparableTaskToTtlExecutorServiceWithPriorityBlockingQueue() demoSubmitOrderTaskToTtlExecutorServiceWithPriorityBlockingQueue() } /** * Demo for cooperation TTL executor(ThreadPoolExecutor) with PriorityBlockingQueue, for Comparable Runnable. * * if you use TTL Agent, no extra work(getTtlRunnableUnwrapComparatorForComparableRunnable) is need. * aka. rewriting PriorityBlockingQueue by TTL Agent automatically and transparently. */ fun demoSubmitComparableTaskToTtlExecutorServiceWithPriorityBlockingQueue() { val comparator: Comparator = TtlExecutors.getTtlRunnableUnwrapComparatorForComparableRunnable() // explicit PriorityBlockingQueue Comparator argument // instead of default constructor PriorityBlockingQueue() // // aka. rewrite // val priorityBlockingQueue = PriorityBlockingQueue() // to val priorityBlockingQueue: BlockingQueue = PriorityBlockingQueue(11, comparator) val threadPoolExecutor = ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, priorityBlockingQueue) val ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor)!! ttlExecutorService.execute(BizComparableTask(0)) ttlExecutorService.execute(BizComparableTask(1)) ttlExecutorService.execute(BizComparableTask(2)) ttlExecutorService.execute(BizComparableTask(42)) ttlExecutorService.execute(BizComparableTask(9)) ttlExecutorService.execute(BizComparableTask(8)) ttlExecutorService.execute(BizComparableTask(7)) threadPoolExecutor.shutdown() threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS) } /** * Demo for cooperation TTL executor(ThreadPoolExecutor) with PriorityBlockingQueue, for Runnable Comparator. * * if you use TTL Agent, no extra work(getTtlRunnableUnwrapComparator) is need. * aka. rewriting Comparator by TTL Agent automatically and transparently. */ fun demoSubmitOrderTaskToTtlExecutorServiceWithPriorityBlockingQueue() { val comparator: Comparator = compareBy { (it as BizOrderTask).order } val ttlRunnableUnwrapComparator: Comparator? = TtlExecutors.getTtlRunnableUnwrapComparator(comparator) // use TtlRunnableUnwrapComparator instead original comparator // // aka. rewrite // val priorityBlockingQueue = PriorityBlockingQueue(11, comparator) // to val priorityBlockingQueue: PriorityBlockingQueue = PriorityBlockingQueue(11, ttlRunnableUnwrapComparator) val threadPoolExecutor = ThreadPoolExecutor(1, 2, 1, TimeUnit.SECONDS, priorityBlockingQueue) val ttlExecutorService = TtlExecutors.getTtlExecutorService(threadPoolExecutor)!! ttlExecutorService.execute(BizOrderTask(0)) ttlExecutorService.execute(BizOrderTask(1)) ttlExecutorService.execute(BizOrderTask(2)) ttlExecutorService.execute(BizOrderTask(42)) ttlExecutorService.execute(BizOrderTask(9)) ttlExecutorService.execute(BizOrderTask(8)) ttlExecutorService.execute(BizOrderTask(7)) threadPoolExecutor.shutdown() threadPoolExecutor.awaitTermination(5, TimeUnit.SECONDS) } private data class BizComparableTask(val num: Int) : Runnable, Comparable { override fun run() { sleep(10) println("run BizComparableTask $num") } override fun compareTo(other: Runnable): Int = num - (other as BizComparableTask).num } private data class BizOrderTask(val order: Int) : Runnable { override fun run() { sleep(10) println("run BizOrderTask $order") } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/TtlExecutorWrapperDemo.kt ================================================ package com.alibaba.demo.ttl import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.TtlExecutors import java.util.concurrent.Callable import java.util.concurrent.Executors /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val ttlExecutorService = Executors.newCachedThreadPool().let { // return TTL wrapper from normal ExecutorService TtlExecutors.getTtlExecutorService(it) }!! val context = TransmittableThreadLocal() context.set("value-set-in-parent") println("[parent thread] set ${context.get()}") ///////////////////////////////////// // Runnable ///////////////////////////////////// val task = Runnable { println("[child thread] get ${context.get()} in Runnable") } ttlExecutorService.submit(task).get() ///////////////////////////////////// // Callable ///////////////////////////////////// val call = Callable { println("[child thread] get ${context.get()} in Callable") 42 } ttlExecutorService.submit(call).get() ///////////////////////////////////// // cleanup ///////////////////////////////////// ttlExecutorService.shutdown() } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/TtlForkJoinTaskDemo.kt ================================================ package com.alibaba.demo.ttl import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TtlRecursiveTask import java.util.concurrent.ForkJoinPool val context = TransmittableThreadLocal().apply { set("value-set-in-parent") println("[parent thread] set ${get()} @ thread ${Thread.currentThread().name}") } /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val pool = ForkJoinPool.commonPool() val result = pool.invoke(SumTask(1..1000)) println("[parent thread] computed result: $result @ thread ${Thread.currentThread().name}") // result is 500500 } private class SumTask(private val numbers: IntRange, private val forkLevel: Int = 0) : TtlRecursiveTask() { override fun compute(): Int = if (numbers.count() <= 16) { println(String.format("direct compute %9s[%4s] with context ${context.get()} at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // compute directly numbers.sum() } else { println(String.format("fork compute %9s[%4s] with context ${context.get()} at fork level %2s @ thread ${Thread.currentThread().name}", numbers, numbers.count(), forkLevel)) // split task val middle = numbers.first + numbers.count() / 2 val nextForkLevel = forkLevel + 1 val taskLeft = SumTask(numbers.first until middle, nextForkLevel) val taskRight = SumTask(middle..numbers.last, nextForkLevel) // fork-join compute taskLeft.fork() taskRight.fork() taskLeft.join() + taskRight.join() } } /* Output: [parent thread] set value-set-in-parent @ thread main fork compute 1..1000[1000] with context value-set-in-parent at fork level 0 @ thread main fork compute 501..1000[ 500] with context value-set-in-parent at fork level 1 @ thread ForkJoinPool.commonPool-worker-5 fork compute 1..500[ 500] with context value-set-in-parent at fork level 1 @ thread ForkJoinPool.commonPool-worker-19 fork compute 1..250[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-23 fork compute 251..500[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..1000[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-27 fork compute 501..750[ 250] with context value-set-in-parent at fork level 2 @ thread ForkJoinPool.commonPool-worker-13 fork compute 126..250[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-31 fork compute 251..375[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-9 fork compute 876..1000[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-7 fork compute 1..125[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-17 fork compute 751..875[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-21 fork compute 376..500[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-3 fork compute 126..187[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 fork compute 251..312[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 fork compute 1..62[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 fork compute 501..625[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-13 fork compute 751..812[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-21 fork compute 876..937[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 fork compute 376..437[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 126..156[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 fork compute 1..31[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 251..281[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 fork compute 751..781[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 fork compute 501..562[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 376..406[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 1..15[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 876..906[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 251..265[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 126..140[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 751..765[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 501..531[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 376..390[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 266..281[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 141..156[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 16..31[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 876..890[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 501..515[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 766..781[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 282..312[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 391..406[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 891..906[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 32..62[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 157..187[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 fork compute 782..812[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 516..531[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 282..296[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 32..46[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 407..437[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 532..562[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 157..171[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 fork compute 907..937[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 47..62[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 532..546[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 297..312[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 782..796[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 907..921[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 172..187[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 407..421[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 313..375[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 direct compute 797..812[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 547..562[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 63..125[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-17 direct compute 422..437[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 188..250[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 direct compute 922..937[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 563..625[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-13 fork compute 813..875[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-21 fork compute 313..343[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 fork compute 438..500[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-3 fork compute 63..93[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 563..593[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 313..327[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 63..77[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 938..1000[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-7 fork compute 188..218[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-31 direct compute 328..343[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 78..93[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 563..577[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 fork compute 438..468[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 813..843[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 fork compute 94..125[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 fork compute 344..375[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 direct compute 188..202[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 438..452[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 938..968[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 94..109[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 813..827[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 578..593[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 110..125[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 938..952[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 453..468[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 203..218[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-31 direct compute 344..359[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 fork compute 594..625[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-13 direct compute 828..843[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 469..500[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 fork compute 219..250[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-17 direct compute 953..968[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 fork compute 844..875[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-21 direct compute 594..609[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 360..375[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 219..234[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 469..484[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 610..625[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-13 direct compute 844..859[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 fork compute 969..1000[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 direct compute 485..500[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 235..250[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 fork compute 626..750[ 125] with context value-set-in-parent at fork level 3 @ thread ForkJoinPool.commonPool-worker-13 direct compute 860..875[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 969..984[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-7 direct compute 985..1000[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 fork compute 688..750[ 63] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-9 fork compute 626..687[ 62] with context value-set-in-parent at fork level 4 @ thread ForkJoinPool.commonPool-worker-31 fork compute 688..718[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-9 fork compute 719..750[ 32] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-7 fork compute 657..687[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-27 fork compute 626..656[ 31] with context value-set-in-parent at fork level 5 @ thread ForkJoinPool.commonPool-worker-3 direct compute 688..702[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-9 direct compute 719..734[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-19 direct compute 703..718[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-21 direct compute 626..640[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-3 direct compute 672..687[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-17 direct compute 657..671[ 15] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-27 direct compute 735..750[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-23 direct compute 641..656[ 16] with context value-set-in-parent at fork level 6 @ thread ForkJoinPool.commonPool-worker-19 [parent thread] computed result: 500500 @ thread main */ ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/TtlWrapperDemo.kt ================================================ package com.alibaba.demo.ttl import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TtlCallable import com.alibaba.ttl.TtlRunnable import java.util.concurrent.Callable import java.util.concurrent.Executors /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val executorService = Executors.newCachedThreadPool() val context = TransmittableThreadLocal() context.set("value-set-in-parent") println("[parent thread] set ${context.get()}") ///////////////////////////////////// // Runnable / TtlRunnable ///////////////////////////////////// val task = Runnable { println("[child thread] get ${context.get()} in Runnable") } val ttlRunnable = TtlRunnable.get(task)!! executorService.submit(ttlRunnable).get() ///////////////////////////////////// // Callable / TtlCallable ///////////////////////////////////// val call = Callable { println("[child thread] get ${context.get()} in Callable") 42 } val ttlCallable = TtlCallable.get(call)!! executorService.submit(ttlCallable).get() ///////////////////////////////////// // cleanup ///////////////////////////////////// executorService.shutdown() } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/TtlWrapperTypeInferenceProblemShowcase.java ================================================ package com.alibaba.demo.ttl; import org.junit.Test; import java.util.ArrayList; import java.util.List; //import java.util.stream.Collectors; /** * @author huangfei1101 (fei.hf at alibaba-inc dot com) * @date 2021/12/30 */ public class TtlWrapperTypeInferenceProblemShowcase { @Test public void wrapFunction() { List source = buildSourceList(10); /* * Try to call the function foo for each element in the list * Because it is compatible with Java 6, the following code is commented * The following line of code has a compilation error */ //List targetWithCompileError = source.parallelStream().map(TtlWrappers.wrap(i->foo(i))).collect(Collectors.toList()); /* * The following line of code is correct */ //List target = source.parallelStream().map(TtlWrappers.wrapFunction(i->foo(i))).collect(Collectors.toList()); } private int foo(int i) { return i*10; } private List buildSourceList(int length) { List l = new ArrayList<>(); for (int i = 0; i < length; ++i) { l.add(i); } return l; } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/agent/AgentDemo.kt ================================================ @file:JvmName("AgentDemo") package com.alibaba.demo.ttl.agent import com.alibaba.ttl.TransmittableThreadLocal import java.util.concurrent.ExecutorService import java.util.concurrent.Executors /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val executorService = Executors.newFixedThreadPool(3) expandThreadPool(executorService) stringTransmittableThreadLocal.set("foo - main") personReferenceTransmittableThreadLocal.set(Person("jerry - reference", 1)) personCopyTransmittableThreadLocal.set(Person("Tom - value", 2)) printTtlInstancesInfo("Main - Before execution of thread pool") val submit = executorService.submit { printTtlInstancesInfo("Thread Pool - enter") stringTransmittableThreadLocal.set("foo - modified in thread pool") personReferenceTransmittableThreadLocal.get().name = "jerry - reference - modified in thread pool" personCopyTransmittableThreadLocal.get().name = "Tom - value - modified in thread pool" printTtlInstancesInfo("Thread Pool - leave") } submit.get() printTtlInstancesInfo("Main - After execution of thread pool") executorService.shutdown() } private data class Person(var name: String = "unnamed", var age: Int = -1) private val stringTransmittableThreadLocal = TransmittableThreadLocal() private val personReferenceTransmittableThreadLocal = object : TransmittableThreadLocal() { override fun initialValue(): Person = Person() } private val personCopyTransmittableThreadLocal = object : TransmittableThreadLocal() { override fun initialValue(): Person = Person() override fun copy(parentValue: Person): Person = parentValue.copy() // copy value to child thread } private fun expandThreadPool(executor: ExecutorService) { (0 until 3).map { executor.submit { Thread.sleep(100) } }.forEach { it.get() } } private fun printTtlInstancesInfo(msg: String) { println("====================================================") println(msg) println("====================================================") println("stringTransmittableThreadLocal: ${stringTransmittableThreadLocal.get()}") println("personReferenceTransmittableThreadLocal: ${personReferenceTransmittableThreadLocal.get()}") println("personCopyTransmittableThreadLocal: ${personCopyTransmittableThreadLocal.get()}") } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/demo/ttl/agent/YourXxxAgent.java ================================================ package com.alibaba.demo.ttl.agent; import com.alibaba.ttl.threadpool.agent.TtlAgent; import java.lang.instrument.Instrumentation; import java.util.logging.Logger; /** * @author Jerry Lee (oldratlee at gmail dot com) */ public final class YourXxxAgent { private static final Logger logger = Logger.getLogger(YourXxxAgent.class.getName()); public static void premain(String agentArgs, Instrumentation inst) throws Exception { TtlAgent.premain(agentArgs, inst); // add TTL Transformer // add your Transformer // ... } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/it/README.md ================================================ Integration Test Cases ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/it/TimerAgentCheck.kt ================================================ package com.alibaba.it import com.alibaba.* import com.alibaba.ttl.testmodel.Task import io.kotest.common.ExperimentalKotest import io.kotest.core.spec.style.FunSpec import io.kotest.core.test.TestScope import io.kotest.engine.test.logging.info import java.util.* import java.util.concurrent.ConcurrentHashMap /** * @author Jerry Lee (oldratlee at gmail dot com) * @see com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.TtlTimerTaskTransformlet */ @ExperimentalKotest class TimerAgentCheckTest : FunSpec({ fun TestScope.printHead(title: String) { info { "======================================\n$title\n======================================" } } test("check").config(enabled = hasTtlAgentRunWithEnableTimerTask()) { val timer = Timer(true) printHead("TimerAgentCheck") val ttlInstances = createParentTtlInstances(ConcurrentHashMap()) val tag = "1" val task = Task(tag, ttlInstances) val timerTask = object : TimerTask() { override fun run() { task.run() } } timer.schedule(timerTask, 0) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) // child Inheritable assertChildTtlValues(tag, task.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) printHead("TimerAgentCheck OK!") } }) ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/perf/Utils.kt ================================================ package com.alibaba.perf import java.util.* private val random = Random() internal fun bytes2Hex(bytes: ByteArray): String { val sb = StringBuilder(1024) for (b in bytes) { val s = Integer.toHexString(b.toInt() and 0xFF) sb.append(if (s.length == 1) "0$s" else s) } return sb.toString() } internal fun getRandomBytes(): ByteArray { val bytes = ByteArray(1024) random.nextBytes(bytes) return bytes } internal fun getRandomString(): String { return bytes2Hex(getRandomBytes()) } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/perf/memoryleak/NoMemoryLeak_ThreadLocal_NoRemove.kt ================================================ @file:JvmName("NoMemoryLeak_ThreadLocal_NoRemove") package com.alibaba.perf.memoryleak import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { var counter: Long = 0 while (true) { val threadLocal = ThreadLocal() threadLocal.set(getRandomString()) if (counter % 1000 == 0L) System.out.printf("%05dK%n", counter / 1000) counter++ } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/perf/memoryleak/NoMemoryLeak_TransmittableThreadLocal_NoRemove.kt ================================================ @file:JvmName("NoMemoryLeak_TransmittableThreadLocal_NoRemove") package com.alibaba.perf.memoryleak import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { var counter: Long = 0 while (true) { val threadLocal = TransmittableThreadLocal() threadLocal.set(getRandomString()) if (counter % 1000 == 0L) System.out.printf("%05dK%n", counter / 1000) counter++ } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/perf/tps/CreateThreadLocalInstanceTps.kt ================================================ @file:JvmName("CreateThreadLocalInstanceTps") package com.alibaba.perf.tps import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val tpsCounter = TpsCounter(2) tpsCounter.setAction { val threadLocal = ThreadLocal() threadLocal.set(getRandomString()) } while (true) { val start = tpsCounter.count Thread.sleep(1000) System.out.printf("tps: %,d\n", tpsCounter.count - start) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/perf/tps/CreateTransmittableThreadLocalInstanceTps.kt ================================================ @file:JvmName("CreateTransmittableThreadLocalInstanceTps") package com.alibaba.perf.tps import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.perf.getRandomString /** * @author Jerry Lee (oldratlee at gmail dot com) */ fun main() { val tpsCounter = TpsCounter(2) tpsCounter.setAction { val threadLocal = TransmittableThreadLocal() threadLocal.set(getRandomString()) } while (true) { val start = tpsCounter.count Thread.sleep(1000) System.out.printf("tps: %d\n", tpsCounter.count - start) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/perf/tps/TpsCounter.kt ================================================ package com.alibaba.perf.tps import org.junit.Assert.assertTrue import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TpsCounter internal constructor(private val threadCount: Int) { private val executorService: ExecutorService = Executors.newFixedThreadPool(threadCount) private val counter = AtomicLong() @Volatile private var stopped = false val count: Long get() = counter.get() internal fun setAction(runnable: Runnable) { val r = { while (!stopped) { runnable.run() counter.incrementAndGet() } } for (i in 0 until threadCount) { executorService.execute(r) } } fun stop() { stopped = true executorService.shutdown() assertTrue("Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS)) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/third_part_lib_test/ExecutorsTest.kt ================================================ package com.alibaba.third_part_lib_test import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import java.util.concurrent.Executors import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit class ExecutorsTest : AnnotationSpec() { @Test fun test_remove_of_ThreadPoolExecutor() { val size = 2 val threadPool = Executors.newFixedThreadPool(size) as ThreadPoolExecutor val futures = (0..size * 2).map { threadPool.submit { Thread.sleep(10) } } Runnable { println("Task should be removed!") }.let { threadPool.execute(it) assertTrue(threadPool.remove(it)) assertFalse(threadPool.remove(it)) } // wait sleep task finished. futures.forEach { it.get() } threadPool.shutdown() assertTrue("Fail to shutdown thread pool", threadPool.awaitTermination(1, TimeUnit.SECONDS)) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/third_part_lib_test/ForkJoinPoolTest.kt ================================================ package com.alibaba.third_part_lib_test import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.shouldBe import java.util.concurrent.ForkJoinPool import java.util.concurrent.RecursiveTask import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger class ForkJoinPoolTest : AnnotationSpec() { @Test fun test_sameTaskDirectReturn_onlyExec1Time_ifHaveRun() { val pool = ForkJoinPool() val numbers = 1L..100L val sumTask = SumTask(numbers) // same task instance run 10 times for (i in 0..9) { pool.invoke(sumTask) shouldBe numbers.sum() } sumTask.execCounter.get() shouldBe 1 // close pool.shutdown() // Fail to shut down thread pool pool.awaitTermination(1, TimeUnit.SECONDS).shouldBeTrue() } private class SumTask(private val numbers: LongRange) : RecursiveTask() { val execCounter = AtomicInteger(0) override fun compute(): Long { execCounter.incrementAndGet() return if (numbers.count() <= 16) { // compute directly numbers.sum() } else { // split task val middle = numbers.first + numbers.count() / 2 val taskLeft = SumTask(numbers.first until middle) val taskRight = SumTask(middle..numbers.last) taskLeft.fork() taskRight.fork() taskLeft.join() + taskRight.join() } } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/third_part_lib_test/JavassistTest.kt ================================================ package com.alibaba.third_part_lib_test import com.alibaba.noTtlAgentRun import io.kotest.assertions.fail import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.AnnotationSpec import io.kotest.core.test.config.TestCaseConfig import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import javassist.ClassPool import javassist.CtClass /** * [simplify the try-finally code gen by javassist, do not need copy method #115](https://github.com/alibaba/transmittable-thread-local/issues/115) */ class JavassistTest : AnnotationSpec() { /** * when run unit test under TTL agent, * javassist is repackaged and excluded. * * skip this test case. */ @Suppress("OVERRIDE_DEPRECATION") override fun defaultTestCaseConfig(): TestCaseConfig = TestCaseConfig(enabled = noTtlAgentRun()) @Test fun insertAfter_as_finally() { val classPool = ClassPool(true) val ctClass = classPool.getCtClass("com.alibaba.third_part_lib_test.DemoRunnable") // To execute it when an exception is thrown, the second parameter asFinally to insertAfter() must be true. ctClass.getDeclaredMethod("run", arrayOf()).insertAfter("value = 42;", true) val instance = ctClass.toClass().getDeclaredConstructor().newInstance() (instance as Supplier).get() shouldBe 0 (instance as Runnable).let { try { it.run() fail("must not run to here") } catch (e: RuntimeException) { e.message shouldBe "Intended" } } (instance as Supplier).get() shouldBe 42 } /** * more info see * - [Bad Bytecode when trying access localVariable using insertAfter and asFinally = true](https://issues.jboss.org/browse/JASSIST-232?_sscc=t) * - Javadoc of [javassist.CtBehavior.addLocalVariable]: * If the second parameter asFinally to insertAfter() is true, the declared local variable is not visible from the code inserted by insertAfter(). */ @Test fun insertAfter_as_finally_fail_with_local_var() { val classPool = ClassPool(true) val ctClass = classPool.getCtClass("com.alibaba.third_part_lib_test.DemoRunnable2") // To execute it when an exception is thrown, the second parameter asFinally to insertAfter() must be true. ctClass.getDeclaredMethod("run", arrayOf()).apply { addLocalVariable("var", CtClass.intType) insertBefore("var = 2;") insertAfter("value = 40 + var;", true) } shouldThrow { (ctClass.toClass().getDeclaredConstructor().newInstance() as Runnable).run() }.message shouldContain "Bad local variable type" } } private interface Supplier { fun get(): Int } @Suppress("unused") private class DemoRunnable : Runnable, Supplier { @Volatile private var value = 0 override fun get(): Int = value override fun run() { throw RuntimeException("Intended") } } @Suppress("unused") private class DemoRunnable2 : Runnable, Supplier { @Volatile private var value = 0 override fun get(): Int = value override fun run() {} } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/InheritableTest.kt ================================================ package com.alibaba.ttl import com.alibaba.hasTtlAgentRunWithDisableInheritableForThreadPool import com.alibaba.ttl.threadpool.TtlExecutors import com.alibaba.ttl.threadpool.TtlForkJoinPoolHelper import com.alibaba.ttl.threadpool.agent.TtlAgent import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import java.util.* import java.util.concurrent.Callable import java.util.concurrent.Executors import java.util.concurrent.ForkJoinPool import java.util.concurrent.ThreadPoolExecutor private const val hello = "hello" private val defaultValue = "${Date()} ${Math.random()}" class InheritableTest : AnnotationSpec() { // =================================================== // Executors // =================================================== @Test fun inheritable_Executors() { val threadPool = Executors.newCachedThreadPool() try { val ttl = TransmittableThreadLocal() ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! // get "hello" value is transmitted by InheritableThreadLocal function! // NOTE: Executors.newCachedThreadPool create thread lazily threadPool.submit(callable).get() shouldBe hello // current thread's TTL must be existed ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_Executors_DisableInheritableThreadFactory() { val threadPool = Executors.newCachedThreadPool(TtlExecutors.getDefaultDisableInheritableThreadFactory()) try { val ttl = TransmittableThreadLocal() ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: null threadPool.submit(callable).get().shouldBeNull() } // current thread's TTL must be existed when using DisableInheritableThreadFactory ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_Executors_TtlDisableInheritableWithInitialValue() { val threadPool = Executors.newCachedThreadPool() try { val ttl = object : TransmittableThreadLocal() { override fun childValue(parentValue: String?): String? = initialValue() } ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: null threadPool.submit(callable).get().shouldBeNull() } // current thread's TTL must be existed ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_Executors_TtlDefaultValue_TtlDisableInheritableWithInitialValue() { val threadPool = Executors.newCachedThreadPool() try { val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String = defaultValue override fun childValue(parentValue: String): String = initialValue() } ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: defaultValue threadPool.submit(callable).get() shouldBe defaultValue } // current thread's TTL must be existed when using DisableInheritableThreadFactory ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_Executors_TtlDefaultValue_DisableInheritableThreadFactory_TtlWithInitialValue() { val threadPool = Executors.newCachedThreadPool(TtlExecutors.getDefaultDisableInheritableThreadFactory()) try { val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String = defaultValue override fun childValue(parentValue: String): String = initialValue() } ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: defaultValue threadPool.submit(callable).get() shouldBe defaultValue } // current thread's TTL must be existed when using DisableInheritableThreadFactory ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_Executors_ByAgent() { val threadPool = Executors.newCachedThreadPool() as ThreadPoolExecutor try { TtlExecutors.isDisableInheritableThreadFactory(threadPool.threadFactory) shouldBe hasTtlAgentRunWithDisableInheritableForThreadPool() } finally { threadPool.shutdown() } } // =================================================== // ForkJoinPool // =================================================== @Test fun inheritable_ForkJoinPool() { val threadPool = ForkJoinPool(4) try { val ttl = TransmittableThreadLocal() ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! // get "hello" value is transmitted by InheritableThreadLocal function! // NOTE: Executors.newCachedThreadPool create thread lazily threadPool.submit(callable).get() shouldBe hello // current thread's TTL must be existed ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_ForkJoinPool_DisableInheritableForkJoinWorkerThreadFactory() { val threadPool = ForkJoinPool( 4, TtlForkJoinPoolHelper.getDefaultDisableInheritableForkJoinWorkerThreadFactory(), null, false ) try { val ttl = TransmittableThreadLocal() ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: null threadPool.submit(callable).get().shouldBeNull() } // current thread's TTL must be existed when using DisableInheritableForkJoinWorkerThreadFactory ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_ForkJoinPool_TtlDisableInheritableWithInitialValue() { val threadPool = ForkJoinPool(4) try { val ttl = object : TransmittableThreadLocal() { override fun childValue(parentValue: String?): String? = initialValue() } ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: null threadPool.submit(callable).get().shouldBeNull() } // current thread's TTL must be existed ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_ForkJoinPool_TtlDefaultValue_TtlDisableInheritableWithInitialValue() { val threadPool = ForkJoinPool(4) try { val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String = defaultValue override fun childValue(parentValue: String): String = initialValue() } ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: null threadPool.submit(callable).get() shouldBe defaultValue } // current thread's TTL must be existed ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_ForkJoinPool_TtlDefaultValue_DisableInheritableForkJoinWorkerThreadFactory_TtlWithInitialValue() { val threadPool = ForkJoinPool( 4, TtlForkJoinPoolHelper.getDefaultDisableInheritableForkJoinWorkerThreadFactory(), null, false ) try { val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String = defaultValue override fun childValue(parentValue: String): String = initialValue() } ttl.set(hello) val callable = Callable { ttl.get() } // NO TtlWrapper(TtlCallable) here!! if (TtlAgent.isTtlAgentLoaded()) { // when ttl agent is loaded, Callable is wrapped when submitted, // so here value is "hello" transmitted by TtlCallable wrapper threadPool.submit(callable).get() shouldBe hello } else { // when ttl agent is not loaded: null threadPool.submit(callable).get() shouldBe defaultValue } // current thread's TTL must be existed when using DisableInheritableForkJoinWorkerThreadFactory ttl.get() shouldBe hello } finally { threadPool.shutdown() } } @Test fun disableInheritable_ForkJoinPool_ByAgent() { val threadPool = ForkJoinPool(4) try { TtlForkJoinPoolHelper.isDisableInheritableForkJoinWorkerThreadFactory(threadPool.factory) shouldBe hasTtlAgentRunWithDisableInheritableForThreadPool() } finally { threadPool.shutdown() } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/README.md ================================================ 测试说明 ===================== 测试过程创建的`TTL`的Key约定: - 父 - parent-created-unmodified-in-child,子中没有去修改 - parent-created-modified-in-child,子中会修改 - parent-created-after-create-TtlTask,在创建`TtlRunnable`/`TtlCallable`之后,在父中创建的`value`。 - 子 - child-created,在子中创建的`value` ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/TtlCallableTest.kt ================================================ package com.alibaba.ttl import com.alibaba.* import com.alibaba.ttl.testmodel.Call import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf import org.junit.Assert.* import java.util.concurrent.Callable import java.util.concurrent.ExecutionException import java.util.concurrent.Executors import java.util.concurrent.TimeUnit /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlCallableTest : AnnotationSpec() { @Test fun test_TtlCallable_runInCurrentThread() { val ttlInstances = createParentTtlInstances() val call = Call("1", ttlInstances) val ttlCallable = TtlCallable.get(call)!! // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) // run in the *current* thread assertEquals("ok", ttlCallable.call()) // child Inheritable assertChildTtlValues("1", call.copied) // child do not effect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } @Test fun test_TtlCallable_asyncRunByExecutorService() { val ttlInstances = createParentTtlInstances() val call = Call("1", ttlInstances) val ttlCallable = if (noTtlAgentRun()) TtlCallable.get(call) else call if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } val future = executorService.submit(ttlCallable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } assertEquals("ok", future.get()) // child Inheritable assertChildTtlValues("1", call.copied) // child do not effect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } @Test fun test_remove_sameAsNotSet() { val ttlInstances = createParentTtlInstances() // add and remove !! newTtlInstanceAndPut("add and removed!", ttlInstances).remove() val call = Call("1", ttlInstances) val ttlCallable = if (noTtlAgentRun()) TtlCallable.get(call) else call if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } val future = executorService.submit(ttlCallable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } assertEquals("ok", future.get()) // child Inheritable assertChildTtlValues("1", call.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } @Test fun test_releaseTtlValueReferenceAfterCall() { val ttlInstances = createParentTtlInstances() val call = Call("1", ttlInstances) val ttlCallable = TtlCallable.get(call, true)!! assertSame(call, ttlCallable.callable) assertEquals("ok", executorService.submit(ttlCallable).get()) val exception = shouldThrow { executorService.submit(ttlCallable).get() } exception.cause.shouldBeInstanceOf() exception.message shouldContain "TTL value reference is released after call!" } @Test fun test_get_same() { val call = Call("1") val ttlCallable = TtlCallable.get(call)!! assertSame(call, ttlCallable.callable) } @Test fun test_get_idempotent() { val call = TtlCallable.get(Call("1")) shouldThrow { TtlCallable.get(call) }.message shouldContain "Already TtlCallable" } @Test fun test_get_nullInput() { TtlCallable.get(null).shouldBeNull() } @Test fun test_gets() { val call1 = Call("1") val call2 = Call("2") val call3 = Call("3") val callList = TtlCallable.gets( listOf?>(call1, call2, null, call3) ) callList.shouldHaveSize(4) callList[0].shouldBeInstanceOf>() callList[1].shouldBeInstanceOf>() callList[2].shouldBeNull() callList[3].shouldBeInstanceOf>() } @Test fun test_unwrap() { assertNull(TtlCallable.unwrap(null)) val callable = Callable { "hello" } val ttlCallable = TtlCallable.get(callable) assertSame(callable, TtlCallable.unwrap(callable)) assertSame(callable, TtlCallable.unwrap(ttlCallable)) assertSame(callable, TtlUnwrap.unwrap(callable)) assertSame(callable, TtlUnwrap.unwrap(ttlCallable)) assertEquals(listOf(callable), TtlCallable.unwraps(listOf(callable))) assertEquals(listOf(callable), TtlCallable.unwraps(listOf(ttlCallable))) assertEquals(listOf(callable, callable), TtlCallable.unwraps(listOf(ttlCallable, callable))) assertEquals(listOf>(), TtlCallable.unwraps(null)) } @AfterAll fun afterAll() { executorService.shutdown() assertTrue("Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS)) } companion object { private val executorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/TtlRunnableTest.kt ================================================ package com.alibaba.ttl import com.alibaba.* import com.alibaba.ttl.testmodel.DeepCopyFooTransmittableThreadLocal import com.alibaba.ttl.testmodel.FooPojo import com.alibaba.ttl.testmodel.FooTask import com.alibaba.ttl.testmodel.Task import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.timing.eventually import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.kotest.matchers.types.shouldBeInstanceOf import org.junit.Assert.* import java.util.concurrent.* import java.util.concurrent.atomic.AtomicInteger import kotlin.time.Duration.Companion.seconds /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlRunnableTest : FunSpec({ lateinit var executorService: ExecutorService beforeSpec { executorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } afterSpec { executorService.shutdown() assertTrue("Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS)) } test("runInCurrentThread") { val ttlInstances = createParentTtlInstances() val task = Task("1", ttlInstances) val ttlRunnable = TtlRunnable.get(task)!! // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) // run in the *current* thread ttlRunnable.run() // child Inheritable assertChildTtlValues("1", task.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } test("asyncRunByNewThread") { val ttlInstances = createParentTtlInstances() val task = Task("1", ttlInstances) val thread1 = Thread(task) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) thread1.start() thread1.join() // child Inheritable assertChildTtlValues("1", task.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } test("asyncRunByExecutorService") { val ttlInstances = createParentTtlInstances() val task = Task("1", ttlInstances) val ttlRunnable = if (noTtlAgentRun()) TtlRunnable.get(task) else task if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } val submit = executorService.submit(ttlRunnable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } submit.get() // child Inheritable assertChildTtlValues("1", task.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } test("remove_sameAsNotSet") { val ttlInstances = createParentTtlInstances() // add and remove !! newTtlInstanceAndPut("add and removed!", ttlInstances).remove() val task = Task("1", ttlInstances) val ttlRunnable = if (noTtlAgentRun()) TtlRunnable.get(task) else task if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } val submit = executorService.submit(ttlRunnable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } submit.get() // child Inheritable assertChildTtlValues("1", task.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } test("callback_copy_beforeExecute_afterExecute") { val counterTtl = CounterTransmittableThreadLocal() counterTtl.set("Foo") // do copy when decorate runnable val ttlRunnable1 = if (noTtlAgentRun()) TtlRunnable.get { /* do nothing Runnable */ } else Runnable { /* do nothing Runnable */ } assertEquals(if (noTtlAgentRun()) 1 else 0, counterTtl.copyCounter.get()) assertEquals(0, counterTtl.beforeExecuteCounter.get()) assertEquals(0, counterTtl.afterExecuteCounter.get()) // do before/after when run executorService.submit(ttlRunnable1).get() assertEquals(1, counterTtl.copyCounter.get()) assertEquals(1, counterTtl.beforeExecuteCounter.get()) eventually(1.seconds) { counterTtl.afterExecuteCounter.get() shouldBe 1 } assertEquals(1, counterTtl.afterExecuteCounter.get()) // do before/after when run executorService.submit(ttlRunnable1).get() assertEquals(if (noTtlAgentRun()) 1 else 2, counterTtl.copyCounter.get()) assertEquals(2, counterTtl.beforeExecuteCounter.get()) eventually(1.seconds) { counterTtl.afterExecuteCounter.get() shouldBe 2 } // do copy when decorate runnable val ttlRunnable2 = if (noTtlAgentRun()) TtlRunnable.get { /* do nothing Runnable */ } else Runnable { /* do nothing Runnable */ } assertEquals(if (noTtlAgentRun()) 2 else 2, counterTtl.copyCounter.get()) assertEquals(2, counterTtl.beforeExecuteCounter.get()) eventually(1.seconds) { counterTtl.afterExecuteCounter.get() shouldBe 2 } // do before/after when run executorService.submit(ttlRunnable2).get() assertEquals(if (noTtlAgentRun()) 2 else 3, counterTtl.copyCounter.get()) assertEquals(3, counterTtl.beforeExecuteCounter.get()) eventually(1.seconds) { counterTtl.afterExecuteCounter.get() shouldBe 3 } } test("copyObject") { val ttlInstances = ConcurrentHashMap>() val parent = DeepCopyFooTransmittableThreadLocal() parent.set(FooPojo(PARENT_CREATE_UNMODIFIED_IN_CHILD, 1)) ttlInstances[PARENT_CREATE_UNMODIFIED_IN_CHILD] = parent val p = DeepCopyFooTransmittableThreadLocal() p.set(FooPojo(PARENT_CREATE_MODIFIED_IN_CHILD, 2)) ttlInstances[PARENT_CREATE_MODIFIED_IN_CHILD] = p val task = FooTask("1", ttlInstances) val ttlRunnable = if (noTtlAgentRun()) TtlRunnable.get(task) else task if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! val after = DeepCopyFooTransmittableThreadLocal() after.set(FooPojo(PARENT_CREATE_AFTER_CREATE_CHILD, 4)) ttlInstances[PARENT_CREATE_AFTER_CREATE_CHILD] = after } val submit = executorService.submit(ttlRunnable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! val after = DeepCopyFooTransmittableThreadLocal() after.set(FooPojo(PARENT_CREATE_AFTER_CREATE_CHILD, 4)) ttlInstances[PARENT_CREATE_AFTER_CREATE_CHILD] = after } submit.get() // child Inheritable assertEquals(3, task.copied.size.toLong()) assertEquals(FooPojo(PARENT_CREATE_UNMODIFIED_IN_CHILD, 1), task.copied[PARENT_CREATE_UNMODIFIED_IN_CHILD]) assertEquals(FooPojo(PARENT_CREATE_MODIFIED_IN_CHILD + "1", 2), task.copied[PARENT_CREATE_MODIFIED_IN_CHILD]) assertEquals(FooPojo(CHILD_CREATE + 1, 3), task.copied[CHILD_CREATE + 1]) // child do not affect parent val copied = copyTtlValues(ttlInstances) assertEquals(3, copied.size.toLong()) assertEquals(FooPojo(PARENT_CREATE_UNMODIFIED_IN_CHILD, 1), copied[PARENT_CREATE_UNMODIFIED_IN_CHILD]) assertEquals(FooPojo(PARENT_CREATE_MODIFIED_IN_CHILD, 2), copied[PARENT_CREATE_MODIFIED_IN_CHILD]) assertEquals(FooPojo(PARENT_CREATE_AFTER_CREATE_CHILD, 4), copied[PARENT_CREATE_AFTER_CREATE_CHILD]) } test("releaseTtlValueReferenceAfterRun") { val ttlInstances = createParentTtlInstances() val task = Task("1", ttlInstances) val ttlRunnable = TtlRunnable.get(task, true) assertNull(executorService.submit(ttlRunnable).get()) val exception = shouldThrow { executorService.submit(ttlRunnable).get() } exception.cause.shouldBeInstanceOf() exception.message shouldContain "TTL value reference is released after run!" } test("get_same") { val task = Task("1") val ttlRunnable = TtlRunnable.get(task)!! assertSame(task, ttlRunnable.runnable) } test("get_idempotent") { val task = TtlRunnable.get(Task("1")) shouldThrow { TtlRunnable.get(task) }.message shouldContain "Already TtlRunnable" } test("et_nullInput") { assertNull(TtlRunnable.get(null)) } test("gets") { val task1 = Task("1") val task2 = Task("2") val task3 = Task("3") val taskList = TtlRunnable.gets(listOf(task1, task2, null, task3)) taskList.shouldHaveSize(4) taskList[0].shouldBeInstanceOf() taskList[1].shouldBeInstanceOf() taskList[2].shouldBeNull() taskList[3].shouldBeInstanceOf() } test("unwrap") { assertNull(TtlRunnable.unwrap(null)) val runnable = Runnable {} val ttlRunnable = TtlRunnable.get(runnable) assertSame(runnable, TtlRunnable.unwrap(runnable)) assertSame(runnable, TtlRunnable.unwrap(ttlRunnable)) assertSame(runnable, TtlUnwrap.unwrap(runnable)) assertSame(runnable, TtlUnwrap.unwrap(ttlRunnable)) assertEquals(listOf(runnable), TtlRunnable.unwraps(listOf(runnable))) assertEquals(listOf(runnable), TtlRunnable.unwraps(listOf(ttlRunnable))) assertEquals(listOf(runnable, runnable), TtlRunnable.unwraps(listOf(ttlRunnable, runnable))) assertEquals(listOf(), TtlRunnable.unwraps(null)) } }) private class CounterTransmittableThreadLocal : TransmittableThreadLocal() { val copyCounter = AtomicInteger() val beforeExecuteCounter = AtomicInteger() val afterExecuteCounter = AtomicInteger() override fun copy(parentValue: String?): String? { copyCounter.incrementAndGet() return super.copy(parentValue) } override fun beforeExecute() { beforeExecuteCounter.incrementAndGet() super.beforeExecute() } override fun afterExecute() { afterExecuteCounter.incrementAndGet() super.afterExecute() } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/TtlTimerTaskTest.kt ================================================ package com.alibaba.ttl import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.* import java.util.* @Suppress("DEPRECATION") class TtlTimerTaskTest : AnnotationSpec() { @Test fun test_get() { assertNull(TtlTimerTask.get(null)) val timerTask = object : TimerTask() { override fun run() {} } val ttlTimerTask = TtlTimerTask.get(timerTask) assertTrue(ttlTimerTask is TtlTimerTask) } @Test fun test_unwrap() { assertNull(TtlTimerTask.unwrap(null)) val timerTask = object : TimerTask() { override fun run() {} } val ttlTimerTask = TtlTimerTask.get(timerTask) assertSame(timerTask, TtlTimerTask.unwrap(timerTask)) assertSame(timerTask, TtlTimerTask.unwrap(ttlTimerTask)) assertEquals(listOf(timerTask), TtlTimerTask.unwraps(listOf(timerTask))) assertEquals(listOf(timerTask), TtlTimerTask.unwraps(listOf(ttlTimerTask))) assertEquals(listOf(timerTask, timerTask), TtlTimerTask.unwraps(listOf(ttlTimerTask, timerTask))) assertEquals(listOf(), TtlTimerTask.unwraps(null)) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/TtlWrappersTest.kt ================================================ package com.alibaba.ttl import com.alibaba.expandThreadPool import com.alibaba.ttl.TtlUnwrap.unwrap import com.alibaba.ttl.TtlWrappers.* import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.* import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.function.* import java.util.function.Function class TtlWrappersTest : AnnotationSpec() { @Test fun test_null() { val supplier: Supplier? = null @Suppress("DEPRECATION") assertNull(wrap(supplier)) assertNull(wrapSupplier(supplier)) assertNull(unwrap(supplier)) val consumer: Consumer? = null @Suppress("DEPRECATION") assertNull(wrap(consumer)) assertNull(wrapConsumer(consumer)) assertNull(unwrap(consumer)) val biConsumer: BiConsumer? = null @Suppress("DEPRECATION") assertNull(wrap(biConsumer)) assertNull(wrapBiConsumer(biConsumer)) assertNull(unwrap(biConsumer)) val function: Function? = null @Suppress("DEPRECATION") assertNull(wrap(function)) assertNull(wrapFunction(function)) assertNull(unwrap(function)) val biFunction: BiFunction? = null @Suppress("DEPRECATION") assertNull(wrap(biFunction)) assertNull(wrapBiFunction(biFunction)) assertNull(unwrap(biFunction)) } @Test fun wrap_ReWrap_Unwrap_same() { // Supplier val supplier = Supplier { 42 } @Suppress("DEPRECATION") val ttlSupplier = wrap(supplier) @Suppress("DEPRECATION") assertSame(ttlSupplier, wrap(ttlSupplier)) assertSame(ttlSupplier, wrapSupplier(ttlSupplier)) assertSame(supplier, unwrap(ttlSupplier)) val ttlSupplier2 = wrapSupplier(supplier) assertSame(ttlSupplier2, wrapSupplier(ttlSupplier2)) @Suppress("DEPRECATION") assertSame(ttlSupplier2, wrap(ttlSupplier2)) assertSame(supplier, unwrap(ttlSupplier2)) // Consumer val consumer = Consumer {} @Suppress("DEPRECATION") val ttlConsumer = wrap(consumer) @Suppress("DEPRECATION") assertSame(ttlConsumer, wrap(ttlConsumer)) assertSame(ttlConsumer, wrapConsumer(ttlConsumer)) assertSame(consumer, unwrap(ttlConsumer)) val ttlConsumer2 = wrapConsumer(consumer) assertSame(ttlConsumer2, wrapConsumer(ttlConsumer2)) @Suppress("DEPRECATION") assertSame(ttlConsumer2, wrap(ttlConsumer2)) assertSame(consumer, unwrap(ttlConsumer2)) // BiConsumer val biConsumer = BiConsumer { _, _ -> } @Suppress("DEPRECATION") val ttlBiConsumer = wrap(biConsumer) @Suppress("DEPRECATION") assertSame(ttlBiConsumer, wrap(ttlBiConsumer)) assertSame(ttlBiConsumer, wrapBiConsumer(ttlBiConsumer)) assertSame(biConsumer, unwrap(ttlBiConsumer)) val ttlBiConsumer2 = wrapBiConsumer(biConsumer) assertSame(ttlBiConsumer2, wrapBiConsumer(ttlBiConsumer2)) @Suppress("DEPRECATION") assertSame(ttlBiConsumer2, wrap(ttlBiConsumer2)) assertSame(biConsumer, unwrap(ttlBiConsumer2)) // Function val function = Function { "" } @Suppress("DEPRECATION") val ttlFunction = wrap(function) @Suppress("DEPRECATION") assertSame(ttlFunction, wrap(ttlFunction)) assertSame(ttlFunction, wrapFunction(ttlFunction)) assertSame(function, unwrap(ttlFunction)) val ttlFunction2 = wrapFunction(function) assertSame(ttlFunction2, wrapFunction(ttlFunction2)) @Suppress("DEPRECATION") assertSame(ttlFunction2, wrap(ttlFunction2)) assertSame(function, unwrap(ttlFunction2)) // BiFunction val biFunction = BiFunction { _, _ -> "" } @Suppress("DEPRECATION") val ttlBiFunction = wrap(biFunction) @Suppress("DEPRECATION") assertSame(ttlBiFunction, wrap(ttlBiFunction)) assertSame(ttlBiFunction, wrapBiFunction(ttlBiFunction)) assertSame(biFunction, unwrap(ttlBiFunction)) val ttlBiFunction2 = wrapBiFunction(biFunction) assertSame(ttlBiFunction2, wrapBiFunction(ttlBiFunction2)) @Suppress("DEPRECATION") assertSame(ttlBiFunction2, wrap(ttlBiFunction2)) assertSame(biFunction, unwrap(ttlBiFunction2)) } @Test fun test_Supplier() { val ttl = TransmittableThreadLocal() fun Supplier.ttlWrapThenAsRunnable(): Runnable { @Suppress("DEPRECATION") val wrap = wrap(this)!! return Runnable { wrap.get() } } ttl.set("1") Supplier { assertEquals("1", ttl.get()) "world" }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } ttl.set("2") Supplier { assertEquals("2", ttl.get()) "world" }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } } @Test fun test_Consumer() { fun Consumer.ttlWrapThenAsRunnable(): Runnable { @Suppress("DEPRECATION") val wrap = wrap(this)!! return Runnable { wrap.accept("hello ${System.nanoTime()}") } } val ttl = TransmittableThreadLocal() ttl.set("1") Consumer { assertEquals("1", ttl.get()) }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } ttl.set("2") Consumer { assertEquals("2", ttl.get()) }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } } @Test fun test_BiConsumer() { fun BiConsumer.ttlWrapThenAsRunnable(): Runnable { @Suppress("DEPRECATION") val wrap = wrap(this)!! return Runnable { wrap.accept("hello ${System.nanoTime()}", "world ${System.nanoTime()}") } } val ttl = TransmittableThreadLocal() ttl.set("1") BiConsumer { _, _ -> assertEquals("1", ttl.get()) }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } ttl.set("2") BiConsumer { _, _ -> assertEquals("2", ttl.get()) }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } } @Test fun test_Function() { fun Function.ttlWrapThenAsRunnable(): Runnable { @Suppress("DEPRECATION") val wrap = wrap(this)!! return Runnable { wrap.apply("hello ${System.nanoTime()}") } } val ttl = TransmittableThreadLocal() ttl.set("1") Function { assertEquals("1", ttl.get()) "world" }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } ttl.set("2") Function { assertEquals("2", ttl.get()) "world" }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } } @Test fun test_BiFunction() { fun BiFunction.ttlWrapThenAsRunnable(): Runnable { @Suppress("DEPRECATION") val wrap = wrap(this)!! return Runnable { wrap.apply("hello ${System.nanoTime()}", "world") } } val ttl = TransmittableThreadLocal() ttl.set("1") BiFunction { _, _ -> assertEquals("1", ttl.get()) "world" }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } ttl.set("2") BiFunction { _, _ -> assertEquals("2", ttl.get()) "world" }.ttlWrapThenAsRunnable().let { ttl.set("main") executorService.submit(it).get() assertEquals("main", ttl.get()) } } @AfterAll fun afterAll() { executorService.shutdown() assertTrue("Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS)) } companion object { private val executorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/forkjoin/ForkJoinPool4RunnableCallableTest.kt ================================================ package com.alibaba.ttl.forkjoin import com.alibaba.* import com.alibaba.ttl.TtlCallable import com.alibaba.ttl.TtlRunnable import com.alibaba.ttl.testmodel.Call import com.alibaba.ttl.testmodel.Task import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.shouldBe import java.util.concurrent.ForkJoinPool private val pool = ForkJoinPool() class ForkJoinPool4RunnableCallableTest : AnnotationSpec() { @Test fun test_Runnable() { val ttlInstances = createParentTtlInstances() val task = Task("1", ttlInstances) val ttlRunnable = if (noTtlAgentRun()) TtlRunnable.get(task) else task if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } val submit = pool.submit(ttlRunnable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } submit.get() // child Inheritable assertChildTtlValues("1", task.copied) // child do not effect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } @Test fun test_Callable() { val ttlInstances = createParentTtlInstances() val call = Call("1", ttlInstances) val ttlCallable = if (noTtlAgentRun()) TtlCallable.get(call) else call if (noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } val future = pool.submit(ttlCallable) if (!noTtlAgentRun()) { // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) } future.get() shouldBe "ok" // child Inheritable assertChildTtlValues("1", call.copied) // child do not effect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/forkjoin/ForkJoinPool4StreamTest.kt ================================================ package com.alibaba.ttl.forkjoin import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.agent.TtlAgent import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import java.util.concurrent.ForkJoinPool private const val hello = "hello" class ForkJoinPool4StreamTest : AnnotationSpec() { @Test fun test_stream_with_agent() { if (!TtlAgent.isTtlAgentLoaded()) return expandThreadPool(ForkJoinPool.commonPool()) val ttl = TransmittableThreadLocal() ttl.set(hello) (0..100).map { ForkJoinPool.commonPool().submit { ttl.get() shouldBe hello } }.forEach { it.get() } (0..1000).toList().stream().parallel().mapToInt { ttl.get() shouldBe hello it }.sum() shouldBe (0..1000).sum() } @Test fun test_stream_no_agent() { if (TtlAgent.isTtlAgentLoaded()) return val name = Thread.currentThread().name expandThreadPool(ForkJoinPool.commonPool()) val ttl = TransmittableThreadLocal() ttl.set(hello) (0..100).map { ForkJoinPool.commonPool().submit { if (Thread.currentThread().name == name) ttl.get() shouldBe hello else ttl.get().shouldBeNull() } }.forEach { it.get() } (0..1000).toList().stream().parallel().mapToInt { if (Thread.currentThread().name == name) ttl.get() shouldBe hello else ttl.get().shouldBeNull() it }.sum() shouldBe (0..1000).sum() } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/forkjoin/recursive_action/TtlRecursiveActionTest.kt ================================================ package com.alibaba.ttl.forkjoin.recursive_action import com.alibaba.* import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TtlRecursiveAction import io.kotest.core.spec.style.AnnotationSpec import mu.KotlinLogging import java.util.concurrent.ConcurrentMap import java.util.concurrent.ForkJoinPool private val pool = ForkJoinPool() private val singleThreadPool = ForkJoinPool(1) /** * TtlRecursiveAction test class * * @author LNAmp * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlRecursiveActionTest : AnnotationSpec() { @Test fun test_TtlRecursiveTask_asyncWithForkJoinPool() { run_test_with_pool(pool) } @Test fun test_TtlRecursiveTask_asyncWithSingleThreadForkJoinPool_changeValue() { run_test_with_pool(singleThreadPool) } } private fun run_test_with_pool(forkJoinPool: ForkJoinPool) { val ttlInstances = createParentTtlInstances() val printAction = PrintAction(1..42, ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = forkJoinPool.submit(printAction) future.get() // child Inheritable assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD /* Not change*/ ), printAction.copied ) // left grand Task Inheritable, changed value assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD + PrintAction.CHANGE_POSTFIX /* CHANGED */ ), printAction.leftSubAction.copied ) // right grand Task Inheritable, not change value assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD /* Not change*/ ), printAction.rightSubAction.copied ) // child do not affect parent assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD, PARENT_CREATE_AFTER_CREATE_CHILD to PARENT_CREATE_AFTER_CREATE_CHILD ), copyTtlValues(ttlInstances) ) } /** * A test demo class * * @author LNAmp */ private class PrintAction( private val numbers: IntRange, private val ttlMap: ConcurrentMap>, private val changeTtlValue: Boolean = false ) : TtlRecursiveAction() { private val logger = KotlinLogging.logger {} lateinit var copied: Map lateinit var leftSubAction: PrintAction lateinit var rightSubAction: PrintAction override fun compute() { if (changeTtlValue) { modifyParentTtlInstances(CHANGE_POSTFIX, ttlMap) } try { if (numbers.count() <= 10) { logger.info { "print numbers: $numbers" } } else { val mid = numbers.first + numbers.count() / 2 // left -> change! right -> not change. val left = PrintAction(numbers.first until mid, ttlMap, true) val right = PrintAction(mid..numbers.last, ttlMap, false) leftSubAction = left rightSubAction = right left.fork() right.fork() left.join() right.join() } } finally { this.copied = copyTtlValues(this.ttlMap) } } companion object { const val CHANGE_POSTFIX = " + 1" } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/forkjoin/recursive_task/TtlRecursiveTaskTest.kt ================================================ package com.alibaba.ttl.forkjoin.recursive_task import com.alibaba.* import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TtlRecursiveTask import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.shouldBe import java.util.concurrent.* private val pool = ForkJoinPool() private val singleThreadPool = ForkJoinPool(1) /** * @author LNAmp * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlRecursiveTaskTest : AnnotationSpec() { @Test fun test_TtlRecursiveTask_asyncWith_ForkJoinPool() { run_test_with_pool(pool) } @Test fun test_TtlRecursiveTask_asyncWith_SingleThreadForkJoinPool() { run_test_with_pool(singleThreadPool) } } private fun run_test_with_pool(forkJoinPool: ForkJoinPool) { val ttlInstances = createParentTtlInstances() val numbers = 0..42 val sumTask: ForkJoinTask = if (noTtlAgentRun()) TtlSumTask(numbers, ttlInstances) else SumTask(numbers, ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = forkJoinPool.submit(sumTask) future.get() shouldBe numbers.sum() // child Inheritable assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD /* Not change*/ ), (sumTask as Getter).getcopied() ) // left grand Task Inheritable, changed value assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD + CHANGE_POSTFIX /* CHANGED */ ), sumTask.getLeftSubTask().getcopied() ) // right grand Task Inheritable, not change value assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD /* Not change*/ ), sumTask.getRightSubTask().getcopied() ) // child do not affect parent assertTtlValues( mapOf( PARENT_CREATE_UNMODIFIED_IN_CHILD to PARENT_CREATE_UNMODIFIED_IN_CHILD, PARENT_CREATE_MODIFIED_IN_CHILD to PARENT_CREATE_MODIFIED_IN_CHILD, PARENT_CREATE_AFTER_CREATE_CHILD to PARENT_CREATE_AFTER_CREATE_CHILD ), copyTtlValues(ttlInstances) ) } private interface Getter { fun getcopied(): Map fun getLeftSubTask(): Getter fun getRightSubTask(): Getter } /** * A test demo class * * @author LNAmp * @see com.alibaba.ttl.TtlRecursiveTask */ private open class TtlSumTask( private val numbers: IntRange, private val ttlMap: ConcurrentMap>, private val changeTtlValue: Boolean = false ) : TtlRecursiveTask(), Getter { lateinit var copied: Map lateinit var leftSubTask: TtlSumTask lateinit var rightSubTask: TtlSumTask override fun compute(): Int { if (changeTtlValue) { modifyParentTtlInstances(CHANGE_POSTFIX, ttlMap) } try { return if (numbers.count() <= 10) { numbers.sum() } else { val mid = numbers.first + numbers.count() / 2 // left -> change! right -> not change. val left = TtlSumTask(numbers.first until mid, ttlMap, true) val right = TtlSumTask(mid..numbers.last, ttlMap, false) this.leftSubTask = left this.rightSubTask = right left.fork() right.fork() left.join() + right.join() } } finally { this.copied = copyTtlValues(this.ttlMap) } } override fun getcopied(): Map = copied override fun getLeftSubTask(): Getter = leftSubTask override fun getRightSubTask(): Getter = rightSubTask } /** * A test demo class */ private class SumTask( private val numbers: IntRange, private val ttlMap: ConcurrentMap>, private val changeTtlValue: Boolean = false ) : RecursiveTask(), Getter { lateinit var copied: Map lateinit var leftSubTask: SumTask lateinit var rightSubTask: SumTask override fun compute(): Int { if (changeTtlValue) { modifyParentTtlInstances(CHANGE_POSTFIX, ttlMap) } try { return if (numbers.count() <= 10) { numbers.sum() } else { val mid = numbers.first + numbers.count() / 2 // left -> change! right -> not change. val left = SumTask(this.numbers.first until mid, ttlMap, true) val right = SumTask(mid..numbers.last, ttlMap, false) this.leftSubTask = left this.rightSubTask = right left.fork() right.fork() left.join() + right.join() } } finally { this.copied = copyTtlValues(this.ttlMap) } } override fun getcopied(): Map = copied override fun getLeftSubTask(): Getter = leftSubTask override fun getRightSubTask(): Getter = rightSubTask } const val CHANGE_POSTFIX = " + 1" ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/reported_bugs/Bug70_Test.kt ================================================ package com.alibaba.ttl.reported_bugs import com.alibaba.noTtlAgentRun import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TtlRunnable import org.junit.Assert.assertEquals import org.junit.Test import java.util.concurrent.Executors import java.util.concurrent.FutureTask import java.util.concurrent.atomic.AtomicReference import kotlin.concurrent.thread /** * Bug URL: https://github.com/alibaba/transmittable-thread-local/issues/70 * Reporter: @aftersss */ class Bug70_Test { @Test fun test_bug70() { val hello = "hello" val executorService = Executors.newSingleThreadExecutor() val threadLocal = TransmittableThreadLocal().apply { set(hello) } assertEquals(hello, threadLocal.get()) FutureTask { threadLocal.get() }.also { val runnable = if (noTtlAgentRun()) TtlRunnable.get(it) else it executorService.submit(runnable) assertEquals(hello, it.get()) } val taskRef = AtomicReference>() thread(name = "the thread for run executor action") { FutureTask { threadLocal.get() }.also { val runnable = if (noTtlAgentRun()) TtlRunnable.get(it, false, false) else it executorService.submit(runnable) taskRef.set(it) } }.join() assertEquals(hello, taskRef.get().get()) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/testmodel/Call.kt ================================================ package com.alibaba.ttl.testmodel import com.alibaba.createChildTtlInstancesAndModifyParentTtlInstances import com.alibaba.ttl.TransmittableThreadLocal import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap /** * @author Jerry Lee (oldratlee at gmail dot com) */ class Call(private val tag: String, private val ttlInstances: ConcurrentMap> = ConcurrentHashMap()) : Callable { lateinit var copied: Map val isCopied: Boolean get() = ::copied.isInitialized override fun call(): String { copied = createChildTtlInstancesAndModifyParentTtlInstances(tag, ttlInstances) return "ok" } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/testmodel/DeepCopyFooTransmittableThreadLocal.kt ================================================ package com.alibaba.ttl.testmodel import com.alibaba.ttl.TransmittableThreadLocal /** * @author Jerry Lee (oldratlee at gmail dot com) */ class DeepCopyFooTransmittableThreadLocal : TransmittableThreadLocal() { override fun copy(parentValue: FooPojo?): FooPojo? = parentValue?.copy() } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/testmodel/FooPojo.kt ================================================ package com.alibaba.ttl.testmodel /** * @author Jerry Lee (oldratlee at gmail dot com) */ data class FooPojo(var name: String?, var age: Int) ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/testmodel/FooTask.kt ================================================ package com.alibaba.ttl.testmodel import com.alibaba.CHILD_CREATE import com.alibaba.PARENT_CREATE_MODIFIED_IN_CHILD import com.alibaba.copyTtlValues import com.alibaba.ttl.TransmittableThreadLocal import mu.KotlinLogging import java.util.concurrent.ConcurrentMap /** * @author Jerry Lee (oldratlee at gmail dot com) */ class FooTask( private val value: String, private val ttlInstances: ConcurrentMap> ) : Runnable { private val logger = KotlinLogging.logger {} @Volatile lateinit var copied: Map override fun run() { try { // Add new val child = DeepCopyFooTransmittableThreadLocal() child.set(FooPojo(CHILD_CREATE + value, 3)) ttlInstances[CHILD_CREATE + value] = child // modify the parent key ttlInstances[PARENT_CREATE_MODIFIED_IN_CHILD]!!.get()!!.name = ttlInstances[PARENT_CREATE_MODIFIED_IN_CHILD]!!.get()!!.name + value copied = copyTtlValues(ttlInstances) logger.info { "Task $value finished!" } } catch (e: Throwable) { e.printStackTrace() } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/testmodel/Task.kt ================================================ package com.alibaba.ttl.testmodel import com.alibaba.createChildTtlInstancesAndModifyParentTtlInstances import com.alibaba.ttl.TransmittableThreadLocal import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import java.util.concurrent.TimeUnit /** * @author Jerry Lee (oldratlee at gmail dot com) */ class Task( private val tag: String, private val ttlInstances: ConcurrentMap> = ConcurrentHashMap() ) : Runnable { private val queue = ArrayBlockingQueue>(1) val copied: Map get() = queue.poll(1, TimeUnit.SECONDS)!! override fun run() { val map = createChildTtlInstancesAndModifyParentTtlInstances(tag, ttlInstances) queue.put(map) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadlocal_integration/ThreadLocalIntegrationTest.kt ================================================ package com.alibaba.ttl.threadlocal_integration import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal.Transmitter.* import com.alibaba.ttl.TtlCopier import com.alibaba.ttl.threadpool.TtlExecutors import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.booleans.shouldBeFalse import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.shouldBe import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.TimeUnit import kotlin.concurrent.thread class ThreadLocalIntegrationTest : AnnotationSpec() { @Test fun threadLocal_do_NOT_transmit() { val threadLocal = ThreadLocal() unregisterThreadLocal(threadLocal).shouldBeFalse() threadLocal.set(PARENT) assertNotTransmit(threadLocal) } private fun assertNotTransmit(threadLocal: ThreadLocal) { val future = executorService.submit { threadLocal.get().shouldBeNull() threadLocal.set(CHILD) } threadLocal.get() shouldBe PARENT future.get(1, TimeUnit.SECONDS) unregisterThreadLocal(threadLocal).shouldBeFalse() } @Test fun threadLocal_registerThreadLocalWithShadowCopier_do_transmit() { val threadLocal = ThreadLocal() unregisterThreadLocal(threadLocal).shouldBeFalse() threadLocal.set(PARENT) registerThreadLocalWithShadowCopier(threadLocal).shouldBeTrue() assertTransmitShadowCopy(threadLocal) // Unregister unregisterThreadLocal(threadLocal).shouldBeTrue() assertNotTransmit(threadLocal) } private fun assertTransmitShadowCopy(threadLocal: ThreadLocal) { val future = executorService.submit { threadLocal.get() shouldBe PARENT threadLocal.set(CHILD) } threadLocal.get() shouldBe PARENT future.get(1, TimeUnit.SECONDS) } @Test fun threadLocal_registerThreadLocal_and_force() { val threadLocal = ThreadLocal() unregisterThreadLocal(threadLocal).shouldBeFalse() registerThreadLocal(threadLocal, APPEND_SUFFIX_COPIER).shouldBeTrue() threadLocal.set(PARENT) assertTransmitSuffixCopy(threadLocal) registerThreadLocalWithShadowCopier(threadLocal, true).shouldBeTrue() // copier changed assertTransmitShadowCopy(threadLocal) registerThreadLocal(threadLocal, APPEND_SUFFIX_COPIER).shouldBeFalse() // copier do not change assertTransmitShadowCopy(threadLocal) registerThreadLocal(threadLocal, APPEND_SUFFIX_COPIER, true).shouldBeTrue() // copier changed assertTransmitSuffixCopy(threadLocal) // Unregister unregisterThreadLocal(threadLocal).shouldBeTrue() assertNotTransmit(threadLocal) } private fun assertTransmitSuffixCopy(threadLocal: ThreadLocal) { val future = executorService.submit { threadLocal.get() shouldBe "$PARENT$COPY_SUFFIX" threadLocal.set(CHILD) } threadLocal.get() shouldBe PARENT future.get(1, TimeUnit.SECONDS) } @Test fun test_clear() { val initValue = "init" val threadLocal = object : ThreadLocal() { override fun initialValue(): String = initValue } threadLocal.get() shouldBe initValue threadLocal.set(PARENT) registerThreadLocalWithShadowCopier(threadLocal).shouldBeTrue() runCallableWithClear { val future = executorService.submit { threadLocal.get() shouldBe initValue threadLocal.set(CHILD) } threadLocal.get() shouldBe initValue future.get(1, TimeUnit.SECONDS) } threadLocal.get() shouldBe PARENT } @Test fun register_ThreadLocal_can_NOT_Inheritable() { val initValue = "init" val threadLocal = object : ThreadLocal() { override fun initialValue(): String = initValue } threadLocal.set(PARENT) registerThreadLocalWithShadowCopier(threadLocal).shouldBeTrue() val blockingQueue = LinkedBlockingQueue(1) thread { blockingQueue.add(threadLocal.get()) } blockingQueue.take() shouldBe initValue } @Test fun register_InheritableThreadLocal_can_Inheritable() { val initValue = "init" val threadLocal = object : InheritableThreadLocal() { override fun initialValue(): String = initValue } threadLocal.set(PARENT) registerThreadLocalWithShadowCopier(threadLocal).shouldBeTrue() val blockingQueue = LinkedBlockingQueue(1) thread { blockingQueue.add(threadLocal.get()) } blockingQueue.take() shouldBe PARENT } @AfterAll fun afterAll() { executorService.shutdown() // Fail to shut down thread pool executorService.awaitTermination(1, TimeUnit.SECONDS).shouldBeTrue() } companion object { private val PARENT = "parent: " + Date() private val CHILD = "child: " + Date() private const val COPY_SUFFIX = " 42" private val APPEND_SUFFIX_COPIER = TtlCopier { "$it$COPY_SUFFIX" } private val executorService: ExecutorService = Executors.newFixedThreadPool(3).let { expandThreadPool(it) TtlExecutors.getTtlExecutorService(it) }!! } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/BeforeAndAfterExecuteMethodOfExecutorSubclassTest.kt ================================================ @file:Suppress("PackageDirectoryMismatch") // Change the package out of com.alibaba.ttl // so agent will transform MyThreadPoolExecutor package com.alibaba.test.ttl.threadpool import com.alibaba.hasTtlAgentRun import com.alibaba.noTtlAgentRun import com.alibaba.ttl.TtlRunnable import com.alibaba.ttl.threadpool.TtlExecutors import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test import java.util.concurrent.* class MyThreadPoolExecutor(count: Int) : ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, LinkedBlockingQueue()) { val runnableList = CopyOnWriteArrayList() private val countDownLatch = CountDownLatch(count * 2) override fun afterExecute(r: Runnable, t: Throwable?) { runnableList.add(r) countDownLatch.countDown() super.afterExecute(r, t) } override fun beforeExecute(t: Thread, r: Runnable) { runnableList.add(r) countDownLatch.countDown() super.beforeExecute(t, r) } fun await() { countDownLatch.await() } } class MyRunnable : Runnable { override fun run() { Thread.sleep(1) } } class BeforeAndAfterExecuteMethodOfExecutorSubclassTest { private val count = 10 @Test fun underAgent() { if (noTtlAgentRun()) return val myThreadPoolExecutor = MyThreadPoolExecutor(count) (0 until count).map { myThreadPoolExecutor.execute(MyRunnable()) } myThreadPoolExecutor.await() assertEquals(count * 2, myThreadPoolExecutor.runnableList.size) assertTrue(myThreadPoolExecutor.runnableList.all { it is MyRunnable }) } /** * for bug submitted by * https://github.com/alibaba/transmittable-thread-local/issues/133#issuecomment-1068793261 */ @Test fun underAgent_task_is_explicit_TtlRunnable__should_not_be_unwrapped() { if (noTtlAgentRun()) return val myThreadPoolExecutor = MyThreadPoolExecutor(count) (0 until count).map { val r = TtlRunnable.get(MyRunnable())!! myThreadPoolExecutor.execute(r) } myThreadPoolExecutor.await() assertEquals(count * 2, myThreadPoolExecutor.runnableList.size) assertTrue(myThreadPoolExecutor.runnableList.all { it is TtlRunnable }) } @Test fun noAgent_task_is_TtlRunnable() { if (hasTtlAgentRun()) return val myThreadPoolExecutor = MyThreadPoolExecutor(count) val ttlExecutorService = TtlExecutors.getTtlExecutorService(myThreadPoolExecutor)!! (0 until count).map { ttlExecutorService.execute(MyRunnable()) } myThreadPoolExecutor.await() assertEquals(count * 2, myThreadPoolExecutor.runnableList.size) assertTrue(myThreadPoolExecutor.runnableList.all { it is TtlRunnable }) } @Test fun noAgent_task_is_NOT_TtlRunnable() { if (hasTtlAgentRun()) return val myThreadPoolExecutor = MyThreadPoolExecutor(count) (0 until count).map { myThreadPoolExecutor.execute(MyRunnable()) } myThreadPoolExecutor.await() assertEquals(count * 2, myThreadPoolExecutor.runnableList.size) assertTrue(myThreadPoolExecutor.runnableList.all { it is MyRunnable }) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/ExecutorClassesTest.kt ================================================ package com.alibaba.ttl.threadpool import com.alibaba.* import com.alibaba.ttl.TtlRunnable import com.alibaba.ttl.testmodel.Task import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test import java.util.concurrent.* private const val POOL_SIZE = 3 val threadFactory = ThreadFactory { Thread(it).apply { isDaemon = true } } val executorService = ThreadPoolExecutor( POOL_SIZE, POOL_SIZE, 10L, TimeUnit.SECONDS, LinkedBlockingQueue(), threadFactory ) val scheduledExecutorService = ScheduledThreadPoolExecutor(POOL_SIZE, threadFactory) class ExecutorClassesTest { @Test fun checkThreadPoolExecutorForRemoveMethod() { val futures = (0 until POOL_SIZE * 2).map { executorService.submit { Thread.sleep(10) } } Runnable { println("Task should be removed!") }.let { if (noTtlAgentRun()) TtlRunnable.get(it) else it }.let { executorService.execute(it) // Does ThreadPoolExecutor#remove method take effect? assertTrue(executorService.remove(it)) assertFalse(executorService.remove(it)) } // wait sleep task finished. futures.forEach { it.get(1, TimeUnit.SECONDS) } } @Test fun checkScheduledExecutorService() { val ttlInstances = createParentTtlInstances(ConcurrentHashMap()) val tag = "2" val task = Task(tag, ttlInstances) val future = scheduledExecutorService.schedule( if (noTtlAgentRun()) TtlRunnable.get(task) else task, 10, TimeUnit.MILLISECONDS ) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) future.get(1, TimeUnit.SECONDS) // child Inheritable assertChildTtlValues(tag, task.copied) // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/ScheduledExecutorServiceTtlWrapperTest.kt ================================================ package com.alibaba.ttl.threadpool import com.alibaba.* import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.testmodel.Call import com.alibaba.ttl.testmodel.Task import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.* import java.util.concurrent.* /** * @author Jerry Lee (oldratlee at gmail dot com) */ class ScheduledExecutorServiceTtlWrapperTest : AnnotationSpec() { private lateinit var ttlInstances: ConcurrentMap> @Before fun setUp() { ttlInstances = createParentTtlInstances(ConcurrentHashMap()) } @After fun tearDown() { // child do not affect parent assertParentTtlValues(copyTtlValues(ttlInstances)) } @Test fun test_execute() { val task = Task("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) executorService.execute(task) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", task.copied) } @Test fun test_submit() { val call = Call("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.submit(call) assertEquals("ok", future.get()) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", call.copied) } @Test fun test_submit_runnable_result() { val task = Task("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.submit(task, "ok") assertEquals("ok", future.get()) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", task.copied) } @Test fun test_submit_runnable_null() { val task = Task("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.submit(task) assertNull(future.get()) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", task.copied) } @Test fun test_invokeAll() { val call1 = Call("1", ttlInstances) val call2 = Call("2", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val futures = executorService.invokeAll(listOf(call1, call2)) for (future in futures) { assertEquals("ok", future.get()) } // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", call1.copied) assertChildTtlValuesWithParentCreateAfterCreateChild("2", call2.copied) } @Test fun test_invokeAll_timeout() { val call1 = Call("1", ttlInstances) val call2 = Call("2", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val futures = executorService.invokeAll(listOf(call1, call2), 1, TimeUnit.SECONDS) for (future in futures) { assertEquals("ok", future.get()) } // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", call1.copied) assertChildTtlValuesWithParentCreateAfterCreateChild("2", call2.copied) } @Test fun test_invokeAny() { val call1 = Call("1", ttlInstances) val call2 = Call("2", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val s = executorService.invokeAny(listOf(call1, call2)) assertEquals("ok", s) assertTrue(call1.isCopied || call2.isCopied) if (call1.isCopied) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", call1.copied) if (call2.isCopied) assertChildTtlValuesWithParentCreateAfterCreateChild("2", call2.copied) } @Test fun test_invokeAny_timeout() { val call1 = Call("1", ttlInstances) val call2 = Call("2", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val s = executorService.invokeAny(listOf(call1, call2), 1, TimeUnit.SECONDS) assertEquals("ok", s) assertTrue(call1.isCopied || call2.isCopied) if (call1.isCopied) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", call1.copied) if (call2.isCopied) assertChildTtlValuesWithParentCreateAfterCreateChild("2", call2.copied) } @Test fun test_schedule_runnable() { val task = Task("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.schedule(task, 100, TimeUnit.MILLISECONDS) assertNull(future.get()) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", task.copied) } @Test fun test_schedule_callable() { val call = Call("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.schedule(call, 100, TimeUnit.MILLISECONDS) assertEquals("ok", future.get()) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", call.copied) } @Test fun test_scheduleAtFixedRate() { val task = Task("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.scheduleAtFixedRate(task, 0, 10, TimeUnit.SECONDS) Thread.sleep(10) future.cancel(true) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", task.copied) } @Test fun test_scheduleWithFixedDelay() { val task = Task("1", ttlInstances) // create after new Task, won't see parent value in in task! createParentTtlInstancesAfterCreateChild(ttlInstances) val future = executorService.scheduleWithFixedDelay(task, 0, 10, TimeUnit.SECONDS) Thread.sleep(10) future.cancel(true) // child Inheritable assertChildTtlValuesWithParentCreateAfterCreateChild("1", task.copied) } @AfterAll fun afterAll() { executorService.shutdown() assertTrue("Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS)) } companion object { private val executorService: ScheduledExecutorService = ScheduledThreadPoolExecutor(3).let { it.setKeepAliveTime(10, TimeUnit.SECONDS) expandThreadPool(it) TtlExecutors.getTtlScheduledExecutorService(it) }!! } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/TtlExecutorsTest.kt ================================================ package com.alibaba.ttl.threadpool import com.alibaba.hasTtlAgentRun import com.alibaba.noTtlAgentRun import com.alibaba.ttl.TtlCallable import com.alibaba.ttl.TtlRunnable import com.alibaba.ttl.TtlUnwrap import com.alibaba.ttl.threadpool.TtlExecutors.* import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.string.shouldContain import org.junit.Assert.* import java.util.concurrent.* import java.util.concurrent.Executors.newScheduledThreadPool import java.util.concurrent.atomic.AtomicInteger /** * @author Jerry Lee (oldratlee at gmail dot com) */ class TtlExecutorsTest : AnnotationSpec() { /////////////////////////////////////////////// // test getTtl*ExecutorService /////////////////////////////////////////////// @Test fun test_getTtlExecutorService__common() { val newScheduledThreadPool = newScheduledThreadPool(3) getTtlExecutor(newScheduledThreadPool).let { if (noTtlAgentRun()) assertTrue(it is ExecutorTtlWrapper) assertEquals(noTtlAgentRun(), isTtlWrapper(it)) assertSame(newScheduledThreadPool, unwrap(it)) assertSame(newScheduledThreadPool, TtlUnwrap.unwrap(it)) } getTtlExecutorService(newScheduledThreadPool).let { if (noTtlAgentRun()) assertTrue(it is ExecutorServiceTtlWrapper) assertEquals(noTtlAgentRun(), isTtlWrapper(it)) assertSame(newScheduledThreadPool, unwrap(it)) assertSame(newScheduledThreadPool, TtlUnwrap.unwrap(it)) } getTtlScheduledExecutorService(newScheduledThreadPool).let { if (noTtlAgentRun()) assertTrue(it is ScheduledExecutorServiceTtlWrapper) assertEquals(noTtlAgentRun(), isTtlWrapper(it)) assertSame(newScheduledThreadPool, unwrap(it)) assertSame(newScheduledThreadPool, TtlUnwrap.unwrap(it)) } newScheduledThreadPool.shutdown() } @Test fun test_getTtlExecutorService__null() { assertNull(getTtlExecutor(null)) assertNull(getTtlExecutorService(null)) assertNull(getTtlScheduledExecutorService(null)) assertFalse(isTtlWrapper(null)) assertNull(unwrap(null)) } @Test fun test_getTtlExecutorService_is__idempotent() { val newScheduledThreadPool = newScheduledThreadPool(3) getTtlExecutor(newScheduledThreadPool)!!.let { assertSame(it, getTtlExecutor(it)) it.execute(TtlRunnable.get { }!!) } getTtlExecutorService(newScheduledThreadPool)!!.let { assertSame(it, getTtlExecutorService(it)) it.submit(TtlCallable.get { 42 }!!).get() it.submit(TtlRunnable.get { }!!, 42).get() it.submit(TtlRunnable.get { }!!).get() it.invokeAll(listOf(TtlCallable.get { 42 }!!)).map { f -> f.get() } it.invokeAll(listOf(TtlCallable.get { 42 }!!), 1, TimeUnit.SECONDS).map { f -> f.get() } it.invokeAny(listOf(TtlCallable.get { 42 }!!)) it.invokeAny(listOf(TtlCallable.get { 42 }!!), 1, TimeUnit.SECONDS) } getTtlScheduledExecutorService(newScheduledThreadPool)!!.let { assertSame(it, getTtlScheduledExecutorService(it)) it.schedule(TtlRunnable.get { }!!, 1, TimeUnit.MICROSECONDS).get() it.schedule(TtlCallable.get { 42 }!!, 1, TimeUnit.MICROSECONDS).get() it.scheduleAtFixedRate(TtlRunnable.get { }!!, 0, 1, TimeUnit.MICROSECONDS).cancel(true) it.scheduleWithFixedDelay(TtlRunnable.get { }!!, 0, 1, TimeUnit.MICROSECONDS).cancel(true) } newScheduledThreadPool.shutdown() } /////////////////////////////////////////////// // test getDisableInheritableThreadFactory /////////////////////////////////////////////// @Test fun test_getDisableInheritableThreadFactory__common() { val threadFactory = ThreadFactory { Thread(it) } getDisableInheritableThreadFactory(threadFactory).let { assertTrue(it is DisableInheritableThreadFactory) assertTrue(isDisableInheritableThreadFactory(it)) assertSame(threadFactory, unwrap(it)) assertSame(threadFactory, TtlUnwrap.unwrap(it)) } } @Test @Suppress("CAST_NEVER_SUCCEEDS") fun test_getDisableInheritableThreadFactory__null() { assertNull(getDisableInheritableThreadFactory(null)) assertFalse(isDisableInheritableThreadFactory(null)) assertNull(unwrap(null as? ThreadFactory)) } @Test fun test_getDisableInheritableThreadFactory__is_idempotent() { val threadFactory = ThreadFactory { Thread(it) } val disableInheritableThreadFactory = getDisableInheritableThreadFactory(threadFactory) assertSame(disableInheritableThreadFactory, getDisableInheritableThreadFactory(disableInheritableThreadFactory)) } /////////////////////////////////////////////// // test getTtlRunnableUnwrapComparator /////////////////////////////////////////////// @Test fun test_getTtlRunnableUnwrapComparator__common() { val comparator: Comparator = Comparator { _, _ -> throw NotImplementedError("An operation is not implemented") } getTtlRunnableUnwrapComparator(comparator).let { // use class name check instead of type check by // assertTrue(it is TtlUnwrapComparator) // // avoid test error under java 11 using TTL Agent: // // java.lang.IllegalAccessError: // failed to access class com.alibaba.ttl.threadpool.TtlUnwrapComparator // from class com.alibaba.ttl.threadpool.TtlExecutorsTest // (com.alibaba.ttl.threadpool.TtlUnwrapComparator is in unnamed module of loader 'bootstrap'; // com.alibaba.ttl.threadpool.TtlExecutorsTest is in unnamed module of loader 'app') assertEquals("com.alibaba.ttl.threadpool.TtlUnwrapComparator", it!!.javaClass.name) assertTrue(isTtlRunnableUnwrapComparator(it)) assertSame(comparator, unwrap(it)) assertSame(comparator, TtlUnwrap.unwrap(it)) } } @Test @Suppress("CAST_NEVER_SUCCEEDS") fun test_getTtlRunnableUnwrapComparator__null() { assertNull(getTtlRunnableUnwrapComparator(null)) assertFalse(isTtlRunnableUnwrapComparator(null)) assertNull(unwrap(null as? java.util.Comparator)) } @Test fun test_getTtlRunnableUnwrapComparator__is_idempotent() { val comparator: Comparator = Comparator { _, _ -> throw NotImplementedError("An operation is not implemented") } val ttlRunnableUnwrapComparator = getTtlRunnableUnwrapComparator(comparator) assertSame(ttlRunnableUnwrapComparator, getTtlRunnableUnwrapComparator(ttlRunnableUnwrapComparator)) } /** * https://github.com/alibaba/transmittable-thread-local/issues/330 */ @Test fun test_reproduce_ClassCastException_of_issue_330() { if (hasTtlAgentRun()) return val priorityBlockingQueue = PriorityBlockingQueue() Pair( BizComparableTask().also { priorityBlockingQueue.put(it) }, BizComparableTask().also { priorityBlockingQueue.put(it) }, ).let { (task0, task1) -> assertEquals(task0, priorityBlockingQueue.poll()) assertEquals(task1, priorityBlockingQueue.poll()) } BizComparableTask().also { priorityBlockingQueue.put(it) } val exception = shouldThrow { val task = BizComparableTask() priorityBlockingQueue.put(TtlRunnable.get(task)!!) } assertClassCastException(exception, TtlRunnable::class.java, Comparable::class.java) } @Test fun test_fixed_ClassCastException_of_issue_330() { val priorityBlockingQueue: BlockingQueue = if (noTtlAgentRun()) { // explicit PriorityBlockingQueue arguments PriorityBlockingQueue(11, getTtlRunnableUnwrapComparatorForComparableRunnable()) } else { // No PriorityBlockingQueue arguments PriorityBlockingQueue() } Pair( BizComparableTask().also { priorityBlockingQueue.put(it) }, BizComparableTask().let { TtlRunnable.get(it) }!!.also { priorityBlockingQueue.put(it) }, ).let { (task0, task1) -> assertEquals(task0, priorityBlockingQueue.poll()) assertEquals(task1, priorityBlockingQueue.poll()) } } @Test fun test_reproduce_ClassCastException_explicit_comparator() { if (hasTtlAgentRun()) return val priorityBlockingQueue = PriorityBlockingQueue(11, compareBy { (it as BizOrderTask).order }) Pair( BizOrderTask(1).also { priorityBlockingQueue.put(it) }, BizOrderTask(2).also { priorityBlockingQueue.put(it) }, ).let { (task0, task1) -> assertEquals(task0, priorityBlockingQueue.poll()) assertEquals(task1, priorityBlockingQueue.poll()) } BizOrderTask(3).also { priorityBlockingQueue.put(it) } val exception = shouldThrow { val task = BizOrderTask(4) priorityBlockingQueue.put(TtlRunnable.get(task)!!) } assertClassCastException(exception, TtlRunnable::class.java, BizOrderTask::class.java) } @Test fun test_fixed_ClassCastException_explicit_comparator() { val priorityBlockingQueue = PriorityBlockingQueue(11, compareBy { (it as BizOrderTask).order }.let { if (noTtlAgentRun()) getTtlRunnableUnwrapComparator(it) else it } ) Pair( BizOrderTask(1).also { priorityBlockingQueue.put(it) }, BizOrderTask(2).let { TtlRunnable.get(it) }.also { priorityBlockingQueue.put(it) }, ).let { (task0, task1) -> assertEquals(task0, priorityBlockingQueue.poll()) assertEquals(task1, priorityBlockingQueue.poll()) } } private fun assertClassCastException(e: ClassCastException, actualClass: Class<*>, targetClass: Class<*>) { withClue("ClassCastException.message: ${e.message}") { e.message shouldContain actualClass.name e.message shouldContain targetClass.name } } /** * https://github.com/alibaba/transmittable-thread-local/issues/361 */ @Test fun test_fixed_ClassCastException_of_issue_361() { val queue = PriorityBlockingQueue() queue.put(1) queue.put(100) queue.put(2) assertEquals(1, queue.poll()) assertEquals(2, queue.poll()) assertEquals(100, queue.poll()) } } private class BizComparableTask : Runnable, Comparable { companion object { val counter = AtomicInteger() } private val num = counter.getAndIncrement() override fun run() { println("BizComparableTask#run") } override fun compareTo(other: Runnable): Int = num - (other as BizComparableTask).num } private data class BizOrderTask(val order: Int) : Runnable { override fun run() { println("BizOrderTask#run") } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/TtlForkJoinPoolHelperTest.kt ================================================ package com.alibaba.ttl.threadpool import com.alibaba.ttl.TtlUnwrap import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.* import java.util.concurrent.ForkJoinPool class TtlForkJoinPoolHelperTest : AnnotationSpec() { @Test fun test_DisableInheritableForkJoinWorkerThreadFactory() { TtlForkJoinPoolHelper.getDefaultDisableInheritableForkJoinWorkerThreadFactory().let { assertTrue(it is DisableInheritableForkJoinWorkerThreadFactory) assertTrue(TtlForkJoinPoolHelper.isDisableInheritableForkJoinWorkerThreadFactory(it)) assertSame(ForkJoinPool.defaultForkJoinWorkerThreadFactory, TtlForkJoinPoolHelper.unwrap(it)) assertSame(ForkJoinPool.defaultForkJoinWorkerThreadFactory, TtlUnwrap.unwrap(it)) } } @Test fun test_null() { assertFalse(TtlForkJoinPoolHelper.isDisableInheritableForkJoinWorkerThreadFactory(null)) assertNull(TtlForkJoinPoolHelper.unwrap(null)) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/agent/TtlAgentHelperTest.kt ================================================ package com.alibaba.ttl.threadpool.agent import com.alibaba.ttl.threadpool.agent.TtlAgentHelper.* import io.kotest.core.spec.style.AnnotationSpec import io.kotest.core.test.config.TestCaseConfig import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.maps.shouldBeEmpty import io.kotest.matchers.shouldBe import org.junit.Assert.* class TtlAgentHelperTest : AnnotationSpec() { @Suppress("OVERRIDE_DEPRECATION") override fun defaultTestCaseConfig(): TestCaseConfig = TestCaseConfig(enabled = !TtlAgent.isTtlAgentLoaded()) @Test fun test_isBooleanOptionSet() { // === test with KV config only === val kvs = mapOf( "ttl.test.bool_k1" to "true", "ttl.test.bool_k2" to "false", "ttl.test.bool_k3" to "" // value absent // ttl.test.notExisted : key absent ) kvs.keys.forEach { System.clearProperty(it) } System.clearProperty("ttl.test.notExisted") assertTrue(isBooleanOptionSet(kvs, "ttl.test.bool_k1", false)) assertTrue(isBooleanOptionSet(kvs, "ttl.test.bool_k1", true)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.bool_k2", true)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.bool_k2", false)) // bool_k3 is *value absent*, IS true assertTrue(isBooleanOptionSet(kvs, "ttl.test.bool_k3", true)) assertTrue(isBooleanOptionSet(kvs, "ttl.test.bool_k3", false)) // notExisted is *key absent*, use defaultValueIfKeyAbsent assertTrue(isBooleanOptionSet(kvs, "ttl.test.notExisted", true)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.notExisted", false)) // === test with -D properties override === // override with -D properties value System.setProperty("ttl.test.bool_k1", "false") assertFalse(isBooleanOptionSet(kvs, "ttl.test.bool_k1", false)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.bool_k1", true)) // override with -D properties empty value, IS true System.setProperty("ttl.test.bool_k2", "") assertTrue(isBooleanOptionSet(kvs, "ttl.test.bool_k2", true)) assertTrue(isBooleanOptionSet(kvs, "ttl.test.bool_k2", false)) // === test with -D properties config only === mapOf( "ttl.test.property_only.bool_k1" to "true", "ttl.test.property_only.bool_k2" to "false", "ttl.test.property_only.bool_k3" to "" // value absent // ttl.test.property_only.notExisted : key absent ).forEach { (k, v) -> System.setProperty(k, v) } System.clearProperty("ttl.test.property_only.notExisted") assertTrue(isBooleanOptionSet(kvs, "ttl.test.property_only.bool_k1", false)) assertTrue(isBooleanOptionSet(kvs, "ttl.test.property_only.bool_k1", true)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.property_only.bool_k2", true)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.property_only.bool_k2", false)) // bool_k3 is *value absent*, IS true assertTrue(isBooleanOptionSet(kvs, "ttl.test.property_only.bool_k3", true)) assertTrue(isBooleanOptionSet(kvs, "ttl.test.property_only.bool_k3", false)) // notExisted is *key absent*, use defaultValueIfKeyAbsent assertTrue(isBooleanOptionSet(kvs, "ttl.test.property_only.notExisted", true)) assertFalse(isBooleanOptionSet(kvs, "ttl.test.property_only.notExisted", false)) } @Test fun test_getOptionStringListValues() { // === test with KV config only === val kvs = mapOf( "ttl.test.str_k1" to "value1", "ttl.test.str_k2" to "" // value absent // ttl.test.notExisted : key absent ) kvs.keys.forEach { System.clearProperty(it) } System.clearProperty("ttl.test.notExisted") assertEquals("value1", getStringOptionValue(kvs, "ttl.test.str_k1", "default_value")) // str_k2 is *value absent*, use default value assertEquals("default_value", getStringOptionValue(kvs, "ttl.test.str_k2", "default_value")) // notExisted is *key absent*, use default value assertEquals("default_value", getStringOptionValue(kvs, "ttl.test.notExisted", "default_value")) // === test with -D properties override === // override with -D properties value System.setProperty("ttl.test.str_k1", "value2") assertEquals("value2", getStringOptionValue(kvs, "ttl.test.str_k1", "default_value")) // override with -D properties empty value, use default value System.setProperty("ttl.test.str_k1", "") assertEquals("default_value", getStringOptionValue(kvs, "ttl.test.str_k1", "default_value")) // === test with -D properties config only === mapOf( "ttl.test.property_only.str_k1" to "value1", "ttl.test.property_only.str_k2" to "" // value absent // ttl.test.property_only.notExisted : key absent ).forEach { (k, v) -> System.setProperty(k, v) } System.clearProperty("ttl.test.property_only.notExisted") assertEquals("value1", getStringOptionValue(kvs, "ttl.test.property_only.str_k1", "default_value")) // str_k2 is *value absent*, use default value assertEquals("default_value", getStringOptionValue(kvs, "ttl.test.property_only.str_k2", "default_value")) // str_k2 is *key absent*, use default value assertEquals("default_value", getStringOptionValue(kvs, "ttl.test.property_only.notExisted", "default_value")) } @Test fun test_splitCommaColonStringToKV() { splitCommaColonStringToKV(null).shouldBeEmpty() splitCommaColonStringToKV("").shouldBeEmpty() splitCommaColonStringToKV(" ").shouldBeEmpty() splitCommaColonStringToKV("k1,k2") shouldBe mapOf("k1" to "", "k2" to "") splitCommaColonStringToKV(" k1, k2 ") shouldBe mapOf("k1" to "", "k2" to "") splitCommaColonStringToKV("ttl.agent.logger:STDOUT") shouldBe mapOf("ttl.agent.logger" to "STDOUT") splitCommaColonStringToKV("k1:v1,ttl.agent.logger:STDOUT") shouldBe mapOf("k1" to "v1", "ttl.agent.logger" to "STDOUT") splitCommaColonStringToKV(" k1 :v1 , ttl.agent.logger :STDOUT ") shouldBe mapOf("k1" to "v1", "ttl.agent.logger" to "STDOUT") splitCommaColonStringToKV(" k1 :v1 , ttl.agent.logger :STDOUT ,k3") shouldBe mapOf("k1" to "v1", "ttl.agent.logger" to "STDOUT", "k3" to "") } @Test fun test_splitListStringToStringList() { splitListStringToStringList(null).shouldBeEmpty() splitListStringToStringList("").shouldBeEmpty() splitListStringToStringList(" ").shouldBeEmpty() splitListStringToStringList(" |").shouldBeEmpty() splitListStringToStringList(" | ").shouldBeEmpty() splitListStringToStringList(" | | ").shouldBeEmpty() splitListStringToStringList("v1|v2") .shouldContainExactly("v1", "v2") splitListStringToStringList(" v1|v2 |v3 ") .shouldContainExactly("v1", "v2", "v3") splitListStringToStringList("com.alibaba.ttl.TtlExecutorTransformlet") .shouldContainExactly("com.alibaba.ttl.TtlExecutorTransformlet") splitListStringToStringList("com.alibaba.ttl.TtlExecutorTransformlet|com.alibaba.ttl.TtlForkJoinTransformlet|v3") .shouldContainExactly( "com.alibaba.ttl.TtlExecutorTransformlet", "com.alibaba.ttl.TtlForkJoinTransformlet", "v3" ) splitListStringToStringList(" com.alibaba.ttl.TtlExecutorTransformlet| com.alibaba.ttl.TtlForkJoinTransformlet |v3 ") .shouldContainExactly( "com.alibaba.ttl.TtlExecutorTransformlet", "com.alibaba.ttl.TtlForkJoinTransformlet", "v3" ) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/agent/TtlExtensionTransformletManagerTest.kt ================================================ package com.alibaba.ttl.threadpool.agent import com.alibaba.noTtlAgentRun import com.alibaba.ttl.threadpool.agent.logging.Logger import io.kotest.core.spec.style.AnnotationSpec import io.kotest.core.test.config.TestCaseConfig import org.junit.Assert.assertEquals class TtlExtensionTransformletManagerTest : AnnotationSpec() { @Suppress("OVERRIDE_DEPRECATION") override fun defaultTestCaseConfig(): TestCaseConfig = TestCaseConfig(enabled = noTtlAgentRun()) @Test fun test_readLines() { val classLoader = TtlExtensionTransformletManagerTest::class.java.classLoader val pair = TtlExtensionTransformletManager.readLinesFromExtensionFiles( classLoader.getResources("test_extension/foo.txt"), mutableMapOf() ) val lines: LinkedHashSet = pair.first assertEquals( linkedSetOf("hello.World", "hello.tabBefore", "hello.tabAfter", "hello.spaceBefore", "hello.spaceAfter"), lines ) } @BeforeAll fun beforeAll() { Logger.setLoggerImplTypeIfNotSetYet("stderr") } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/agent/transformlet/helper/TtlTransformletHelperTest.kt ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.helper import com.alibaba.ttl.threadpool.agent.TtlAgent import com.alibaba.ttl.threadpool.agent.logging.Logger import com.alibaba.ttl.threadpool.agent.transformlet.helper.TtlTransformletHelper.* import io.kotest.core.spec.style.AnnotationSpec import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldEndWith import javassist.ClassPool import org.apache.commons.lang3.StringUtils import org.junit.Assert.* class TtlTransformletHelperTest : AnnotationSpec() { @Test fun test_getFileLocationOfClass_javaClass() { getLocationFileOfClass(String::class.java).shouldBeNull() val locationFileOfClass = getLocationFileOfClass(StringUtils::class.java) locationFileOfClass shouldEndWith ".jar" locationFileOfClass shouldContain "/commons-lang3-" } @Test fun test_getFileLocationOfClass_ctClass() { if (TtlAgent.isTtlAgentLoaded()) return val classPool = ClassPool(true) // Java 8: file:/path/to/jdk_8/jre/lib/rt.jar!/java/lang/String.class // Java 11: /java.base/java/lang/String.class getLocationFileOfClass(classPool.getCtClass("java.lang.String")) .shouldEndWith("/java/lang/String.class") // Java 8: file:/path/to/commons-lang3-3.5.jar!/org/apache/commons/lang3/StringUtils.class val locationFileOfClass = getLocationFileOfClass(classPool.getCtClass("org.apache.commons.lang3.StringUtils")) locationFileOfClass shouldEndWith ".jar!/org/apache/commons/lang3/StringUtils.class" locationFileOfClass shouldContain "/commons-lang3-" } @Test fun test_className_package() { assertEquals("", getPackageName("Hello")) assertEquals("com.foo", getPackageName("com.foo.Hello")) assertTrue(isClassAtPackage("java.util.TimerTask", "java.util")) assertFalse(isClassAtPackage("java.util.TimerTask", "java.utils")) assertFalse(isClassAtPackage("java.util.TimerTask", "java")) assertFalse(isClassAtPackage("java.util.TimerTask", "java.util.zip")) assertTrue(isClassUnderPackage("java.util.TimerTask", "java.util")) assertFalse(isClassUnderPackage("java.util.TimerTask", "java.utils")) assertTrue(isClassUnderPackage("java.util.TimerTask", "java")) assertFalse(isClassUnderPackage("java.util.TimerTask", "javax")) assertTrue(isClassAtPackageJavaUtil("java.util.PriorityQueue")) assertFalse(isClassAtPackageJavaUtil("java.util.zip.ZipInputStream")) } @BeforeAll fun beforeClass() { Logger.setLoggerImplTypeIfNotSetYet("stderr") } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/ttl/threadpool/agent/transformlet/internal/UtilsTest.java ================================================ package com.alibaba.ttl.threadpool.agent.transformlet.internal; import org.junit.Test; import java.util.HashMap; import java.util.Map; import static org.junit.Assert.fail; public class UtilsTest { @Test public void test_get_unboxing_boolean_fromMap() { Map map = new HashMap<>(); try { getUnboxingBoolean(map, "not_existed"); fail(); } catch (NullPointerException expected) { // do nothing } } private static boolean getUnboxingBoolean(Map map, String key) { return (Boolean) map.get(key); } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/user_api_test/README.md ================================================ User API Test Cases ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/user_api_test/ttl/DisableIgnoreNullValueSemanticsTest.kt ================================================ package com.alibaba.user_api_test.ttl import com.alibaba.ttl.TransmittableThreadLocal import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Test import java.util.concurrent.FutureTask import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread /** * Test the "Ignore-Null-Value Semantics" of [TransmittableThreadLocal] from user code(different package) */ class DisableIgnoreNullValueSemanticsTest { @Test fun test_TTL_not_disableIgnoreNullValueSemantics_defaultTtlBehavior() { val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String { return "init" } override fun childValue(parentValue: String?): String { return "$parentValue + child" } } assertEquals("init", ttl.get()) ttl.set(null) // DO NOT `ttl.get()` ! // `get` operation will re-init the value of ThreadLocal val task = FutureTask { ttl.get() } thread { task.run() }.join() // `get` operation will re-init the value of ThreadLocal ! assertEquals("init", ttl.get()) // "Ignore-Null-Value Semantics" will not transmit ThreadLocal with the null value, // so the value in new thread is "init" value assertEquals("init", task.get()) ////////////////////////////////////// val task2 = FutureTask { ttl.get() } thread { task2.run() }.join() assertEquals("init", ttl.get()) assertEquals("init + child", task2.get()) } @Test fun test_TTL_not_disableIgnoreNullValueSemantics_defaultTtlBehavior_getSafe_ForNullInit() { val count = AtomicInteger() val ttl = object : TransmittableThreadLocal() { override fun initialValue(): String? { count.getAndIncrement() return super.initialValue() } override fun childValue(parentValue: String?): String? { count.getAndSet(1000) return super.childValue(parentValue) } } assertNull(ttl.get()) assertEquals(1, count.get()) ttl.set(null) assertNull(ttl.get()) assertEquals(2, count.get()) } @Test fun test_TTL_disableIgnoreNullValueSemantics_sameAsThreadLocal() { val ttl = object : TransmittableThreadLocal(true) { override fun initialValue(): String { return "init" } override fun childValue(parentValue: String?): String { return "$parentValue + child" } } assertEquals("init", ttl.get()) ttl.set(null) assertNull(ttl.get()) val task = FutureTask { ttl.get() } thread { task.run() }.join() assertNull(ttl.get()) assertEquals("null + child", task.get()) ////////////////////////////////////// val task2 = FutureTask { ttl.get() } thread { task2.run() }.join() assertNull(ttl.get()) assertEquals("null + child", task.get()) } @Test fun test_InheritableThreadLocal() { val ttl = object : InheritableThreadLocal() { override fun initialValue(): String { return "init" } override fun childValue(parentValue: String?): String { return "$parentValue + child" } } assertEquals("init", ttl.get()) ttl.set(null) assertNull(ttl.get()) val task = FutureTask { ttl.get() } thread { task.run() }.join() assertNull(ttl.get()) assertEquals("null + child", task.get()) ////////////////////////////////////// val task2 = FutureTask { ttl.get() } thread { task2.run() }.join() assertNull(ttl.get()) assertEquals("null + child", task.get()) } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/user_api_test/ttl/TransmittableThreadLocal_Transmitter_UserTest.kt ================================================ package com.alibaba.user_api_test.ttl import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.TransmittableThreadLocal.Transmitter import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.* import java.util.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit /** * Test [Transmitter] from user code(different package) */ class TransmittableThreadLocal_Transmitter_UserTest : AnnotationSpec() { @Test fun test_crr() { val ttl = TransmittableThreadLocal() ttl.set(PARENT) val capture = Transmitter.capture() val future = executorService.submit { ttl.set(CHILD) val backup = Transmitter.replay(capture) assertEquals(PARENT, ttl.get()) Transmitter.restore(backup) assertEquals(CHILD, ttl.get()) } assertEquals(PARENT, ttl.get()) future.get(1, TimeUnit.SECONDS) assertEquals(PARENT, ttl.get()) } @Test fun test_clear_restore() { val ttl = TransmittableThreadLocal() ttl.set(PARENT) val future = executorService.submit { ttl.set(CHILD) val backup = Transmitter.clear() assertNull(ttl.get()) Transmitter.restore(backup) assertEquals(CHILD, ttl.get()) } assertEquals(PARENT, ttl.get()) future.get(1, TimeUnit.SECONDS) assertEquals(PARENT, ttl.get()) } @Test fun test_runSupplierWithCaptured() { val ttl = TransmittableThreadLocal() ttl.set(PARENT) val capture = Transmitter.capture() val future = executorService.submit { ttl.set("child") Transmitter.runSupplierWithCaptured(capture) { assertEquals(PARENT, ttl.get()) ttl.get() } } assertEquals(PARENT, ttl.get()) future.get(1, TimeUnit.SECONDS) assertEquals(PARENT, ttl.get()) } @Test fun test_runSupplierWithClear() { val ttl = TransmittableThreadLocal() ttl.set(PARENT) val future = executorService.submit { ttl.set("child") Transmitter.runSupplierWithClear { assertNull(ttl.get()) ttl.get() } } assertEquals(PARENT, ttl.get()) future.get(1, TimeUnit.SECONDS) assertEquals(PARENT, ttl.get()) } @Test fun test_runCallableWithCaptured() { val ttl = TransmittableThreadLocal() ttl.set(PARENT) val capture = Transmitter.capture() val future = executorService.submit { ttl.set("child") try { Transmitter.runCallableWithCaptured(capture) { assertEquals(PARENT, ttl.get()) ttl.get() } } catch (e: Exception) { throw RuntimeException(e) } } assertEquals(PARENT, ttl.get()) future.get(1, TimeUnit.SECONDS) assertEquals(PARENT, ttl.get()) } @Test fun test_runCallableWithClear() { val ttl = TransmittableThreadLocal() ttl.set(PARENT) val future = executorService.submit { ttl.set("child") try { Transmitter.runCallableWithClear { assertNull(ttl.get()) ttl.get() } } catch (e: Exception) { throw RuntimeException(e) } } assertEquals(PARENT, ttl.get()) future.get(1, TimeUnit.SECONDS) assertEquals(PARENT, ttl.get()) } @AfterAll fun afterAll() { executorService.shutdown() assertTrue("Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS)) } companion object { private val PARENT = "parent: " + Date() private val CHILD = "child: " + Date() private val executorService: ExecutorService = Executors.newFixedThreadPool(3).also { expandThreadPool(it) } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/user_api_test/ttl/TransmittableThreadLocal_Transmitter_registerTransmittee_UserTest.kt ================================================ package com.alibaba.user_api_test.ttl import com.alibaba.noTtlAgentRun import com.alibaba.ttl.TransmittableThreadLocal.Transmitter import io.kotest.core.spec.style.AnnotationSpec import io.kotest.core.test.config.TestCaseConfig import io.kotest.matchers.booleans.shouldBeTrue import io.mockk.* import org.apache.commons.lang3.JavaVersion import org.apache.commons.lang3.SystemUtils /** * Test [Transmitter] from user code(different package) */ class TransmittableThreadLocal_Transmitter_registerTransmittee_UserTest : AnnotationSpec() { @Suppress("OVERRIDE_DEPRECATION") override fun defaultTestCaseConfig(): TestCaseConfig { // If run under Agent and under java 11+, fail to find proxy classes; // so just skipped. // // error info: // java.lang.NoClassDefFoundError: io/mockk/proxy/jvm/advice/jvm/JvmMockKProxyInterceptor // more info error info see: // https://github.com/alibaba/transmittable-thread-local/runs/7826806473?check_suite_focus=true if (SystemUtils.isJavaVersionAtMost(JavaVersion.JAVA_1_8)) { return TestCaseConfig(enabled = true) } return TestCaseConfig(enabled = noTtlAgentRun()) } @Test fun test_registerTransmittee_crr() { // ======================================== // 0. mocks creation and stubbing // ======================================== val transmittee = mockk, Set>>() @Suppress("UnusedEquals", "ReplaceCallWithBinaryOperator") excludeRecords { transmittee.equals(any()) transmittee.hashCode() } every { transmittee.capture() } returns listOf("42", "43") every { transmittee.replay(listOf("42", "43")) } returns setOf(42, 43) every { transmittee.restore(setOf(42, 43)) } just Runs try { // ======================================== // 1. mock record(aka. invocation) // ======================================== Transmitter.registerTransmittee(transmittee).shouldBeTrue() val captured = Transmitter.capture() val backup = Transmitter.replay(captured) Transmitter.restore(backup) // ======================================== // 2. mock verification // ======================================== verifySequence { transmittee.capture() transmittee.replay(any()) transmittee.restore(any()) } confirmVerified(transmittee) } finally { Transmitter.unregisterTransmittee(transmittee).shouldBeTrue() } } @Test fun test_registerTransmittee_clear_restore() { // ======================================== // 0. mocks creation and stubbing // ======================================== val transmittee = mockk, Set>>() @Suppress("UnusedEquals", "ReplaceCallWithBinaryOperator") excludeRecords { transmittee.equals(any()) transmittee.hashCode() } every { transmittee.clear() } returns setOf(42, 43) every { transmittee.restore(setOf(42, 43)) } just Runs try { // ======================================== // 1. mock record(aka. invocation) // ======================================== Transmitter.registerTransmittee(transmittee).shouldBeTrue() val backup = Transmitter.clear() Transmitter.restore(backup) // ======================================== // 2. mock verification // ======================================== verifySequence { transmittee.clear() transmittee.restore(any()) } confirmVerified(transmittee) } finally { Transmitter.unregisterTransmittee(transmittee).shouldBeTrue() } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/user_api_test/ttl/TransmittableThreadLocal_withInit_Null_Test.java ================================================ package com.alibaba.user_api_test.ttl; import com.alibaba.ttl.TransmittableThreadLocal; import org.junit.Test; import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class TransmittableThreadLocal_withInit_Null_Test { @Test public void test_null__withInitial() { try { TransmittableThreadLocal.withInitial(null); fail(); } catch (NullPointerException e) { assertEquals("supplier is null", e.getMessage()); } } @Test public void test_null__withInitialAndCopier_2() { try { TransmittableThreadLocal.withInitialAndCopier(null, null); fail(); } catch (NullPointerException e) { assertEquals("supplier is null", e.getMessage()); } try { TransmittableThreadLocal.withInitialAndCopier((Supplier) () -> null, null); fail(); } catch (NullPointerException e) { assertEquals("ttl copier is null", e.getMessage()); } } @Test public void test_null__withInitialAndCopier_3() { try { TransmittableThreadLocal.withInitialAndCopier(null, null, null); fail(); } catch (NullPointerException e) { assertEquals("supplier is null", e.getMessage()); } try { TransmittableThreadLocal.withInitialAndCopier((Supplier) () -> null, null, null); fail(); } catch (NullPointerException e) { assertEquals("ttl copier for child value is null", e.getMessage()); } try { TransmittableThreadLocal.withInitialAndCopier((Supplier) () -> null, parentValue -> null, null); fail(); } catch (NullPointerException e) { assertEquals("ttl copier for copy value is null", e.getMessage()); } } } ================================================ FILE: ttl2-compatible/src/test/java/com/alibaba/user_api_test/ttl/TransmittableThreadLocal_withInit_Test.kt ================================================ package com.alibaba.user_api_test.ttl import com.alibaba.expandThreadPool import com.alibaba.ttl.TransmittableThreadLocal import com.alibaba.ttl.threadpool.TtlExecutors import io.kotest.core.spec.style.AnnotationSpec import org.junit.Assert.* import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import kotlin.concurrent.thread class TransmittableThreadLocal_withInit_Test : AnnotationSpec() { @Test fun test_withInit() { val ttl: TransmittableThreadLocal = TransmittableThreadLocal.withInitial { 42 } assertNotNull(ttl) assertEquals(42, ttl.get()) val atomicInteger = AtomicInteger(-1) thread { atomicInteger.set(ttl.get()) }.join() assertEquals(42, atomicInteger.get()) atomicInteger.set(-1) executorService.submit { atomicInteger.set(ttl.get()) }.get() assertEquals(42, atomicInteger.get()) } @Test fun test_withInitialAndCopier_2() { val ttl = TransmittableThreadLocal.withInitialAndCopier( { 42 }, { it + 100 }, ) assertNotNull(ttl) assertEquals(42, ttl.get()) val atomicInteger = AtomicInteger(-1) thread { atomicInteger.set(ttl.get()) }.join() assertEquals(142, atomicInteger.get()) atomicInteger.set(-1) executorService.submit { atomicInteger.set(ttl.get()) }.get() assertEquals(142, atomicInteger.get()) } @Test fun test_withInitialAndCopier_3() { val ttl = TransmittableThreadLocal.withInitialAndCopier( { 42 }, { it + 100 }, { it + 1000 }, ) assertNotNull(ttl) assertEquals(42, ttl.get()) val atomicInteger = AtomicInteger(-1) thread { atomicInteger.set(ttl.get()) }.join() assertEquals(142, atomicInteger.get()) atomicInteger.set(-1) executorService.submit { atomicInteger.set(ttl.get()) }.get() assertEquals(1042, atomicInteger.get()) } @AfterAll fun afterClass() { executorService.shutdown() assertTrue( "Fail to shutdown thread pool", executorService.awaitTermination(1, TimeUnit.SECONDS) ) } companion object { private val executorService: ExecutorService = Executors.newFixedThreadPool(3).let { expandThreadPool(it) TtlExecutors.getTtlExecutorService(it)!! } } } ================================================ FILE: ttl2-compatible/src/test/resources/io/mockk/settings.properties ================================================ stackTracesAlignment=left ================================================ FILE: ttl2-compatible/src/test/resources/log4j.xml ================================================ ================================================ FILE: ttl2-compatible/src/test/resources/test_extension/foo.txt ================================================ # comments hello.World # comments spaceInside space tabInside tab hello.tabBefore hello.tabAfter hello.spaceBefore hello.spaceAfter hello.!IllegalName