Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture context snapshots as part of the API #527

Merged
merged 13 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions context-propagation-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ although technically these use cases are not appropriate.
Manages contexts by initializing and maintaining an active context value.

Normally it is not necessary to interact directly with individual context managers.
The `ContextManagers` utility class detects available context managers and lets
you take a [_snapshot_](#context-snapshot) of **all** active contexts at once.
The api detects available context managers and lets
you capture a [_snapshot_](#context-snapshot) of **all** active contexts at once.

- [ContextManager javadoc][contextmanager]
- [ContextManagers javadoc][contextmanagers]
- [ContextSnapshot javadoc][contextsnapshot]

### Context Snapshot

A context snapshot is created by the [ContextManagers]' `createContextSnapshot()` method.
A context snapshot is captured by the [ContextSnapshot]' `capture()` method.
The snapshot contains active context values from all known [ContextManager] implementations.
Once created, the captured _values_ in such context snapshot will not change anymore,
even when the active context is later modified.
Expand All @@ -52,7 +52,6 @@ They stay active until the reactivation is closed again (or are overwritten by n
Closing the reactivated object is mandatory (from the thread where the reactivation was called).

- [ContextSnapshot javadoc](https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/ContextSnapshot.html)
- [ContextManagers javadoc](https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/ContextManagers.html)

## Creating your own context manager

Expand All @@ -68,23 +67,24 @@ It should contain the fully qualified classname of your implementation.

```java
public class DummyContextManager implements ContextManager<String> {
public Context<String> initializeNewContext(String value) {
return new DummyContext(value);
public Context<String> initializeNewContext(String value) {
return new DummyContext(value);
}

public Context<String> getActiveContextValue() {
DummyContext current = DummyContext.current();
return current != null ? current.getValue() : null;
}

private static final class DummyContext extends AbstractThreadLocalContext<String> {
private DummyContext(String newValue) {
super(newValue);
}

public Context<String> getActiveContext() {
return DummyContext.current();
}

private static final class DummyContext extends AbstractThreadLocalContext<String> {
private DummyContext(String newValue) {
super(newValue);
}

private static Context<String> current() {
return AbstractThreadLocalContext.current(DummyContext.class);
}
private static Context<String> current() {
return AbstractThreadLocalContext.current(DummyContext.class);
}
}
}
```

Expand All @@ -95,7 +95,6 @@ public class DummyContextManager implements ContextManager<String> {
[javadoc]: https://www.javadoc.io/doc/nl.talsmasoftware.context/context-propagation

[threadlocal]: https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html
[context]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/Context.html
[contextsnapshot]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/ContextSnapshot.html
[contextmanager]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/ContextManager.html
[contextmanagers]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/ContextManagers.html
[context]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/api/Context.html
[contextsnapshot]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/api/ContextSnapshot.html
[contextmanager]: https://javadoc.io/page/nl.talsmasoftware.context/context-propagation/latest/nl/talsmasoftware/context/api/ContextManager.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
*/
public interface Context<T> extends Closeable {

// TODO think about removing Context.getValue as ContextManager.getActiveContextValue() should suffice.

/**
* The value associated with this context.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,62 @@ public interface ContextManager<T> {
* to clear all contexts before returning threads to the pool.
*
* <p>
* This method normally should only get called by {@code ContextManagers.clearActiveContexts()}.
* This method normally should only get called by {@linkplain #clearAll()}.
*
* @since 2.0.0
*/
void clear();

/**
* Clears all active contexts from the current thread.
*
* <p>
* Contexts that are 'stacked' (i.e. restore the previous state upon close)
* should be closed in a way that includes all 'parent' contexts as well.
*
* <p>
* This operation is not intended to be used by general application code
* as it likely breaks any 'stacked' active context that surrounding code may depend upon.
*
* <p>
* Appropriate use includes thread management, where threads are reused by some pooling mechanism.<br>
* For example, it is considered safe to clear the context when obtaining a 'fresh' thread from a
* thread pool (as no context expectations should exist at that point).<br>
* An even better strategy would be to clear the context right before returning a used thread
* back to the pool as this will allow any unclosed contexts to be garbage collected.<br>
* Besides preventing contextual issues, this reduces the risk of memory leaks by unbalanced context calls.
*
* @since 2.0.0
*/
static void clearAll() {
ContextSnapshotImpl.clearActiveContexts();
}

/**
* Override the {@linkplain ClassLoader} used to lookup {@linkplain ContextManager context managers}.
*
* <p>
* Normally, capturing a snapshot uses the {@linkplain Thread#getContextClassLoader() Context ClassLoader} from the
* {@linkplain Thread#currentThread() current thread} to look up all {@linkplain ContextManager context managers}.
* It is possible to configure a fixed, single classloader in your application for these lookups.
*
* <p>
* Using this method to specify a fixed classloader will only impact
* <strong>new</strong> {@linkplain ContextSnapshot context snapshots}.<br>
* Existing snapshots will <strong>not</strong> be impacted.
*
* <p>
* <strong>Notes:</strong><br>
* <ul>
* <li>Please be aware that this configuration is global!
* <li>This will also affect the lookup of {@linkplain ContextTimer context timers}
* </ul>
*
* @param classLoader The single, fixed ClassLoader to use for finding context managers.
* Specify {@code null} to restore the default behaviour.
* @since 2.0.0
*/
static void useClassLoader(ClassLoader classLoader) {
ServiceCache.useClassLoader(classLoader);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
* ensuring that all context values are set in that other thread.
*
* <p>
* A snapshot can be obtained from the {@code ContextManagers} utility class for interaction with all registered
* {@link ContextManager} implementations.
* A snapshot can be obtained from the {@linkplain #capture()} method.
*
* <p>
* This library contains several utility classes named {@code ContextAware...} or {...WithContext} that will
Expand All @@ -41,6 +40,23 @@
* @since 2.0.0
*/
public interface ContextSnapshot {
/**
* Captures a snapshot of the current
* {@link ContextManager#getActiveContextValue() active context value}
* from <em>all known {@link ContextManager}</em> implementations.
*
* <p>
* This snapshot with context values is returned as a single object and can be temporarily
* {@link ContextSnapshot#reactivate() reactivated}.
* Remember to {@link Context#close() close} the reactivated context once you're done,
* preferably in a <code>try-with-resources</code> construct.
*
* @return A new context snapshot that can be reactivated elsewhere (e.g. a background thread)
* within a try-with-resources construct.
*/
static ContextSnapshot capture() {
return ContextSnapshotImpl.capture();
}

/**
* Temporarily reactivates all captured context values that are in the snapshot.
Expand Down
Loading
Loading