GSIP 96 - Machinery to pass thread locals down in thread pools

Overview

Allow code running in thread pools to have the same context variables as the one running in the main request threads (e.g., authentication information)

Proposed By

Andrea Aime

Assigned to Release

2.3.3

State

Choose one of: Under Discussion, In Progress, Completed, Rejected, Deferred

Motivation

The main motivator for this proposal is GEOS-5370, WPS inability to run against secured data, which is happening because all WPS requests are running inside thread pools, as opposed to be running on the main request thread.
This however brings to light a broader set of issues, as we have a number of thread locals being used in GeoServer code that would not be available inside thread pools, like request workspace/layer context, 'env' function context, the very own Dispatcher.REQUEST that some low level code uses to reach directly to the originating raw request.

Proposal

Thread locals need to be copied to some temporary storage, which is then sent down along with the Runnable/Callable in the thread pool, and then restored once the thread starts processing the Runnable/Callable.

Thread locals might be public static fields, can be reachable via static methods, but also be local and only allow package private access. Given the variety, a new extension point to collect thread locals ìs proposed:

/**
 * Implementors of this interface allow to transfer thread locals from the current thread to another
 * one. They are used to make sure important thread local values are transferred into thread
 * pools/scheduled tasks/delayed actions executing portions of the GeoServer work
 * 
 * @author Andrea Aime - GeoSolutions
 * 
 */
public interface ThreadLocalTransfer {

    /**
     * Collects the current thread locals values into the storage. The implementation must try hard
     * to use a unique name for each key (a good choice is
     * fullyQualifiedClassName#thredLocalFieldName), the caller utility will make sure there are no
     * conflicts (in case of conflict an exception will be thrown)
     */
    void collect(Map<String, Object> storage);

    /**
     * Set the thread local values in the current thread
     */
    void apply(Map<String, Object> storage);

    /**
     * Clean up the thread locals in the current thread
     */
    void cleanup();
}

Objects implementing the interface will then be registered in the Spring context, and used via a utility class can then be coded and used to transfer the thread locals:

/**
 * Collects all {@link ThreadLocalTransfer} found in the application context and applies them on
 * request. This object is not thread safe, it's meant to be instantiated when a transfer is
 * required To use it:
 * <ul>
 * <li>Create a {@link ThreadLocalsTransfer}</li>
 * <li>Call {@link #collect()}</li>
 * <li>Pass the object as state to the {@link Runnable}/{@link Callable} being run in a thread pool</li>
 * <li>As the {@link Runnable}/{@link Callable} starts its activity, invoke {@link #apply()}</li>
 * <li>In a finally block wrapping the whole activity of the {@link Runnable}/{@link Callable} call
 * {@link #cleanup()} to clean the current Thread {@link ThreadLocal} variables</li>
 * </ul>
 * 
 * @author Andrea Aime - GeoSolutions
 * 
 */
public class ThreadLocalsTransfer {

    Map<String, Object> storage = new HashMap<String, Object>();

    private List<ThreadLocalTransfer> transfers;

    public ThreadLocalsTransfer() {
        transfers = GeoServerExtensions.extensions(ThreadLocalTransfer.class);
    }

    /**
     * Runs all registered {@link ThreadLocalsTransfer} objects and collects the associated thread
     * locals in the storage object
     */
    void collect() {
        for (ThreadLocalTransfer transfer : transfers) {
            transfer.collect(storage);
        }
    }

    /**
     * Set the thread local values in the current thread
     */
    void apply() {
        for (ThreadLocalTransfer transfer : transfers) {
            transfer.apply(storage);
        }
    }

    /**
     * Clean up the thread locals in the current thread
     */
    void cleanup() {
        for (ThreadLocalTransfer transfer : transfers) {
            transfer.cleanup();
        }
    }
}

Feedback

This section should contain feedback provided by PSC members who may have a problem with the proposal.

Backwards Compatibility

No backwards compatibility issue, the new interface is purely additive.

Voting

Alessio Fabiani: +0
Andrea Aime: +1
Ben Caradoc-Davies: +1
Christian Mueller:
Gabriel Roldán:
Jody Garnett:
Jukka Rahkonen:
Justin Deoliveira: +1
Phil Scadden: +0
Simone Giannecchini:

Links

JIRA Task
Email Discussion

Added by Andrea Aime, last edited by Andrea Aime on Nov 30, 2013  (view change)
View Attachments (0) Info