001 // Copyright 2006-2012 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.ioc.internal.services; 016 017 import org.apache.tapestry5.ioc.Invokable; 018 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 019 import org.apache.tapestry5.ioc.internal.util.JDKUtils; 020 import org.apache.tapestry5.ioc.services.PerThreadValue; 021 import org.apache.tapestry5.ioc.services.PerthreadManager; 022 import org.apache.tapestry5.ioc.services.RegistryShutdownHub; 023 import org.apache.tapestry5.ioc.services.ThreadCleanupListener; 024 import org.slf4j.Logger; 025 026 import java.util.List; 027 import java.util.Map; 028 import java.util.concurrent.atomic.AtomicBoolean; 029 import java.util.concurrent.atomic.AtomicInteger; 030 import java.util.concurrent.locks.Lock; 031 032 @SuppressWarnings("all") 033 public class PerthreadManagerImpl implements PerthreadManager 034 { 035 private final Lock lock = JDKUtils.createLockForThreadLocalCreation(); 036 037 private final PerThreadValue<List<ThreadCleanupListener>> listenersValue; 038 039 private static class MapHolder extends ThreadLocal<Map> 040 { 041 @Override 042 protected Map initialValue() 043 { 044 return CollectionFactory.newMap(); 045 } 046 } 047 048 private final Logger logger; 049 050 private final MapHolder holder = new MapHolder(); 051 052 private final AtomicInteger uuidGenerator = new AtomicInteger(); 053 054 private final AtomicBoolean shutdown = new AtomicBoolean(); 055 056 public PerthreadManagerImpl(Logger logger) 057 { 058 this.logger = logger; 059 060 listenersValue = createValue(); 061 } 062 063 public void registerForShutdown(RegistryShutdownHub hub) 064 { 065 hub.addRegistryShutdownListener(new Runnable() 066 { 067 @Override 068 public void run() 069 { 070 cleanup(); 071 shutdown.set(true); 072 } 073 }); 074 } 075 076 private Map getPerthreadMap() 077 { 078 // This is a degenerate case; it may not even exist; but if during registry shutdown somehow code executes 079 // that attempts to create new values or add new listeners, those go into a new map instance that is 080 // not referenced (and so immediately GCed). 081 if (shutdown.get()) 082 { 083 return CollectionFactory.newMap(); 084 } 085 086 lock.lock(); 087 088 try 089 { 090 return holder.get(); 091 } finally 092 { 093 lock.unlock(); 094 } 095 } 096 097 private List<ThreadCleanupListener> getListeners() 098 { 099 List<ThreadCleanupListener> result = listenersValue.get(); 100 101 if (result == null) 102 { 103 result = CollectionFactory.newList(); 104 listenersValue.set(result); 105 } 106 107 return result; 108 } 109 110 public void addThreadCleanupListener(ThreadCleanupListener listener) 111 { 112 getListeners().add(listener); 113 } 114 115 /** 116 * Instructs the hub to notify all its listeners (for the current thread). 117 * It also discards its list of listeners. 118 */ 119 public void cleanup() 120 { 121 List<ThreadCleanupListener> listeners = getListeners(); 122 123 listenersValue.set(null); 124 125 for (ThreadCleanupListener listener : listeners) 126 { 127 try 128 { 129 listener.threadDidCleanup(); 130 } catch (Exception ex) 131 { 132 logger.warn(ServiceMessages.threadCleanupError(listener, ex), ex); 133 } 134 } 135 136 // Listeners should not re-add themselves or store any per-thread state 137 // here, it will be lost. 138 139 try 140 { 141 lock.lock(); 142 143 // Discard the per-thread map of values, including the key that stores 144 // the listeners. This means that if a listener attempts to register 145 // new listeners, the new listeners will not be triggered and will be 146 // released to the GC. 147 148 holder.remove(); 149 } finally 150 { 151 lock.unlock(); 152 } 153 } 154 155 private static Object NULL_VALUE = new Object(); 156 157 <T> PerThreadValue<T> createValue(final Object key) 158 { 159 return new PerThreadValue<T>() 160 { 161 public T get() 162 { 163 return get(null); 164 } 165 166 public T get(T defaultValue) 167 { 168 Map map = getPerthreadMap(); 169 170 if (map.containsKey(key)) 171 { 172 Object storedValue = map.get(key); 173 174 if (storedValue == NULL_VALUE) 175 return null; 176 177 return (T) storedValue; 178 } 179 180 return defaultValue; 181 } 182 183 public T set(T newValue) 184 { 185 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue); 186 187 return newValue; 188 } 189 190 public boolean exists() 191 { 192 return getPerthreadMap().containsKey(key); 193 } 194 }; 195 } 196 197 public <T> PerThreadValue<T> createValue() 198 { 199 return createValue(uuidGenerator.getAndIncrement()); 200 } 201 202 public void run(Runnable runnable) 203 { 204 assert runnable != null; 205 206 try 207 { 208 runnable.run(); 209 } finally 210 { 211 cleanup(); 212 } 213 } 214 215 public <T> T invoke(Invokable<T> invokable) 216 { 217 try 218 { 219 return invokable.invoke(); 220 } finally 221 { 222 cleanup(); 223 } 224 } 225 }