All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.logstash.logback.util.ThreadLocalHolder Maven / Gradle / Ivy

Go to download

Provides logback encoders, layouts, and appenders to log in JSON and other formats supported by Jackson

There is a newer version: 8.0
Show newest version
/*
 * Copyright 2013-2022 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
 *
 *      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 net.logstash.logback.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;


/**
 * Maintains a per-thread value created by the {@link Supplier} given to the constructor.
 *
 * 

A thread obtains the value by calling {@link #acquire()} and must release it after * use by calling {@link #release()}. If the value is not released, subsequent calls to * {@link #acquire()} will throw an {@link IllegalStateException}. * *

Instances value may also implement the optional {@link ThreadLocalHolder.Lifecycle} * interface if they wish to be notified when they are recycled or disposed. * *

The holder keeps track of each requesting thread and takes care of disposing the * allocated value when it dies. * * All allocated values are automatically disposed when {@link ThreadLocalHolder#close()} * is called. * *

Note: This class is for internal use only and subject to backward incompatible change * at any time. * * @param type of instances returned by this {@link ThreadLocalHolder}. * * @author brenuart */ public class ThreadLocalHolder { /** * The factory used to create new instances */ private final Supplier factory; /** * ThreadLocal holding per-thread instances */ private final ThreadLocal> threadLocal = ThreadLocal.withInitial(this::initializeThread); /** * Collection of values assigned to each thread */ protected final Map threadValues = new ConcurrentHashMap<>(); /* visible for testing */ /** * Reference to dead threads */ private final ReferenceQueue deadThreads = new ReferenceQueue<>(); /** * {@code true} when the {@link ThreadLocalHolder} is closed. * When closed, values released by threads will be immediately disposed and the reference cleared. */ private volatile boolean closed = false; /** * Create a new instance of the pool. * * @param factory the factory used to create new instances. */ public ThreadLocalHolder(Supplier factory) { this.factory = Objects.requireNonNull(factory); } /** * Get the value assigned to the current thread, creating a new one if none is assigned yet or the * previous has been disposed. * * The value must be {@link #release()} to ensure proper life cycle before it can be {@link #acquire()} * again. * * @return the value assigned to this thread * @throws IllegalStateException if the value is already in use and {@link #release()} was not yet invoked. */ public final T acquire() { Holder holder = this.threadLocal.get(); if (holder.leased) { throw new IllegalStateException("ThreadLocal value is already in use and not yet released."); } if (holder.value == null) { holder.value = Objects.requireNonNull(createInstance()); } holder.leased = true; return holder.value; } /** * Release the value and recycle it if possible. * * @throws IllegalStateException if the value was not previously {@link #acquire()}. */ public final void release() { Holder holder = this.threadLocal.get(); if (!holder.leased) { throw new IllegalStateException("Invalid attempt at releasing a value that was not previously acquired."); } holder.leased = false; /* * Dispose value if it cannot be recycled */ if (this.closed || !safelyRecycleInstance(holder.value)) { disposeHolder(holder); } /* * Dispose values assigned to threads that just died */ processDeadThreads(); } /** * Close the holder and dispose all values. * Threads are still able to {@link #acquire()} values after the holder is closed, but they will be disposed * immediately when {@link #release()} instead of recycled. */ public void close() { /* * Indicate the holder so values released by running threads will be disposed * immediately instead of being recycled. */ this.closed = true; /* * Dispose value assigned to running threads. * "inuse" values will be disposed by the owning thread when it releases it. */ for (HolderRef holderRef: this.threadValues.values()) { Holder holder = holderRef.getHolder(); if (!holder.leased) { disposeHolder(holder); } } this.threadValues.clear(); /* * Dispose values assigned to threads that just died */ processDeadThreads(); } /** * Create a new {@link Holder} and keep track of the asking thread for clearing when the thread * is gone. * * @return a {@link Holder} assigned to the current thread. */ private Holder initializeThread() { final Thread currentThread = Thread.currentThread(); final long threadId = currentThread.getId(); /* Since java 16+ we can't guarantee that `initializeThread` will be called only once per thread as pools * like the `ForkJoinPool.commonPool()` will create innocuous workers which clean their thread locals before * executing tasks submitted to it. * * Because of it, we changed the strategy of `threadValues` to become also a fallback for the values that * we expect to be associated to the Thread, i.e., it relates the value to the thread id to what we expect * to be in the `ThreadLocal` throughout its life. * * See https://github.com/logfellow/logstash-logback-encoder/issues/722#issuecomment-1107836944 for more * information. */ return threadValues.computeIfAbsent( threadId, ignore -> new HolderRef(currentThread, new Holder<>(), deadThreads)) .holder; } /** * Dispose values of dead threads */ @SuppressWarnings("unchecked") private void processDeadThreads() { // Note: // ReferenceQueue#poll is thread safe and doesn't block when empty. HolderRef ref = (HolderRef) deadThreads.poll(); while (ref != null) { Holder holder = ref.getHolder(); disposeHolder(holder); threadValues.remove(ref.getThreadId()); ref = (HolderRef) deadThreads.poll(); } } private void disposeHolder(Holder holder) { safelyDisposeInstance(holder.value); holder.value = null; } /** * Create a new object instance (must be non-null). * Sub-classes may override this method to implement their own custom logic if needed. * * @return a new object instance */ protected T createInstance() { return this.factory.get(); } /** * Dispose the object instance by calling its life cycle methods. * Sub-classes may override this method if they wish to implement their own custom logic. * * @param instance the instance to dispose */ protected void disposeInstance(T instance) { if (instance instanceof Lifecycle) { ((Lifecycle) instance).dispose(); } } /** * Safely dispose the given instance, ignoring any exception that may be thrown. * * @param instance the instance to dispose */ private void safelyDisposeInstance(T instance) { try { disposeInstance(instance); } catch (Exception e) { // ignore } } /** * Recycle the instance before returning it to the pool. * Sub-classes may override this method if they wish to implement their own custom logic. * * @param instance the instance to recycle * @return {@code true} if the instance can be recycled and returned to the pool, {@code false} if not. */ protected boolean recycleInstance(T instance) { if (instance instanceof Lifecycle) { return ((Lifecycle) instance).recycle(); } else { return true; } } /** * Safely call {@link ThreadLocalHolder#recycleInstance(Object)}, ignoring exceptions but returning * {@code false} to prevent reuse if any is thrown. * * @param instance the instance to recycle * @return {@code true} if the instance can be recycled, {@code false} otherwise. */ private boolean safelyRecycleInstance(T instance) { try { return recycleInstance(instance); } catch (Exception e) { return false; } } /** * Optional interface that pooled instances may implement if they wish to be notified of * life cycle events. */ public interface Lifecycle { /** * Indicate whether the instance can be recycled and returned to the pool and perform * the necessary recycling tasks. * * @return {@code true} if the instance can be returned to the pool, {@code false} if * it must be disposed instead. */ default boolean recycle() { return true; } /** * Dispose the instance and free allocated resources. */ default void dispose() { // noop } } /** * Holds the value assigned to a thread together with its "inuse" state. * * This class is static as to not have a reference to the outer {@link ThreadLocalHolder} * and prevent it from being garbage collected. */ private static class Holder { /** * Reference to the thread local instance. * May be null if none is already assigned or when the previous is disposed. */ private T value; /** * Indicate whether the instance is in use (acquired). * Maintaining this flag helps to avoid recreating a new SoftReference every time * the instance is released. */ private boolean leased; } /** * A {@link WeakReference} to a thread with the {@link Holder} assigned to it. * Used to detect the death of a thread and dispose the associated value. */ /* visible for testing */ protected class HolderRef extends WeakReference { private final Holder holder; private final long threadId; HolderRef(Thread owningThread, Holder holder, ReferenceQueue referenceQueue) { super(owningThread, referenceQueue); this.threadId = owningThread.getId(); this.holder = holder; } public Holder getHolder() { return this.holder; } public long getThreadId() { return this.threadId; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy