• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

talsma-ict / context-propagation / #1481

01 Nov 2024 04:47PM UTC coverage: 91.679% (-3.1%) from 94.737%
#1481

push

web-flow
Add deprecations for v2 simplification (#510)

* Deprecate Clearable to be removed in next major release.
* Deprecate complicated things and introduce new ContextObserver mechanism.
* Switch to new minor version due to relatively big change in this PR.
* Move ContextSnapshot to api package.
* Move Context to api package and improve documentation.
* Move ContextManager to api package and improve documentation.
* Simplify Wrapper base class.
* Simplify Wrapper-related classes and methods.
* Move ContextManagers class to core package.
* Move ContextTimer service interface to api package.
* Remove observer registration methods that were never released.
* Add test for new wrapper method.
* Move Wrapper base class to core package
* Deprecate ContextSnapshotSupplier
* Move DelegatingFuture to core.delegation package
* Move DelegatingExecutorService to core.delegation package
* Move WrapperWithContext to core.delegation package
* Move CallMappingExecutorService to core.delegation package
* Move ContextAwareExecutorService to core.concurrent package
* Move AbstractThreadLocalContext to core.threadlocal package
* Move ContextAwareCompletableFuture to core.concurrent package
* Move BiConsumerWithContext to core.function package
* Move BiFunctionWithContext to core.function package
* Move BinaryOperatorWithContext to core.function package
* Move BiPredicateWithContext to core.function package
* Move BooleanSupplierWithContext to core.function package
* Move ConsumerWithContext to core.function package
* Move FunctionWithContext to core.function package
* Move PredicateWithContext to core.function package
* Move RunnableWithContext to core.function package
* Move SupplierWithContext to core.function package
* Move UnaryOperatorWithContext to core.function package
* Clean up left over stuff from old ContextManagers class.
* Rename Timers logger.
* Add unit test for concurrent observer registration.
* Add unit test new context obse... (continued)

758 of 832 new or added lines in 46 files covered. (91.11%)

9 existing lines in 3 files now uncovered.

1256 of 1370 relevant lines covered (91.68%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.26
/context-propagation/src/main/java/nl/talsmasoftware/context/core/ContextManagers.java
1
/*
2
 * Copyright 2016-2024 Talsma ICT
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *         http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package nl.talsmasoftware.context.core;
17

18
import nl.talsmasoftware.context.Context;
19
import nl.talsmasoftware.context.ContextManager;
20
import nl.talsmasoftware.context.ContextSnapshot;
21
import nl.talsmasoftware.context.api.ContextObserver;
22
import nl.talsmasoftware.context.api.ContextSnapshot.Reactivation;
23
import nl.talsmasoftware.context.clearable.Clearable;
24
import nl.talsmasoftware.context.delegation.Wrapper;
25

26
import java.util.ArrayList;
27
import java.util.Arrays;
28
import java.util.Iterator;
29
import java.util.LinkedList;
30
import java.util.List;
31
import java.util.concurrent.CopyOnWriteArrayList;
32
import java.util.logging.Level;
33
import java.util.logging.Logger;
34

35
/**
36
 * Core implementation to allow {@link #createContextSnapshot() taking a snapshot of all contexts}.
37
 *
38
 * <p>
39
 * Such a {@link ContextSnapshot snapshot} can be passed to a background task to allow the context to be
40
 * {@link ContextSnapshot#reactivate() reactivated} in that background thread, until it gets
41
 * {@link Context#close() closed} again (preferably in a <code>try-with-resources</code> construct).
42
 *
43
 * @author Sjoerd Talsma
44
 * @since 1.1.0
45
 */
46
public final class ContextManagers {
47
    private static final Logger LOGGER = Logger.getLogger(ContextManagers.class.getName());
1✔
48

49
    /**
50
     * The service loader that loads (and possibly caches) {@linkplain ContextManager} instances in prioritized order.
51
     */
52
    private static final PriorityServiceLoader<ContextManager> CONTEXT_MANAGERS =
1✔
53
            new PriorityServiceLoader<ContextManager>(ContextManager.class);
54

55
    /**
56
     * Registered observers.
57
     */
58
    private static final CopyOnWriteArrayList<ObservableContextManager> OBSERVERS =
1✔
59
            new CopyOnWriteArrayList<ObservableContextManager>();
60

61
    /**
62
     * Private constructor to avoid instantiation of this class.
63
     */
NEW
64
    private ContextManagers() {
×
NEW
65
        throw new UnsupportedOperationException();
×
66
    }
67

68
    /**
69
     * This method is able to create a 'snapshot' from the current
70
     * {@link ContextManager#getActiveContext() active context} from <em>all known {@link ContextManager}</em>
71
     * implementations.
72
     *
73
     * <p>
74
     * This snapshot is returned as a single object that can be temporarily
75
     * {@link ContextSnapshot#reactivate() reactivated}. Don't forget to {@link Context#close() close} the reactivated
76
     * context once you're done, preferably in a <code>try-with-resources</code> construct.
77
     *
78
     * @return A new snapshot that can be reactivated elsewhere (e.g. a background thread)
79
     * within a try-with-resources construct.
80
     */
81
    public static nl.talsmasoftware.context.api.ContextSnapshot createContextSnapshot() {
82
        final long start = System.nanoTime();
1✔
83
        final List<ContextManager> managers = new LinkedList<ContextManager>();
1✔
84
        final List<Object> values = new LinkedList<Object>();
1✔
85
        Long managerStart = null;
1✔
86
        for (ContextManager manager : getContextManagers()) {
1✔
87
            managerStart = System.nanoTime();
1✔
88
            try {
89
                final Context activeContext = manager.getActiveContext();
1✔
90
                if (activeContext != null) {
1✔
91
                    values.add(activeContext.getValue());
1✔
92
                    managers.add(manager);
1✔
93
                    if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
94
                        LOGGER.finest("Active context of " + manager + " added to new snapshot: " + activeContext + ".");
×
95
                    }
96
                    Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "getActiveContext");
1✔
97
                } else if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
98
                    LOGGER.log(Level.FINEST, "There is no active context for " + manager + " in this snapshot.");
×
99
                }
100
            } catch (RuntimeException rte) {
1✔
101
                CONTEXT_MANAGERS.clearCache();
1✔
102
                LOGGER.log(Level.WARNING, "Exception obtaining active context from " + manager + " for snapshot.", rte);
1✔
103
                Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "getActiveContext.exception");
1✔
104
            }
1✔
105
        }
1✔
106
        if (managerStart == null) {
1✔
107
            NoContextManagersFound noContextManagersFound = new NoContextManagersFound();
1✔
108
            LOGGER.log(Level.INFO, noContextManagersFound.getMessage(), noContextManagersFound);
1✔
109
        }
110
        ContextSnapshotImpl result = new ContextSnapshotImpl(managers, values);
1✔
111
        Timers.timed(System.nanoTime() - start, ContextManagers.class, "createContextSnapshot");
1✔
112
        return result;
1✔
113
    }
114

115
    /**
116
     * Clears all active contexts from the current thread.
117
     * <p>
118
     * Contexts that are 'stacked' (i.e. restore the previous state upon close) should be
119
     * closed in a way that includes all 'parent' contexts as well.
120
     * <p>
121
     * This operation is not intended to be used by general application code as it likely breaks any 'stacked'
122
     * active context that surrounding code may depend upon.
123
     * Appropriate use includes thread management, where threads are reused by some pooling
124
     * mechanism. For example, it is considered safe to clear the context when obtaining a 'fresh' thread from a
125
     * thread pool (as no context expectations should exist at that point).
126
     * An even better strategy would be to clear the context right before returning a used thread to the pool
127
     * as this will allow any unclosed contexts to be garbage collected. Besides preventing contextual issues,
128
     * this reduces the risk of memory leaks by unbalanced context calls.
129
     * <p>
130
     * For context managers that are not {@linkplain Clearable} and contain an active {@linkplain Context}
131
     * that is not {@code Clearable} either, this active context will be closed normally.
132
     */
133
    public static void clearActiveContexts() {
134
        final long start = System.nanoTime();
1✔
135
        Long managerStart = null;
1✔
136
        for (ContextManager manager : getContextManagers()) {
1✔
137
            managerStart = System.nanoTime();
1✔
138
            try {
139
                // TODO add ContextObserver.onClear()
140
                if (manager instanceof Clearable) {
1✔
141
                    ((Clearable) manager).clear();
1✔
142
                    if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
143
                        LOGGER.finest("Active context of " + manager + " was cleared.");
×
144
                    }
145
                    Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "clear");
1✔
146
                } else {
147
                    Context activeContext = manager.getActiveContext();
1✔
148
                    if (activeContext != null) {
1✔
149
                        clearContext(manager, activeContext);
1✔
150
                    } else if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
151
                        LOGGER.finest("There is no active context for " + manager + " to be cleared.");
×
152
                    }
153
                }
154
            } catch (RuntimeException rte) {
1✔
155
                LOGGER.log(Level.WARNING, "Exception clearing active context from " + manager + ".", rte);
1✔
156
                Timers.timed(System.nanoTime() - managerStart, manager.getClass(), "clear.exception");
1✔
157
            }
1✔
158
        }
1✔
159
        if (managerStart == null) {
1✔
160
            NoContextManagersFound noContextManagersFound = new NoContextManagersFound();
1✔
161
            LOGGER.log(Level.INFO, noContextManagersFound.getMessage(), noContextManagersFound);
1✔
162
        }
163
        Timers.timed(System.nanoTime() - start, ContextManagers.class, "clearActiveContexts");
1✔
164
    }
1✔
165

166
    /**
167
     * Register an observer for contexts managed by the specified ContextManager type.
168
     *
169
     * @param contextObserver            The observer to register.
170
     * @param observedContextManagerType The context manager type to observe.
171
     * @param <T>                        Type of the value in the context.
172
     * @return {@code true} if the observer was registered.
173
     * @since 1.1.0
174
     */
175
    public static <T> boolean registerContextObserver(ContextObserver<? super T> contextObserver, Class<? extends ContextManager<T>> observedContextManagerType) {
176
        if (contextObserver == null) {
1✔
177
            throw new NullPointerException("Context observer must not be null.");
1✔
178
        } else if (observedContextManagerType == null) {
1✔
179
            throw new NullPointerException("Observed ContextManager type must not be null.");
1✔
180
        }
181

182
        // Find ContextManager to register.
183
        ObservableContextManager observableContextManager = null;
1✔
184
        ContextManager contextManager = null;
1✔
185
        for (ContextManager manager : getContextManagers()) {
1✔
186
            if (manager instanceof ObservableContextManager
1✔
187
                    && ((ObservableContextManager) manager).observes(observedContextManagerType)) {
1✔
188
                observableContextManager = (ObservableContextManager) manager;
1✔
189
                break;
1✔
190
            } else if (observedContextManagerType.isInstance(manager)) {
1✔
191
                contextManager = manager;
1✔
192
                break;
1✔
193
            }
194
        }
1✔
195
        if (observableContextManager == null && contextManager == null) {
1✔
196
            LOGGER.warning("Trying to register observer to missing ContextManager type: " + observedContextManagerType + ".");
1✔
197
            return false;
1✔
198
        }
199

200
        if (observableContextManager == null) {
1✔
201
            // Register new observer by wrapping the context manager.
202
            ObservableContextManager<T> newObserver = new ObservableContextManager<T>(contextManager, (List) Arrays.asList(contextObserver));
1✔
203
            if (OBSERVERS.addIfAbsent(newObserver)) {
1✔
204
                return true;
1✔
205
            }
206

207
            // There is already an existing ObservableContextManager, add the observer to it.
NEW
208
            observableContextManager = OBSERVERS.get(OBSERVERS.indexOf(newObserver));
×
209
        }
210

211
        // Add the context observer to the existing observable context manager.
212
        synchronized (observableContextManager) {
1✔
213
            return observableContextManager.observers.addIfAbsent(contextObserver);
1✔
214
        }
215
    }
216

217
    /**
218
     * Unregister an observer for any context.
219
     *
220
     * @param contextObserver The previously registered context observer.
221
     * @return {@code true} if the observer was unregistered.
222
     * @since 1.1.0
223
     */
224
    public static boolean unregisterContextObserver(ContextObserver<?> contextObserver) {
225
        boolean unregistered = false;
1✔
226
        for (ObservableContextManager observer : OBSERVERS) {
1✔
227
            unregistered |= observer.observers.remove(contextObserver);
1✔
228
            synchronized (observer) {
1✔
229
                if (observer.observers.isEmpty()) {
1✔
230
                    OBSERVERS.remove(observer);
1✔
231
                }
232
            }
1✔
233
        }
1✔
234
        return unregistered;
1✔
235
    }
236

237
    /**
238
     * Override the {@linkplain ClassLoader} used to lookup {@linkplain ContextManager contextmanagers}.
239
     * <p>
240
     * Normally, taking a snapshot uses the {@linkplain Thread#getContextClassLoader() Context ClassLoader} from the
241
     * {@linkplain Thread#currentThread() current thread} to look up all {@linkplain ContextManager context managers}.
242
     * It is possible to configure a fixed, single classloader in your application for looking up the context managers.
243
     * <p>
244
     * Using this method to specify a fixed classloader will only impact
245
     * new {@linkplain ContextSnapshot context snapshots}. Existing snapshots will not be impacted.
246
     * <p>
247
     * <strong>Notes:</strong><br>
248
     * <ul>
249
     * <li>Please be aware that this configuration is global!
250
     * <li>This will also affect the lookup of
251
     * {@linkplain nl.talsmasoftware.context.observer.ContextObserver context observers}
252
     * </ul>
253
     *
254
     * @param classLoader The single, fixed ClassLoader to use for finding context managers.
255
     *                    Specify {@code null} to restore the default behaviour.
256
     * @since 1.0.5
257
     */
258
    public static void useClassLoader(ClassLoader classLoader) {
259
        Level loglevel = PriorityServiceLoader.classLoaderOverride == classLoader ? Level.FINEST : Level.FINE;
1✔
260
        if (LOGGER.isLoggable(loglevel)) {
1✔
NEW
261
            LOGGER.log(loglevel, "Setting override classloader for loading ContextManager and ContextObserver " +
×
262
                    "instances to " + classLoader + " (was: " + PriorityServiceLoader.classLoaderOverride + ").");
263
        }
264
        PriorityServiceLoader.classLoaderOverride = classLoader;
1✔
265
    }
1✔
266

267
    private static Iterable<ContextManager> getContextManagers() {
268
        // TODO change to stream implementation when java 8
269
        return OBSERVERS.isEmpty() ? CONTEXT_MANAGERS : new Iterable<ContextManager>() {
1✔
270
            public Iterator<ContextManager> iterator() {
271
                return new Iterator<ContextManager>() {
1✔
272
                    private final Iterator<ContextManager> delegate = CONTEXT_MANAGERS.iterator();
1✔
273

274
                    public boolean hasNext() {
275
                        return delegate.hasNext();
1✔
276
                    }
277

278
                    public ContextManager next() {
279
                        ContextManager contextManager = delegate.next();
1✔
280
                        if (!(contextManager instanceof ObservableContextManager)) {
1✔
281
                            for (ObservableContextManager observableContextManager : OBSERVERS) {
1✔
282
                                if (observableContextManager.isWrapperOf(contextManager)) {
1✔
283
                                    CONTEXT_MANAGERS.replaceInCache(contextManager, observableContextManager);
1✔
284
                                    return observableContextManager;
1✔
285
                                }
NEW
286
                            }
×
287
                        }
288
                        return contextManager;
1✔
289
                    }
290

291
                    public void remove() {
NEW
292
                        delegate.remove();
×
NEW
293
                    }
×
294
                };
295
            }
296
        };
297
    }
298

299
    private static void clearContext(ContextManager manager, Context context) {
300
        final long start = System.nanoTime();
1✔
301
        final Class<? extends Context> contextType = context.getClass();
1✔
302
        if (context instanceof Clearable) {
1✔
303
            ((Clearable) context).clear();
1✔
304
            if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
305
                LOGGER.finest("Active context of " + manager + " was cleared.");
×
306
            }
307
        } else {
308
            int maxAttempts = 255;
1✔
309
            while (context != null && --maxAttempts > 0) {
1✔
310
                context.close();
1✔
311
                context = manager.getActiveContext();
1✔
312
            }
313
            if (context != null) {
1✔
314
                Logger.getLogger(manager.getClass().getName()).warning(
1✔
315
                        "Possible endless loop prevented clearing the active context for " + manager +
316
                                ". Could it be that this manager returns a non-null context by default " +
317
                                "and has not implemented the Clearable interface?");
318
            }
319
        }
320
        Timers.timed(System.nanoTime() - start, contextType, "clear");
1✔
321
    }
1✔
322

323
    /**
324
     * Implementation of the <code>createContextSnapshot</code> functionality that can reactivate all values from the
325
     * snapshot in each corresponding {@link ContextManager}.
326
     */
327
    private static final class ContextSnapshotImpl implements nl.talsmasoftware.context.api.ContextSnapshot {
328
        private static final ContextManager[] MANAGER_ARRAY = new ContextManager[0];
1✔
329
        private final ContextManager[] managers;
330
        private final Object[] values;
331

332
        private ContextSnapshotImpl(List<ContextManager> managers, List<Object> values) {
1✔
333
            this.managers = managers.toArray(MANAGER_ARRAY);
1✔
334
            this.values = values.toArray();
1✔
335
        }
1✔
336

337
        public Reactivation reactivate() {
338
            final long start = System.nanoTime();
1✔
339
            final List<Context<?>> reactivatedContexts = new ArrayList<Context<?>>(managers.length);
1✔
340
            try {
341
                for (int i = 0; i < managers.length && i < values.length; i++) {
1✔
342
                    reactivatedContexts.add(reactivate(managers[i], values[i]));
1✔
343
                }
344
                ReactivationImpl reactivation = new ReactivationImpl(reactivatedContexts);
1✔
345
                Timers.timed(System.nanoTime() - start, nl.talsmasoftware.context.api.ContextSnapshot.class, "reactivate");
1✔
346
                return reactivation;
1✔
347
            } catch (RuntimeException reactivationException) {
1✔
348
                CONTEXT_MANAGERS.clearCache();
1✔
349
                for (Context alreadyReactivated : reactivatedContexts) {
1✔
350
                    if (alreadyReactivated != null) try {
1✔
351
                        if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
352
                            LOGGER.finest("Snapshot reactivation failed! " +
×
353
                                    "Closing already reactivated context: " + alreadyReactivated + ".");
354
                        }
355
                        alreadyReactivated.close();
1✔
NEW
356
                    } catch (RuntimeException rte) {
×
NEW
357
                        addSuppressedOrWarn(reactivationException, rte, "Could not close already reactivated context.");
×
358
                    }
1✔
359
                }
1✔
360
                throw reactivationException;
1✔
361
            }
362
        }
363

364
        @SuppressWarnings("unchecked") // As we got the values from the managers themselves, they must also accept them!
365
        private Context reactivate(ContextManager contextManager, Object snapshotValue) {
366
            long start = System.nanoTime();
1✔
367
            Context reactivated = contextManager.initializeNewContext(snapshotValue);
1✔
368
            if (LOGGER.isLoggable(Level.FINEST)) {
1✔
NEW
369
                LOGGER.finest("Context reactivated from snapshot by " + contextManager + ": " + reactivated + ".");
×
370
            }
371
            Timers.timed(System.nanoTime() - start, contextManager.getClass(), "initializeNewContext");
1✔
372
            return reactivated;
1✔
373
        }
374

375
        @Override
376
        public String toString() {
377
            return "ContextSnapshot{size=" + managers.length + '}';
1✔
378
        }
379
    }
380

381
    /**
382
     * Implementation of the reactivated 'container' context that closes all reactivated contexts
383
     * when it is closed itself.<br>
384
     * This context contains no meaningful value in itself and purely exists to close the reactivated contexts.
385
     */
386
    private static final class ReactivationImpl implements Reactivation {
387
        private final List<Context<?>> reactivated;
388

389
        private ReactivationImpl(List<Context<?>> reactivated) {
1✔
390
            this.reactivated = reactivated;
1✔
391
        }
1✔
392

393
        public Void getValue() {
394
            return null;
1✔
395
        }
396

397
        public void close() {
398
            RuntimeException closeException = null;
1✔
399
            // close in reverse order of reactivation
400
            for (int i = this.reactivated.size() - 1; i >= 0; i--) {
1✔
401
                Context<?> reactivated = this.reactivated.get(i);
1✔
402
                if (reactivated != null) try {
1✔
403
                    reactivated.close();
1✔
404
                } catch (RuntimeException rte) {
1✔
405
                    if (closeException == null) closeException = rte;
1✔
NEW
406
                    else addSuppressedOrWarn(closeException, rte, "Exception closing the reactivated context.");
×
407
                }
1✔
408
            }
409
            if (closeException != null) throw closeException;
1✔
410
        }
1✔
411

412
        @Override
413
        public String toString() {
414
            return "ReactivatedContext{size=" + reactivated.size() + '}';
1✔
415
        }
416
    }
417

418
    @SuppressWarnings("Since15") // That's why we catch the LinkageError here
419
    private static void addSuppressedOrWarn(Throwable exception, Throwable toSuppress, String message) {
NEW
420
        if (exception != null && toSuppress != null) try {
×
NEW
421
            exception.addSuppressed(toSuppress);
×
NEW
422
        } catch (LinkageError le) {
×
NEW
423
            LOGGER.log(Level.WARNING, message, toSuppress);
×
NEW
424
        }
×
NEW
425
    }
×
426

427
    private static final class ObservableContextManager<T> extends Wrapper<ContextManager<T>> implements ContextManager<T> {
428
        private final CopyOnWriteArrayList<ContextObserver<? super T>> observers;
429

430
        private ObservableContextManager(ContextManager<T> delegate, List<ContextObserver<? super T>> observers) {
431
            super(delegate);
1✔
432
            this.observers = new CopyOnWriteArrayList<ContextObserver<? super T>>(observers);
1✔
433
        }
1✔
434

435
        private boolean observes(Class<? extends ContextManager<?>> contextManagerType) {
436
            return contextManagerType.isInstance(delegate());
1✔
437
        }
438

439
        private T getActiveContextValue() {
440
            final Context<T> activeContext = delegate().getActiveContext();
1✔
441
            return activeContext != null ? activeContext.getValue() : null;
1✔
442
        }
443

444
        private void notifyActivated(T newValue, T oldValue) {
445
            for (ContextObserver<? super T> observer : observers) {
1✔
446
                try {
447
                    observer.onActivate(newValue, oldValue);
1✔
NEW
448
                } catch (RuntimeException observerError) {
×
NEW
449
                    LOGGER.log(Level.SEVERE, "Error in observer.onActivate of " + observer, observerError);
×
450
                }
1✔
451
            }
1✔
452
        }
1✔
453

454
        private void notifyDeactivated(T deactivatedValue, T restoredValue) {
455
            for (ContextObserver<? super T> observer : observers) {
1✔
456
                try {
457
                    observer.onDeactivate(deactivatedValue, restoredValue);
1✔
NEW
458
                } catch (RuntimeException observerError) {
×
NEW
459
                    LOGGER.log(Level.SEVERE, "Error in observer.onActivate of " + observer, observerError);
×
460
                }
1✔
461
            }
1✔
462
        }
1✔
463

464
        public Context<T> initializeNewContext(final T newValue) {
465
            final T oldValue = getActiveContextValue();
1✔
466
            final Context<T> context = delegate().initializeNewContext(newValue);
1✔
467
            notifyActivated(newValue, oldValue);
1✔
468

469
            return new Context<T>() {
1✔
470
                public T getValue() {
NEW
471
                    return context.getValue();
×
472
                }
473

474
                public void close() {
475
                    T deactivated = context.getValue(); // get before closing!
1✔
476
                    context.close();
1✔
477
                    notifyDeactivated(deactivated, getActiveContextValue());
1✔
478
                }
1✔
479
            };
480
        }
481

482
        @Deprecated
483
        public Context<T> getActiveContext() {
484
            return delegate().getActiveContext();
1✔
485
        }
486

487
        @Override
488
        public String toString() {
NEW
489
            return getClass().getSimpleName() + '{' + delegate() + ", " + observers + '}';
×
490
        }
491
    }
492

493
    /**
494
     * Exception that we don't actually throw, but it helps track the issue if we log it including the stacktrace.
495
     */
496
    private static class NoContextManagersFound extends RuntimeException {
497
        private NoContextManagersFound() {
498
            super("Context snapshot was created but no ContextManagers were found!"
1✔
499
                    + " Thread=" + Thread.currentThread()
1✔
500
                    + ", ContextClassLoader=" + Thread.currentThread().getContextClassLoader());
1✔
501
        }
1✔
502
    }
503
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc