001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins;
023:
024: import java.util.ArrayList;
025: import java.util.Timer;
026: import java.util.TimerTask;
027:
028: import org.jboss.deployment.DeploymentException;
029: import org.jboss.ejb.EnterpriseContext;
030: import org.jboss.logging.Logger;
031: import org.jboss.metadata.MetaData;
032: import org.jboss.metadata.XmlLoadable;
033: import org.jboss.monitor.Monitorable;
034: import org.jboss.monitor.client.BeanCacheSnapshot;
035: import org.jboss.util.LRUCachePolicy;
036: import org.w3c.dom.Element;
037:
038: /**
039: * Least Recently Used cache policy for EnterpriseContexts.
040: *
041: * @see AbstractInstanceCache
042: * @author <a href="mailto:simone.bordet@compaq.com">Simone Bordet</a>
043: * @author <a href="mailto:bill@jboss.org">Bill Burke</a>
044: * @version $Revision: 57209 $
045: */
046: public class LRUEnterpriseContextCachePolicy extends LRUCachePolicy
047: implements XmlLoadable, Monitorable {
048: // Constants -----------------------------------------------------
049:
050: // Attributes ----------------------------------------------------
051: protected static Logger log = Logger
052: .getLogger(LRUEnterpriseContextCachePolicy.class);
053: protected static Timer tasksTimer = new Timer(true);
054: static {
055: log.debug("Cache policy timer started, tasksTimer="
056: + tasksTimer);
057: }
058:
059: /** The AbstractInstanceCache that uses this cache policy */
060: private AbstractInstanceCache m_cache;
061:
062: /** The period of the resizer's runs */
063: private long m_resizerPeriod;
064:
065: /** The period of the overager's runs */
066: private long m_overagerPeriod;
067:
068: /** The age after which a bean is automatically passivated */
069: private long m_maxBeanAge;
070:
071: /**
072: * Enlarge cache capacity if there is a cache miss every or less
073: * this member's value
074: */
075: private long m_minPeriod;
076:
077: /**
078: * Shrink cache capacity if there is a cache miss every or more
079: * this member's value
080: */
081: private long m_maxPeriod;
082:
083: /**
084: * The resizer will always try to keep the cache capacity so
085: * that the cache is this member's value loaded of cached objects
086: */
087: private double m_factor;
088:
089: /** The overager timer task */
090: private TimerTask m_overager;
091:
092: /** The resizer timer task */
093: private TimerTask m_resizer;
094:
095: /** Useful for log messages */
096: private StringBuffer m_buffer = new StringBuffer();
097:
098: // Static --------------------------------------------------------
099:
100: // Constructors --------------------------------------------------
101:
102: /**
103: * Creates a LRU cache policy object given the instance cache that use
104: * this policy object.
105: */
106: public LRUEnterpriseContextCachePolicy(AbstractInstanceCache eic) {
107: if (eic == null)
108: throw new IllegalArgumentException(
109: "Instance cache argument cannot be null");
110:
111: m_cache = eic;
112: }
113:
114: // Public --------------------------------------------------------
115:
116: // Monitorable implementation ------------------------------------
117:
118: public void sample(Object s) {
119: if (m_cache == null)
120: return;
121:
122: BeanCacheSnapshot snapshot = (BeanCacheSnapshot) s;
123: LRUList list = getList();
124: synchronized (m_cache.getCacheLock()) {
125: snapshot.m_cacheMinCapacity = list.m_minCapacity;
126: snapshot.m_cacheMaxCapacity = list.m_maxCapacity;
127: snapshot.m_cacheCapacity = list.m_capacity;
128: snapshot.m_cacheSize = list.m_count;
129: }
130: }
131:
132: // Z implementation ----------------------------------------------
133:
134: public void start() {
135: if (m_resizerPeriod > 0) {
136: m_resizer = new ResizerTask(m_resizerPeriod);
137: long delay = (long) (Math.random() * m_resizerPeriod);
138: tasksTimer.schedule(m_resizer, delay, m_resizerPeriod);
139: }
140:
141: if (m_overagerPeriod > 0) {
142: m_overager = new OveragerTask(m_overagerPeriod);
143: long delay = (long) (Math.random() * m_overagerPeriod);
144: tasksTimer.schedule(m_overager, delay, m_overagerPeriod);
145: }
146: }
147:
148: public void stop() {
149: if (m_resizer != null) {
150: m_resizer.cancel();
151: }
152: if (m_overager != null) {
153: m_overager.cancel();
154: }
155: super .stop();
156: }
157:
158: public void destroy() {
159: m_overager = null;
160: m_resizer = null;
161: m_cache = null;
162: super .destroy();
163: }
164:
165: /**
166: * Reads from the configuration the parameters for this cache policy, that are
167: * all optionals.
168: * FIXME 20010626 marcf:
169: * Simone seriously arent' all the options overkill? give it another 6 month .
170: * Remember you are exposing the guts of this to the end user, also provide defaults
171: * so that if an entry is not specified you can still work and it looks _much_ better in
172: * the configuration files.
173: *
174: */
175: public void importXml(Element element) throws DeploymentException {
176: String min = MetaData.getElementContent(MetaData
177: .getOptionalChild(element, "min-capacity"));
178: String max = MetaData.getElementContent(MetaData
179: .getOptionalChild(element, "max-capacity"));
180: String op = MetaData.getElementContent(MetaData
181: .getOptionalChild(element, "overager-period"));
182: String rp = MetaData.getElementContent(MetaData
183: .getOptionalChild(element, "resizer-period"));
184: String ma = MetaData.getElementContent(MetaData
185: .getOptionalChild(element, "max-bean-age"));
186: String map = MetaData.getElementContent(MetaData
187: .getOptionalChild(element, "max-cache-miss-period"));
188: String mip = MetaData.getElementContent(MetaData
189: .getOptionalChild(element, "min-cache-miss-period"));
190: String fa = MetaData.getElementContent(MetaData
191: .getOptionalChild(element, "cache-load-factor"));
192: try {
193: if (min != null) {
194: int s = Integer.parseInt(min);
195: if (s <= 0) {
196: throw new DeploymentException(
197: "Min cache capacity can't be <= 0");
198: }
199: m_minCapacity = s;
200: }
201: if (max != null) {
202: int s = Integer.parseInt(max);
203: if (s <= 0) {
204: throw new DeploymentException(
205: "Max cache capacity can't be <= 0");
206: }
207: m_maxCapacity = s;
208: }
209: if (op != null) {
210: int p = Integer.parseInt(op);
211: if (p <= 0) {
212: throw new DeploymentException(
213: "Overager period can't be <= 0");
214: }
215: m_overagerPeriod = p * 1000;
216: }
217: if (rp != null) {
218: int p = Integer.parseInt(rp);
219: if (p <= 0) {
220: throw new DeploymentException(
221: "Resizer period can't be <= 0");
222: }
223: m_resizerPeriod = p * 1000;
224: }
225: if (ma != null) {
226: int a = Integer.parseInt(ma);
227: if (a <= 0) {
228: throw new DeploymentException(
229: "Max bean age can't be <= 0");
230: }
231: m_maxBeanAge = a * 1000;
232: }
233: if (map != null) {
234: int p = Integer.parseInt(map);
235: if (p <= 0) {
236: throw new DeploymentException(
237: "Max cache miss period can't be <= 0");
238: }
239: m_maxPeriod = p * 1000;
240: }
241: if (mip != null) {
242: int p = Integer.parseInt(mip);
243: if (p <= 0) {
244: throw new DeploymentException(
245: "Min cache miss period can't be <= 0");
246: }
247: m_minPeriod = p * 1000;
248: }
249: if (fa != null) {
250: double f = Double.parseDouble(fa);
251: if (f <= 0.0) {
252: throw new DeploymentException(
253: "Cache load factor can't be <= 0");
254: }
255: m_factor = f;
256: }
257: } catch (NumberFormatException x) {
258: throw new DeploymentException(
259: "Can't parse policy configuration", x);
260: }
261: }
262:
263: // Y overrides ---------------------------------------------------
264:
265: /**
266: * Flush is overriden here because in this policy impl
267: * flush might not actually remove all the instances from the cache.
268: * Those instances that are in use (associated with a transaction) should not
269: * be removed from the cache. So, the iteration is done not until the cache is empty
270: * but until we tried to age-out every instance in the cache.
271: */
272: public void flush() {
273: int i = size();
274: LRUCacheEntry entry = null;
275: while (i-- > 0 && (entry = m_list.m_tail) != null) {
276: ageOut(entry);
277: }
278: }
279:
280: // Package protected ---------------------------------------------
281:
282: // Protected -----------------------------------------------------
283:
284: protected LRUList createList() {
285: return new ContextLRUList();
286: }
287:
288: protected void ageOut(LRUCacheEntry entry) {
289: if (m_cache == null)
290: return;
291:
292: if (entry == null) {
293: throw new IllegalArgumentException(
294: "Cannot remove a null cache entry");
295: }
296:
297: if (log.isTraceEnabled()) {
298: m_buffer.setLength(0);
299: m_buffer.append("Aging out from cache bean ");
300: m_buffer.append(m_cache.getContainer().getBeanMetaData()
301: .getEjbName());
302: m_buffer.append("with id = ");
303: m_buffer.append(entry.m_key);
304: m_buffer.append("; cache size = ");
305: m_buffer.append(getList().m_count);
306: log.trace(m_buffer.toString());
307: }
308:
309: // This will schedule the passivation
310: m_cache.release((EnterpriseContext) entry.m_object);
311: }
312:
313: protected void cacheMiss() {
314: LRUList list = getList();
315: ++list.m_cacheMiss;
316: }
317:
318: // Private -------------------------------------------------------
319:
320: private LRUList getList() {
321: return m_list;
322: }
323:
324: // Inner classes -------------------------------------------------
325:
326: /**
327: * This TimerTask resizes the cache capacity using the cache miss frequency
328: * algorithm, that is the more cache misses we have, the more the cache size
329: * is enlarged, and viceversa. <p>
330: * Of course, the maximum and minimum capacity are the bounds that this
331: * resizer never passes.
332: */
333: protected class ResizerTask extends TimerTask {
334: private String m_message;
335: private StringBuffer m_buffer;
336: private long resizerPeriod;
337:
338: protected ResizerTask(long resizerPeriod) {
339: this .resizerPeriod = resizerPeriod;
340: m_message = "Resized cache for bean "
341: + m_cache.getContainer().getBeanMetaData()
342: .getEjbName() + ": old capacity = ";
343: m_buffer = new StringBuffer();
344: }
345:
346: public void run() {
347: // For now implemented as a Cache Miss Frequency algorithm
348: if (m_cache == null) {
349: cancel();
350: return;
351: }
352:
353: LRUList list = getList();
354:
355: // Sync with the cache, since it is accessed also by another thread
356: synchronized (m_cache.getCacheLock()) {
357: int period = list.m_cacheMiss == 0 ? Integer.MAX_VALUE
358: : (int) (resizerPeriod / list.m_cacheMiss);
359: int cap = list.m_capacity;
360: if (period <= m_minPeriod && cap < list.m_maxCapacity) {
361: // Enlarge cache capacity: if period == m_minPeriod then
362: // the capacity is increased of the (1-m_factor)*100 %.
363: double factor = 1.0
364: + ((double) m_minPeriod / period)
365: * (1.0 - m_factor);
366: int newCap = (int) (cap * factor);
367: list.m_capacity = newCap < list.m_maxCapacity ? newCap
368: : list.m_maxCapacity;
369: log(cap, list.m_capacity);
370: } else if (period >= m_maxPeriod
371: && cap > list.m_minCapacity
372: && list.m_count < (cap * m_factor)) {
373: // Shrink cache capacity
374: int newCap = (int) (list.m_count / m_factor);
375: list.m_capacity = newCap > list.m_minCapacity ? newCap
376: : list.m_minCapacity;
377: log(cap, list.m_capacity);
378: }
379: list.m_cacheMiss = 0;
380: }
381: }
382:
383: private void log(int oldCapacity, int newCapacity) {
384: if (log.isTraceEnabled()) {
385: m_buffer.setLength(0);
386: m_buffer.append(m_message);
387: m_buffer.append(oldCapacity);
388: m_buffer.append(", new capacity = ");
389: m_buffer.append(newCapacity);
390: log.trace(m_buffer.toString());
391: }
392: }
393: }
394:
395: /**
396: * This TimerTask passivates cached beans that have not been called for a while.
397: */
398: protected class OveragerTask extends TimerTask {
399: private String m_message;
400: private StringBuffer m_buffer;
401:
402: protected OveragerTask(long period) {
403: m_message = getTaskLogMessage()
404: + " "
405: + m_cache.getContainer().getBeanMetaData()
406: .getEjbName() + " with id = ";
407: m_buffer = new StringBuffer();
408: }
409:
410: public void run() {
411: if (m_cache == null) {
412: cancel();
413: return;
414: }
415:
416: LRUList list = getList();
417: long now = System.currentTimeMillis();
418: ArrayList passivateEntries = null;
419: synchronized (m_cache.getCacheLock()) {
420: for (LRUCacheEntry entry = list.m_tail; entry != null; entry = entry.m_prev) {
421: if (now - entry.m_time >= getMaxAge()) {
422: // Attempt to remove this entry from cache
423: if (passivateEntries == null)
424: passivateEntries = new ArrayList();
425: passivateEntries.add(entry);
426: } else {
427: break;
428: }
429: }
430: }
431: // We need to do this outside of cache lock because of deadlock possibilities
432: // with EntityInstanceInterceptor and Stateful. This is because tryToPassivate
433: // calls lock.synch and other interceptor call lock.synch and after call a cache method that locks
434: if (passivateEntries != null) {
435: for (int i = 0; i < passivateEntries.size(); i++) {
436: LRUCacheEntry entry = (LRUCacheEntry) passivateEntries
437: .get(i);
438: try {
439: m_cache
440: .tryToPassivate((EnterpriseContext) entry.m_object);
441: } catch (Throwable t) {
442: log
443: .debug(
444: "Ignored error while trying to passivate ctx",
445: t);
446: }
447: }
448: }
449: }
450:
451: private void log(Object key, int count) {
452: if (log.isTraceEnabled()) {
453: m_buffer.setLength(0);
454: m_buffer.append(m_message);
455: m_buffer.append(key);
456: m_buffer.append(" - Cache size = ");
457: m_buffer.append(count);
458: log.trace(m_buffer.toString());
459: }
460: }
461:
462: protected String getTaskLogMessage() {
463: return "Scheduling for passivation overaged bean";
464: }
465:
466: protected String getJMSTaskType() {
467: return "OVERAGER";
468: }
469:
470: protected long getMaxAge() {
471: return m_maxBeanAge;
472: }
473: }
474:
475: /**
476: * Subclass that logs list activity events.
477: */
478: protected class ContextLRUList extends LRUList {
479: boolean trace = log.isTraceEnabled();
480:
481: protected void entryPromotion(LRUCacheEntry entry) {
482: if (trace)
483: log.trace("entryPromotion, entry=" + entry);
484:
485: // The cache is full, temporarily increase it
486: if (m_count == m_capacity && m_capacity >= m_maxCapacity) {
487: ++m_capacity;
488: log
489: .warn("Cache has reached maximum capacity for container "
490: + m_cache.getContainer().getJmxName()
491: + " - probably because all instances are in use. "
492: + "Temporarily increasing the size to "
493: + m_capacity);
494: }
495: }
496:
497: protected void entryAdded(LRUCacheEntry entry) {
498: if (trace)
499: log.trace("entryAdded, entry=" + entry);
500: }
501:
502: protected void entryRemoved(LRUCacheEntry entry) {
503: if (trace)
504: log.trace("entryRemoved, entry=" + entry);
505: }
506:
507: protected void capacityChanged(int oldCapacity) {
508: if (trace)
509: log
510: .trace("capacityChanged, oldCapacity="
511: + oldCapacity);
512: }
513: }
514:
515: }
|