Skip to content

Commit 237aa98

Browse files
Stephen-BaoStephen-Bao
and
Stephen-Bao
authored
Thread safety enhancements (#111)
* Introduced changes to make the library thread-safe. * Added thread-safety tests for the library. * Added jmh benchmarking for ReadWriteLock & StampedLock * Adjusted the code format * Made some changes to test cases to introduce more concurrency * Finished concurrency test case revision * Changed benchmark to use fixed batch size and updated the results * Added benchmark section in README * Added documentation on synchronization policy * Adjusted the code format * retry integ test * code format * made a minor change * rerun integ test * Added thread-safety measures to newly merged features * Moving literals to constants Co-authored-by: Stephen-Bao <[email protected]>
1 parent f8d6cf7 commit 237aa98

File tree

13 files changed

+1163
-38
lines changed

13 files changed

+1163
-38
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,24 @@ config.setAgentEndpoint("udp://127.0.0.1:1000");
346346
AWS_EMF_AGENT_ENDPOINT="udp://127.0.0.1:1000"
347347
```
348348

349+
## Thread-safety
350+
351+
### Internal Synchronization
352+
353+
The MetricsLogger class is thread-safe. Specifically, the generalized multi-threading use cases for this library are:
354+
355+
1. Collect some metrics or metadata on a single MetricsLogger; Pass the logger into one or more async contexts where new metrics or metadata can be added concurrently; Join the async contexts (e.g. Future.get()) and flush the metrics.
356+
2. Collect some metrics or metadata on a single MetricsLogger; Pass the logger into an async context; Flush from the async context concurrently.
357+
358+
Thread-safety for the first use case is achieved by introducing concurrent internal data structures and atomic operations associated with these models, to ensure the access to shared mutable resources are always synchronized.
359+
360+
Thread-safety for the second use case is achieved by using a ReentrantReadWriteLock. This lock is used to create an internal sync context for flush() method in multi-threading situations. `flush()` acquires write lock, while other methods (which have access to mutable shared data with `flush()`) acquires read lock. This makes sure `flush()` is always executed exclusively, while other methods can be executed concurrently.
361+
362+
### Use Cases that are Not Covered
363+
364+
With all the internal synchronization measures, however, there're still certain multi-threading use cases that are not covered by this library, which might require external synchronizations or other protection measures.
365+
This is due to the fact that the execution order of APIs are not determined in async contexts. For example, if user needs to associate a given set of properties with a metric in each thread, the results are not guaranteed since the execution order of `putProperty()` is not determined across threads. In such cases, we recommend using a different MetricsLogger instance for different threads, so that no resources are shared and no thread-safety problem would ever happen. Note that this can often be simplified by using a ThreadLocal variable.
366+
349367
## Examples
350368

351369
Check out the [examples](https://github.com/awslabs/aws-embedded-metrics-java/tree/master/examples) directory to get started.
@@ -392,6 +410,14 @@ To auto fix code style, run
392410
./gradlew :spotlessApply
393411
```
394412

413+
### Benchmark
414+
415+
We use [JMH](https://github.com/openjdk/jmh) as our framework for concurrency performance benchmarking. Benchmarks can be run by:
416+
```
417+
./gradlew jmh
418+
```
419+
To run a single benchmark, consider using JMH plugins. For example, [JMH plugin for IntelliJ IDEA](https://github.com/artyushov/idea-jmh-plugin)
420+
395421
## License
396422

397423
This project is licensed under the Apache-2.0 License.

build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ plugins {
1818
id 'com.diffplug.spotless' version '5.8.2'
1919
id 'maven-publish'
2020
id 'signing'
21+
id "me.champeau.jmh" version "0.6.6"
2122
}
2223

2324
group "software.amazon.cloudwatchlogs"
@@ -78,6 +79,10 @@ dependencies {
7879
testImplementation 'software.amazon.awssdk:cloudwatch:2.13.54'
7980
testCompileOnly 'org.projectlombok:lombok:1.18.12'
8081
testAnnotationProcessor 'org.projectlombok:lombok:1.18.12'
82+
83+
implementation 'org.openjdk.jmh:jmh-core:1.29'
84+
implementation 'org.openjdk.jmh:jmh-generator-annprocess:1.29'
85+
jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.29'
8186
}
8287

8388
spotless {
@@ -124,7 +129,7 @@ tasks.withType(JavaCompile) {
124129
}
125130

126131
tasks.named('wrapper') {
127-
gradleVersion = '6.5.1'
132+
gradleVersion = '7.4.2'
128133
distributionType = Wrapper.DistributionType.ALL
129134
}
130135

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)