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

talsma-ict / context-propagation / #1585

15 Nov 2024 01:04PM CUT coverage: 94.267% (-0.7%) from 94.919%
#1585

push

web-flow
Merge 7b17dead7 into d8f77d505

5 of 10 new or added lines in 2 files covered. (50.0%)

1 existing line in 1 file now uncovered.

855 of 907 relevant lines covered (94.27%)

0.94 hits per line

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

87.91
/context-propagation-core/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.api.Context;
19
import nl.talsmasoftware.context.api.ContextManager;
20
import nl.talsmasoftware.context.api.ContextSnapshot;
21
import nl.talsmasoftware.context.api.ContextSnapshot.Reactivation;
22

23
import java.util.List;
24
import java.util.logging.Level;
25
import java.util.logging.Logger;
26

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

41
    /**
42
     * Private constructor to avoid instantiation of this class.
43
     */
44
    private ContextManagers() {
1✔
45
        throw new UnsupportedOperationException();
1✔
46
    }
47

48
    /**
49
     * This method is able to create a 'snapshot' from the current
50
     * {@link ContextManager#getActiveContextValue() active context value}
51
     * from <em>all known {@link ContextManager}</em> implementations.
52
     *
53
     * <p>
54
     * This snapshot is returned as a single object that can be temporarily
55
     * {@link ContextSnapshot#reactivate() reactivated}. Don't forget to {@link Context#close() close} the reactivated
56
     * context once you're done, preferably in a <code>try-with-resources</code> construct.
57
     *
58
     * @return A new snapshot that can be reactivated elsewhere (e.g. a background thread)
59
     * within a try-with-resources construct.
60
     */
61
    @SuppressWarnings("rawtypes")
62
    public static ContextSnapshot createContextSnapshot() {
63
        final long start = System.nanoTime();
1✔
64
        final List<ContextManager> managers = ServiceCache.cached(ContextManager.class); // Cached list is immutable
1✔
65
        final Object[] values = new Object[managers.size()];
1✔
66

67
        for (int i = 0; i < values.length; i++) {
1✔
68
            values[i] = getActiveContextValue(managers.get(i));
1✔
69
        }
70

71
        final ContextSnapshotImpl result = new ContextSnapshotImpl(managers, values);
1✔
72
        if (managers.isEmpty() && LOGGER.isLoggable(Level.FINER)) {
1✔
73
            LOGGER.finer(result + " was created but no ContextManagers were found! "
×
74
                    + " Thead=" + Thread.currentThread()
×
75
                    + ", ContextClassLoader=" + Thread.currentThread().getContextClassLoader());
×
76
        }
77
        Timers.timed(System.nanoTime() - start, ContextManagers.class, "createContextSnapshot", null);
1✔
78
        return result;
1✔
79
    }
80

81
    /**
82
     * Clears all active contexts from the current thread.
83
     *
84
     * <p>
85
     * Contexts that are 'stacked' (i.e. restore the previous state upon close) should be
86
     * closed in a way that includes all 'parent' contexts as well.
87
     *
88
     * <p>
89
     * This operation is not intended to be used by general application code as it likely breaks any 'stacked'
90
     * active context that surrounding code may depend upon.
91
     * Appropriate use includes thread management, where threads are reused by some pooling
92
     * mechanism. For example, it is considered safe to clear the context when obtaining a 'fresh' thread from a
93
     * thread pool (as no context expectations should exist at that point).
94
     * An even better strategy would be to clear the context right before returning a used thread to the pool
95
     * as this will allow any unclosed contexts to be garbage collected. Besides preventing contextual issues,
96
     * this reduces the risk of memory leaks by unbalanced context calls.
97
     */
98
    public static void clearActiveContexts() {
99
        final long start = System.nanoTime();
1✔
100
        for (ContextManager<?> manager : ServiceCache.cached(ContextManager.class)) {
1✔
101
            clear(manager);
1✔
102
        }
1✔
103
        Timers.timed(System.nanoTime() - start, ContextManagers.class, "clearActiveContexts", null);
1✔
104
    }
1✔
105

106
    /**
107
     * Override the {@linkplain ClassLoader} used to lookup {@linkplain ContextManager contextmanagers}.
108
     * <p>
109
     * Normally, taking a snapshot uses the {@linkplain Thread#getContextClassLoader() Context ClassLoader} from the
110
     * {@linkplain Thread#currentThread() current thread} to look up all {@linkplain ContextManager context managers}.
111
     * It is possible to configure a fixed, single classloader in your application for looking up the context managers.
112
     * <p>
113
     * Using this method to specify a fixed classloader will only impact
114
     * new {@linkplain ContextSnapshot context snapshots}. Existing snapshots will not be impacted.
115
     * <p>
116
     * <strong>Notes:</strong><br>
117
     * <ul>
118
     * <li>Please be aware that this configuration is global!
119
     * <li>This will also affect the lookup of
120
     * {@linkplain nl.talsmasoftware.context.api.ContextTimer context timers}
121
     * </ul>
122
     *
123
     * @param classLoader The single, fixed ClassLoader to use for finding context managers.
124
     *                    Specify {@code null} to restore the default behaviour.
125
     * @since 1.0.5
126
     */
127
    public static synchronized void useClassLoader(ClassLoader classLoader) {
128
        ServiceCache.useClassLoader(classLoader);
1✔
129
    }
1✔
130

131
    private static Object getActiveContextValue(ContextManager<?> manager) {
132
        final long start = System.nanoTime();
1✔
133
        RuntimeException error = null;
1✔
134
        try {
135

136
            final Object activeContextValue = manager.getActiveContextValue();
1✔
137
            LOGGER.finest(() -> activeContextValue == null
1✔
138
                    ? "There is no active context value for " + manager + " (in thread " + Thread.currentThread().getName() + ")."
×
139
                    : "Active context value of " + manager + " in " + Thread.currentThread().getName() + ": " + activeContextValue);
×
140
            return activeContextValue;
1✔
141

142
        } catch (RuntimeException e) {
1✔
143
            LOGGER.log(Level.WARNING, e, () -> "Error obtaining active context from " + manager + " (in thread " + Thread.currentThread().getName() + ").");
1✔
144
            error = e;
1✔
145
            return null;
1✔
146
        } finally {
147
            Timers.timed(System.nanoTime() - start, manager.getClass(), "getActiveContextValue", error);
1✔
148
        }
149
    }
150

151
    private static void clear(ContextManager<?> manager) {
152
        final long start = System.nanoTime();
1✔
153
        RuntimeException error = null;
1✔
154
        try {
155

156
            manager.clear();
1✔
157
            LOGGER.finest(() -> "Active context of " + manager + " was cleared.");
1✔
158

159
        } catch (RuntimeException e) {
×
160
            LOGGER.log(Level.WARNING, e, () -> "Error clearing active context from " + manager + "(in thread " + Thread.currentThread().getName() + ").");
×
161
            error = e;
×
162
        } finally {
163
            Timers.timed(System.nanoTime() - start, manager.getClass(), "clear", error);
1✔
164
        }
165
    }
1✔
166

167
    /**
168
     * Implementation of the <code>createContextSnapshot</code> functionality that can reactivate all values from the
169
     * snapshot in each corresponding {@link ContextManager}.
170
     */
171
    @SuppressWarnings("rawtypes")
172
    private static final class ContextSnapshotImpl implements ContextSnapshot {
173
        private final List<ContextManager> managers;
174
        private final Object[] values;
175

176
        private ContextSnapshotImpl(List<ContextManager> managers, Object[] values) {
1✔
177
            this.managers = managers;
1✔
178
            this.values = values;
1✔
179
        }
1✔
180

181
        public Reactivation reactivate() {
182
            final long start = System.nanoTime();
1✔
183
            RuntimeException error = null;
1✔
184
            final Context[] reactivatedContexts = new Context[managers.size()];
1✔
185
            try {
186

187
                for (int i = 0; i < values.length; i++) {
1✔
188
                    reactivatedContexts[i] = reactivate(managers.get(i), values[i]);
1✔
189
                }
190
                return new ReactivationImpl(reactivatedContexts);
1✔
191

192
            } catch (RuntimeException reactivationException) {
1✔
193
                tryClose(reactivatedContexts, reactivationException);
1✔
194
                ServiceCache.clear();
1✔
195
                throw error = reactivationException;
1✔
196
            } finally {
197
                Timers.timed(System.nanoTime() - start, ContextSnapshot.class, "reactivate", error);
1✔
198
            }
199
        }
200

201
        @Override
202
        public String toString() {
203
            return "ContextSnapshot{size=" + managers.size() + '}';
1✔
204
        }
205

206
        /**
207
         * Reactivates a snapshot value for a single context manager.
208
         *
209
         * <p>
210
         * This initializes a new context with the context manager
211
         * (normally on another thread the snapshot value was captured from).
212
         *
213
         * @param contextManager The context manager to reactivate the snapshot value for.
214
         * @param snapshotValue  The snapshot value to be reactivated.
215
         * @return The context to be included in the reactivation object.
216
         */
217
        @SuppressWarnings("unchecked") // We got the value from the managers itself.
218
        private static Context reactivate(ContextManager contextManager, Object snapshotValue) {
219
            if (snapshotValue == null) return null;
1✔
220
            long start = System.nanoTime();
1✔
221
            RuntimeException error = null;
1✔
222
            try {
223

224
                Context reactivated = contextManager.initializeNewContext(snapshotValue);
1✔
225
                LOGGER.finest(() -> "Context reactivated from snapshot by " + contextManager + ": " + reactivated + ".");
1✔
226
                return reactivated;
1✔
227

228
            } catch (RuntimeException e) {
1✔
229
                throw error = e;
1✔
230
            } finally {
231
                Timers.timed(System.nanoTime() - start, contextManager.getClass(), "initializeNewContext", error);
1✔
232
            }
233
        }
234

235
        /**
236
         * Try to close already-reactivated contexts when a later context manager threw an exception.
237
         *
238
         * @param reactivatedContexts The contexts that were already reactivated when the error happened.
239
         * @param reason              The error that happened.
240
         */
241
        private static void tryClose(Context[] reactivatedContexts, Throwable reason) {
242
            for (Context alreadyReactivated : reactivatedContexts) {
1✔
243
                if (alreadyReactivated != null) {
1✔
244
                    try {
245
                        alreadyReactivated.close();
1✔
246
                    } catch (RuntimeException rte) {
×
247
                        reason.addSuppressed(rte);
×
248
                    }
1✔
249
                }
250
            }
251
        }
1✔
252
    }
253

254
    /**
255
     * Implementation of the reactivated 'container' context that closes all reactivated contexts
256
     * when it is closed itself.<br>
257
     * This context contains no meaningful value in itself and purely exists to close the reactivated contexts.
258
     */
259
    @SuppressWarnings("rawtypes")
260
    private static final class ReactivationImpl implements Reactivation {
261
        private final Context[] reactivated;
262

263
        private ReactivationImpl(Context[] reactivated) {
1✔
264
            this.reactivated = reactivated;
1✔
265
        }
1✔
266

267
        public void close() {
268
            RuntimeException closeException = null;
1✔
269
            // close in reverse order of reactivation
270
            for (int i = this.reactivated.length - 1; i >= 0; i--) {
1✔
271
                Context<?> reactivated = this.reactivated[i];
1✔
272
                if (reactivated != null) try {
1✔
273
                    reactivated.close();
1✔
274
                } catch (RuntimeException rte) {
1✔
275
                    if (closeException == null) closeException = rte;
1✔
276
                    else closeException.addSuppressed(rte);
×
277
                }
1✔
278
            }
279
            if (closeException != null) throw closeException;
1✔
280
        }
1✔
281

282
        @Override
283
        public String toString() {
284
            return "ContextSnapshot.Reactivation{size=" + reactivated.length + '}';
1✔
285
        }
286
    }
287
}
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