001package ca.uhn.fhir.jpa.subscription.channel.impl;
002
003/*-
004 * #%L
005 * HAPI FHIR Storage api
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.jpa.subscription.channel.api.ChannelConsumerSettings;
025import ca.uhn.fhir.jpa.subscription.channel.api.ChannelProducerSettings;
026import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
027import ca.uhn.fhir.jpa.subscription.channel.api.IChannelProducer;
028import ca.uhn.fhir.jpa.subscription.channel.api.IChannelReceiver;
029import ca.uhn.fhir.jpa.subscription.channel.api.IChannelSettings;
030import ca.uhn.fhir.jpa.subscription.channel.subscription.IChannelNamer;
031import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionConstants;
032import ca.uhn.fhir.util.StopWatch;
033import org.apache.commons.lang3.concurrent.BasicThreadFactory;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import javax.annotation.PreDestroy;
038import java.util.Collections;
039import java.util.HashMap;
040import java.util.Map;
041import java.util.concurrent.LinkedBlockingQueue;
042import java.util.concurrent.RejectedExecutionException;
043import java.util.concurrent.RejectedExecutionHandler;
044import java.util.concurrent.ThreadFactory;
045import java.util.concurrent.ThreadPoolExecutor;
046import java.util.concurrent.TimeUnit;
047
048public class LinkedBlockingChannelFactory implements IChannelFactory {
049
050        private static final Logger ourLog = LoggerFactory.getLogger(LinkedBlockingChannelFactory.class);
051        private final IChannelNamer myChannelNamer;
052        private final Map<String, LinkedBlockingChannel> myChannels = Collections.synchronizedMap(new HashMap<>());
053
054        public LinkedBlockingChannelFactory(IChannelNamer theChannelNamer) {
055                myChannelNamer = theChannelNamer;
056        }
057
058        @Override
059        public IChannelReceiver getOrCreateReceiver(String theChannelName, Class<?> theMessageType, ChannelConsumerSettings theChannelSettings) {
060                return getOrCreateChannel(theChannelName, theChannelSettings.getConcurrentConsumers(), theChannelSettings);
061        }
062
063        @Override
064        public IChannelProducer getOrCreateProducer(String theChannelName, Class<?> theMessageType, ChannelProducerSettings theChannelSettings) {
065                return getOrCreateChannel(theChannelName, theChannelSettings.getConcurrentConsumers(), theChannelSettings);
066        }
067
068        @Override
069        public IChannelNamer getChannelNamer() {
070                return myChannelNamer;
071        }
072
073        private LinkedBlockingChannel getOrCreateChannel(String theChannelName,
074                                                                                                                                         int theConcurrentConsumers,
075                                                                                                                                         IChannelSettings theChannelSettings) {
076                // TODO - does this need retry settings?
077                final String channelName = myChannelNamer.getChannelName(theChannelName, theChannelSettings);
078
079                return myChannels.computeIfAbsent(channelName, t -> {
080
081                        String threadNamingPattern = channelName + "-%d";
082
083                        ThreadFactory threadFactory = new BasicThreadFactory.Builder()
084                                .namingPattern(threadNamingPattern)
085                                .daemon(false)
086                                .priority(Thread.NORM_PRIORITY)
087                                .build();
088
089                        LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(SubscriptionConstants.DELIVERY_EXECUTOR_QUEUE_SIZE);
090                        RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> {
091                                ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", queue.size());
092                                StopWatch sw = new StopWatch();
093                                try {
094                                        queue.put(theRunnable);
095                                } catch (InterruptedException e) {
096                                        Thread.currentThread().interrupt();
097                                        throw new RejectedExecutionException(Msg.code(568) + "Task " + theRunnable.toString() +
098                                                " rejected from " + e.toString());
099                                }
100                                ourLog.info("Slot become available after {}ms", sw.getMillis());
101                        };
102                        ThreadPoolExecutor executor = new ThreadPoolExecutor(
103                                1,
104                                theConcurrentConsumers,
105                                0L,
106                                TimeUnit.MILLISECONDS,
107                                queue,
108                                threadFactory,
109                                rejectedExecutionHandler);
110                        return new LinkedBlockingChannel(channelName, executor, queue);
111
112                });
113        }
114
115
116        @PreDestroy
117        public void stop() {
118                myChannels.clear();
119        }
120
121}