001// Copyright 2006-2013 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 015package org.apache.tapestry5.ioc.internal.services; 016 017import org.apache.tapestry5.ioc.Invokable; 018import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 019import org.apache.tapestry5.ioc.internal.util.JDKUtils; 020import org.apache.tapestry5.ioc.services.PerThreadValue; 021import org.apache.tapestry5.ioc.services.PerthreadManager; 022import org.apache.tapestry5.ioc.services.RegistryShutdownHub; 023import org.apache.tapestry5.ioc.services.ThreadCleanupListener; 024import org.slf4j.Logger; 025 026import java.util.List; 027import java.util.Map; 028import java.util.concurrent.atomic.AtomicBoolean; 029import java.util.concurrent.atomic.AtomicInteger; 030import java.util.concurrent.locks.Lock; 031 032@SuppressWarnings("all") 033public class PerthreadManagerImpl implements PerthreadManager 034{ 035 private final Lock lock = JDKUtils.createLockForThreadLocalCreation(); 036 037 private final PerThreadValue<List<Runnable>> callbacksValue; 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 callbacksValue = 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<Runnable> getCallbacks() 097 { 098 List<Runnable> result = callbacksValue.get(); 099 100 if (result == null) 101 { 102 result = CollectionFactory.newList(); 103 callbacksValue.set(result); 104 } 105 106 return result; 107 } 108 109 public void addThreadCleanupListener(final ThreadCleanupListener listener) 110 { 111 assert listener != null; 112 113 addThreadCleanupCallback(new Runnable() 114 { 115 public void run() 116 { 117 listener.threadDidCleanup(); 118 } 119 }); 120 } 121 122 public void addThreadCleanupCallback(Runnable callback) 123 { 124 assert callback != null; 125 126 getCallbacks().add(callback); 127 } 128 129 /** 130 * Instructs the hub to notify all its listeners (for the current thread). 131 * It also discards its list of listeners. 132 */ 133 public void cleanup() 134 { 135 List<Runnable> callbacks = getCallbacks(); 136 137 callbacksValue.set(null); 138 139 for (Runnable callback : callbacks) 140 { 141 try 142 { 143 callback.run(); 144 } catch (Exception ex) 145 { 146 logger.warn(String.format("Error invoking callback %s: %s", callback, ex), 147 ex); 148 } 149 } 150 151 // Listeners should not re-add themselves or store any per-thread state 152 // here, it will be lost. 153 154 try 155 { 156 lock.lock(); 157 158 // Discard the per-thread map of values, including the key that stores 159 // the listeners. This means that if a listener attempts to register 160 // new listeners, the new listeners will not be triggered and will be 161 // released to the GC. 162 163 holder.remove(); 164 } finally 165 { 166 lock.unlock(); 167 } 168 } 169 170 private static Object NULL_VALUE = new Object(); 171 172 <T> PerThreadValue<T> createValue(final Object key) 173 { 174 return new PerThreadValue<T>() 175 { 176 public T get() 177 { 178 return get(null); 179 } 180 181 public T get(T defaultValue) 182 { 183 Map map = getPerthreadMap(); 184 185 if (map.containsKey(key)) 186 { 187 Object storedValue = map.get(key); 188 189 if (storedValue == NULL_VALUE) 190 return null; 191 192 return (T) storedValue; 193 } 194 195 return defaultValue; 196 } 197 198 public T set(T newValue) 199 { 200 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue); 201 202 return newValue; 203 } 204 205 public boolean exists() 206 { 207 return getPerthreadMap().containsKey(key); 208 } 209 }; 210 } 211 212 public <T> PerThreadValue<T> createValue() 213 { 214 return createValue(uuidGenerator.getAndIncrement()); 215 } 216 217 public void run(Runnable runnable) 218 { 219 assert runnable != null; 220 221 try 222 { 223 runnable.run(); 224 } finally 225 { 226 cleanup(); 227 } 228 } 229 230 public <T> T invoke(Invokable<T> invokable) 231 { 232 try 233 { 234 return invokable.invoke(); 235 } finally 236 { 237 cleanup(); 238 } 239 } 240}