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 public void run() 068 { 069 cleanup(); 070 shutdown.set(true); 071 } 072 }); 073 } 074 075 private Map getPerthreadMap() 076 { 077 // This is a degenerate case; it may not even exist; but if during registry shutdown somehow code executes 078 // that attempts to create new values or add new listeners, those go into a new map instance that is 079 // not referenced (and so immediately GCed). 080 if (shutdown.get()) 081 { 082 return CollectionFactory.newMap(); 083 } 084 085 lock.lock(); 086 087 try 088 { 089 return holder.get(); 090 } finally 091 { 092 lock.unlock(); 093 } 094 } 095 096 private List<ThreadCleanupListener> getListeners() 097 { 098 List<ThreadCleanupListener> result = listenersValue.get(); 099 100 if (result == null) 101 { 102 result = CollectionFactory.newList(); 103 listenersValue.set(result); 104 } 105 106 return result; 107 } 108 109 public void addThreadCleanupListener(ThreadCleanupListener listener) 110 { 111 getListeners().add(listener); 112 } 113 114 /** 115 * Instructs the hub to notify all its listeners (for the current thread). 116 * It also discards its list of listeners. 117 */ 118 public void cleanup() 119 { 120 List<ThreadCleanupListener> listeners = getListeners(); 121 122 listenersValue.set(null); 123 124 for (ThreadCleanupListener listener : listeners) 125 { 126 try 127 { 128 listener.threadDidCleanup(); 129 } catch (Exception ex) 130 { 131 logger.warn(ServiceMessages.threadCleanupError(listener, ex), ex); 132 } 133 } 134 135 // Listeners should not re-add themselves or store any per-thread state 136 // here, it will be lost. 137 138 try 139 { 140 lock.lock(); 141 142 // Discard the per-thread map of values, including the key that stores 143 // the listeners. This means that if a listener attempts to register 144 // new listeners, the new listeners will not be triggered and will be 145 // released to the GC. 146 147 holder.remove(); 148 } finally 149 { 150 lock.unlock(); 151 } 152 } 153 154 private static Object NULL_VALUE = new Object(); 155 156 <T> PerThreadValue<T> createValue(final Object key) 157 { 158 return new PerThreadValue<T>() 159 { 160 public T get() 161 { 162 return get(null); 163 } 164 165 public T get(T defaultValue) 166 { 167 Map map = getPerthreadMap(); 168 169 if (map.containsKey(key)) 170 { 171 Object storedValue = map.get(key); 172 173 if (storedValue == NULL_VALUE) 174 return null; 175 176 return (T) storedValue; 177 } 178 179 return defaultValue; 180 } 181 182 public T set(T newValue) 183 { 184 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue); 185 186 return newValue; 187 } 188 189 public boolean exists() 190 { 191 return getPerthreadMap().containsKey(key); 192 } 193 }; 194 } 195 196 public <T> PerThreadValue<T> createValue() 197 { 198 return createValue(uuidGenerator.getAndIncrement()); 199 } 200 201 public void run(Runnable runnable) 202 { 203 assert runnable != null; 204 205 try 206 { 207 runnable.run(); 208 } finally 209 { 210 cleanup(); 211 } 212 } 213 214 public <T> T invoke(Invokable<T> invokable) 215 { 216 try 217 { 218 return invokable.invoke(); 219 } finally 220 { 221 cleanup(); 222 } 223 } 224 }