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}