<commit_before>/*
Copyright (c) 2012, <NAME>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of Adam Retter Consulting nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <NAME> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.exist.extensions.exquery.restxq.impl;

import java.net.URI;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.exist.extensions.exquery.restxq.RestXqServiceCompiledXQueryCache;
import org.exist.storage.DBBroker;
import org.exist.xquery.CompiledXQuery;
import org.exquery.restxq.RestXqService;
import org.exquery.restxq.RestXqServiceException;

/**
 *
 * @author <NAME> <<EMAIL>>
 */
public class RestXqServiceCompiledXQueryCacheImpl implements RestXqServiceCompiledXQueryCache {
    
    private final static RestXqServiceCompiledXQueryCacheImpl instance = new RestXqServiceCompiledXQueryCacheImpl();
    
    //TODO could introduce a MAX stack size, i.e. you can only have N compiled main.xqy's in the cache
    private final Map<URI, Deque<CompiledXQuery>> cache = new HashMap<>();
    private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
    
    public static RestXqServiceCompiledXQueryCacheImpl getInstance() {
        return instance;
    }

    @Override
    public CompiledXQuery getCompiledQuery(final DBBroker broker, final URI xqueryLocation) throws RestXqServiceException {
        
        CompiledXQuery xquery = null;
        cacheLock.writeLock().lock();
        try {
            final Deque<CompiledXQuery> queries = cache.get(xqueryLocation);
            
            if(queries != null && !queries.isEmpty()) {
                xquery = queries.pop();
            }
        } finally {
            cacheLock.writeLock().unlock();
        }
        
        if(xquery == null) {
            xquery = XQueryCompiler.compile(broker, xqueryLocation);
        }
        
        //reset the state of the query
        xquery.reset();
        xquery.getContext().getWatchDog().reset();
        xquery.getContext().prepareForExecution();
        
        return xquery;
    }
    
    @Override
    public void returnCompiledQuery(final URI xqueryLocation, final CompiledXQuery xquery) {
        cacheLock.writeLock().lock();
        try {
            Deque<CompiledXQuery> queries = cache.get(xqueryLocation);
            if(queries == null) {
                queries = new ArrayDeque<>();
            }
            
            //reset the query and context
            xquery.reset();
            xquery.getContext().reset();
            
            queries.push(xquery);
            
            cache.put(xqueryLocation, queries);
            
        } finally {
            cacheLock.writeLock().unlock();
        }
    }
    
    @Override
    public void removeService(final RestXqService service) {
        cacheLock.writeLock().lock();
        try {        
            cache.remove(service.getResourceFunction().getXQueryLocation());
        } finally {
            cacheLock.writeLock().unlock();
        }
    }
    
    @Override
    public void removeServices(final Iterable<RestXqService> services) {
        cacheLock.writeLock().lock();
        try {
            for(final RestXqService service : services) {
                cache.remove(service.getResourceFunction().getXQueryLocation());
            }
        } finally {
            cacheLock.writeLock().unlock();
        }
    }
}<commit_msg>[feature] Performance improvement. RESTXQ Compiled XQuery Cache is now multi-threaded. Concurrency is now per-query URI.<commit_after>/*
Copyright (c) 2012, <NAME>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of <NAME>ter Consulting nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <NAME> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.exist.extensions.exquery.restxq.impl;

import java.net.URI;
import java.util.Iterator;
import java.util.Queue;
import java.util.function.Function;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.exist.extensions.exquery.restxq.RestXqServiceCompiledXQueryCache;
import org.exist.storage.DBBroker;
import org.exist.xquery.CompiledXQuery;
import org.exquery.restxq.RestXqService;
import org.exquery.restxq.RestXqServiceException;
import org.jctools.queues.atomic.MpmcAtomicArrayQueue;

/**
 * Compiled XQuery Cache for RESTXQ Resource Functions.
 *
 * @author <a href="mailto:<EMAIL>"><NAME></a>
 */
public class RestXqServiceCompiledXQueryCacheImpl implements RestXqServiceCompiledXQueryCache {
    
    private static final RestXqServiceCompiledXQueryCacheImpl INSTANCE = new RestXqServiceCompiledXQueryCacheImpl();

    // TODO(AR) make configurable?
    private static final int DEFAULT_MAX_POOL_SIZE = 256;
    private static final int DEFAULT_MAX_QUERY_STACK_SIZE = 64;

    private final Cache<URI, Queue<CompiledXQuery>> cache;

    private RestXqServiceCompiledXQueryCacheImpl() {
        this.cache = Caffeine.newBuilder()
                .maximumSize(DEFAULT_MAX_POOL_SIZE)
                .build();
    }

    public static RestXqServiceCompiledXQueryCacheImpl getInstance() {
        return INSTANCE;
    }

    @Override
    public CompiledXQuery getCompiledQuery(final DBBroker broker, final URI xqueryLocation)
            throws RestXqServiceException {
        final Queue<CompiledXQuery> queue =
                cache.get(xqueryLocation,
                        key -> new MpmcAtomicArrayQueue<>(DEFAULT_MAX_QUERY_STACK_SIZE));

        CompiledXQuery xquery = queue.poll();
        if(xquery == null) {
            xquery = XQueryCompiler.compile(broker, xqueryLocation);
        }

        //reset the state of the query
        xquery.reset();
        xquery.getContext().getWatchDog().reset();
        xquery.getContext().prepareForExecution();

        return xquery;
    }
    
    @Override
    public void returnCompiledQuery(final URI xqueryLocation, final CompiledXQuery xquery) {
        //reset the query and context
        xquery.reset();
        xquery.getContext().reset();

        // place in the cache
        final Queue<CompiledXQuery> queue =
                cache.get(xqueryLocation,
                        key -> new MpmcAtomicArrayQueue<>(DEFAULT_MAX_QUERY_STACK_SIZE));
        queue.offer(xquery);
    }
    
    @Override
    public void removeService(final RestXqService service) {
        final URI xqueryLocation = service.getResourceFunction().getXQueryLocation();
        cache.invalidate(xqueryLocation);
    }
    
    @Override
    public void removeServices(final Iterable<RestXqService> services) {
        cache.invalidateAll(mapIterable(services,
                restXqService -> restXqService.getResourceFunction().getXQueryLocation()));
    }

    /**
     * Utility function that given an Iterable<T> and a function of T -> U, returns an Iterable<U>.
     *
     * @param <T> The input type.
     * @param <U> The mapped result type.
     *
     * @param input The input iterable.
     * @param mapper The mapping function.
     *
     * @return The mapped iterable.
     */
    private static <T, U> Iterable<U> mapIterable(final Iterable<T> input, final Function<T, U> mapper) {
        return new Iterable<U>() {
            @Override
            public Iterator<U> iterator() {
                final Iterator<T> it = input.iterator();
                return new Iterator<U>() {
                    @Override
                    public boolean hasNext() {
                        return it.hasNext();
                    }

                    @Override
                    public U next() {
                        return mapper.apply(it.next());
                    }
                };
            }
        };
    }
}
{'fraction_non_alphanumeric': 0.07669332305617112, 'fraction_numerical': 0.0012525291453897292, 'mean_word_length': 2.7883211678832116, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1122816', 'n_tokens_mistral': 2825, 'n_tokens_neox': 2685, 'n_words': 866}