From 632550220dda04f626822bea4cabd2d55b3a6588 Mon Sep 17 00:00:00 2001 From: curry772 Date: Thu, 23 Apr 2026 15:25:58 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Reload=20=EB=8F=99=EC=8B=9C=EC=84=B1=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20-=20volatile=20ConcurrentHashMap=20+=20?= =?UTF-8?q?=EC=9B=90=EC=9E=90=EC=A0=81=20=EB=A7=B5=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 맵 5개를 volatile + ConcurrentHashMap으로 변경 - 비동기 읽기(isGroupPass 등) 중 HashMap 구조 변경으로 발생하던 race condition 제거 2. initAdapter/Interface/Group()에서 빌드-완성 후 원자적 교체 패턴 적용 - 신규 맵을 완전히 구성한 뒤 this.field = newMap으로 한 번에 교체 - 구→신 전환 중 불완전 상태가 외부에 노출되던 문제 제거 3. removeAdapter/Interface/Group()에 synchronized 추가 - 락 없이 맵을 수정하던 메서드와 reload 사이의 race condition 방지 Co-Authored-By: Claude Sonnet 4.6 --- .../common/inflow/InflowControlManager.java | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/eactive/eai/common/inflow/InflowControlManager.java b/src/main/java/com/eactive/eai/common/inflow/InflowControlManager.java index b44d5ac..1992993 100644 --- a/src/main/java/com/eactive/eai/common/inflow/InflowControlManager.java +++ b/src/main/java/com/eactive/eai/common/inflow/InflowControlManager.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -39,11 +40,11 @@ public class InflowControlManager implements Lifecycle, Bucket { public static final String QUOTA_TIMEUNIT_DAY = "DAY"; public static final String QUOTA_TIMEUNIT_MONTH = "MON"; - private Map adapterBucketList = new HashMap<>(); - private Map interfaceBucketList = new HashMap<>(); - private Map groupBucketList = new HashMap<>(); - private Map interfaceToGroupMap = new HashMap<>(); - private Map groupVoMap = new HashMap<>(); + private volatile Map adapterBucketList = new ConcurrentHashMap<>(); + private volatile Map interfaceBucketList = new ConcurrentHashMap<>(); + private volatile Map groupBucketList = new ConcurrentHashMap<>(); + private volatile Map interfaceToGroupMap = new ConcurrentHashMap<>(); + private volatile Map groupVoMap = new ConcurrentHashMap<>(); private boolean started; private LifecycleSupport lifecycle = new LifecycleSupport(this); @@ -97,37 +98,39 @@ public class InflowControlManager implements Lifecycle, Bucket { } private void initAdapter() throws Exception { - adapterBucketList = new HashMap<>(); + Map newMap = new HashMap<>(); List inflowAdapterVoList = inflowControlDAO.getInflowAdapterList(); for (InflowTargetVO inflowVo : inflowAdapterVoList) { if (InflowControlManager.isInflowTarget(inflowVo)) { CustomBucket bucket = makeBucketUsingInflowVO(inflowVo); if (bucket != null) { - adapterBucketList.put(inflowVo.getName(), bucket); + newMap.put(inflowVo.getName(), bucket); } } } + this.adapterBucketList = newMap; } private void initInterface() throws Exception { - interfaceBucketList = new HashMap<>(); + Map newMap = new HashMap<>(); List inflowInterfaceVoList = inflowControlDAO.getInflowInterfaceList(); for (InflowTargetVO inflowVo : inflowInterfaceVoList) { if (InflowControlManager.isInflowTarget(inflowVo)) { CustomBucket bucket = makeBucketUsingInflowVO(inflowVo); if (bucket != null) { - interfaceBucketList.put(inflowVo.getName(), bucket); + newMap.put(inflowVo.getName(), bucket); } } } + this.interfaceBucketList = newMap; } private void initGroup() throws Exception { - groupBucketList = new HashMap<>(); - interfaceToGroupMap = new HashMap<>(); - groupVoMap = new HashMap<>(); + Map newGroupBucketList = new HashMap<>(); + Map newInterfaceToGroupMap = new HashMap<>(); + Map newGroupVoMap = new HashMap<>(); List inflowGroupVoList = inflowControlDAO.getInflowGroupList(); @@ -135,20 +138,24 @@ public class InflowControlManager implements Lifecycle, Bucket { if (isInflowGroupTarget(groupVo)) { CustomGroupBucket bucket = makeGroupBucket(groupVo); if (bucket != null) { - groupBucketList.put(groupVo.getGroupId(), bucket); - groupVoMap.put(groupVo.getGroupId(), groupVo); + newGroupBucketList.put(groupVo.getGroupId(), bucket); + newGroupVoMap.put(groupVo.getGroupId(), groupVo); // 인터페이스 → 그룹 매핑 등록 for (String interfaceId : groupVo.getInterfaceList()) { - interfaceToGroupMap.put(interfaceId, groupVo.getGroupId()); + newInterfaceToGroupMap.put(interfaceId, groupVo.getGroupId()); } } } } if (logger.isInfo()) { - logger.info("InflowControlManager] initGroup completed. groups=" + groupBucketList.size() - + ", mappings=" + interfaceToGroupMap.size()); + logger.info("InflowControlManager] initGroup completed. groups=" + newGroupBucketList.size() + + ", mappings=" + newInterfaceToGroupMap.size()); } + + this.groupBucketList = newGroupBucketList; + this.interfaceToGroupMap = newInterfaceToGroupMap; + this.groupVoMap = newGroupVoMap; } public synchronized void reloadAdapter() throws Exception { @@ -293,11 +300,11 @@ public class InflowControlManager implements Lifecycle, Bucket { // Notify our interested LifecycleListeners lifecycle.fireLifecycleEvent(STOPING_EVENT, this); - adapterBucketList.clear(); - interfaceBucketList.clear(); - groupBucketList.clear(); - interfaceToGroupMap.clear(); - groupVoMap.clear(); + adapterBucketList = new ConcurrentHashMap<>(); + interfaceBucketList = new ConcurrentHashMap<>(); + groupBucketList = new ConcurrentHashMap<>(); + interfaceToGroupMap = new ConcurrentHashMap<>(); + groupVoMap = new ConcurrentHashMap<>(); started = false; // Notify our interested LifecycleListeners @@ -340,15 +347,15 @@ public class InflowControlManager implements Lifecycle, Bucket { return svcCd; } - public void removeAdapter(String adapter) { + public synchronized void removeAdapter(String adapter) { adapterBucketList.remove(adapter); } - public void removeInterface(String inter) { + public synchronized void removeInterface(String inter) { interfaceBucketList.remove(inter); } - public void removeGroup(String groupId) { + public synchronized void removeGroup(String groupId) { groupBucketList.remove(groupId); groupVoMap.remove(groupId); // 해당 그룹에 속한 인터페이스 매핑도 제거