1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.mx.loading;
23
24 import java.net.URL;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.LinkedList;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Set;
32 import java.util.WeakHashMap;
33 import java.security.PrivilegedAction;
34 import java.security.AccessController;
35
36 import org.jboss.logging.Logger;
37 import org.jboss.mx.loading.ClassLoadingTask.ThreadTask;
38
39
40 /** A utility class used by the UnifiedClassLoader3 to manage the thread based
41 * class loading tasks.
42 *
43 * @author Scott.Stark@jboss.org
44 * @version $Revision: 68584 $
45 */
46 public class LoadMgr3
47 {
48 private static Logger log = Logger.getLogger(LoadMgr3.class);
49 /** Used as a synchronization monitor during the setup/teardown of the
50 thread owning a UCL.loadClass lock
51 */
52 private static Object registrationLock = new Object();
53
54 /** A Map<UnifiedClassLoader3, Thread> of the active loadClass UCL3/threads.
55 * This must be accessed under the registrationLock monitor.
56 */
57 private static HashMap loadClassThreads = new HashMap();
58 /** A Map<Thread, LinkedList<ThreadTask> > of the class loading tasks
59 * associated with a thread
60 */
61 private static Map loadTasksByThread = Collections.synchronizedMap(new WeakHashMap());
62
63 private static SecurityManager sm = System.getSecurityManager();
64
65 /** A UCL and its relative ordering with respect to the class loading.
66 * The UCL with the lowest order to load a class is the UCL that will
67 * populate the repository cache and be assigned as the UCL.loadClass
68 * return value.
69 */
70 public static class PkgClassLoader
71 {
72 public final RepositoryClassLoader ucl;
73 public final int order;
74
75 public PkgClassLoader(RepositoryClassLoader ucl)
76 {
77 this(ucl, Integer.MAX_VALUE);
78 }
79 public PkgClassLoader(RepositoryClassLoader ucl, int order)
80 {
81 this.ucl = ucl;
82 this.order = order;
83 }
84
85 public String toString()
86 {
87 StringBuffer buffer = new StringBuffer(100);
88 buffer.append(super.toString());
89 buffer.append("{ucl=").append(ucl);
90 buffer.append(" order=").append(order);
91 buffer.append('}');
92 return buffer.toString();
93 }
94 }
95 /** A PrivilegedAction for locating a class as a resource
96 *
97 */
98 private static class ResourceAction implements PrivilegedAction
99 {
100 RepositoryClassLoader ucl;
101 String classRsrcName;
102 ResourceAction(RepositoryClassLoader ucl, String classRsrcName)
103 {
104 this.ucl = ucl;
105 this.classRsrcName = classRsrcName;
106 }
107 public Object run()
108 {
109 URL url = ucl.getResourceLocally(classRsrcName);
110 ucl = null;
111 classRsrcName = null;
112 return url;
113 }
114 }
115
116 /** Register that a thread owns the UCL3.loadClass monitor. This is called
117 * from within UCL3.loadClass(String,boolean) and this method creates
118 * entries in the loadClassThreads and loadTasksByThread maps.
119 */
120 public static void registerLoaderThread(RepositoryClassLoader ucl, Thread t)
121 {
122 synchronized( registrationLock )
123 {
124 Object prevThread = loadClassThreads.put(ucl, t);
125 if( log.isTraceEnabled() )
126 log.trace("registerLoaderThread, ucl="+ucl+", t="+t+", prevT="+prevThread);
127
128 synchronized( loadTasksByThread )
129 {
130 List taskList = (List) loadTasksByThread.get(t);
131 if( taskList == null )
132 {
133 taskList = Collections.synchronizedList(new LinkedList());
134 loadTasksByThread.put(t, taskList);
135 if( log.isTraceEnabled() )
136 log.trace("created new task list");
137 }
138 }
139 registrationLock.notifyAll();
140 }
141 }
142
143 /** Initiate the class loading task. This is called by UCL3.loadClass to
144 * initiate the process of loading the requested class. This first attempts
145 * to load the class from the repository cache, and then the class loaders
146 * in the repsository. If the package of the class is found in the repository
147 * then one or more ThreadTask are created to complete the ClassLoadingTask.
148 * The ThreadTask are assigned to the threads that own the associated UCL3
149 * monitor. If no class loader serves the class package, then the requesting
150 * class loader is asked if it can load the class.
151 *
152 * @return true if the class could be loaded from the cache or requesting
153 * UCL3, false to indicate the calling thread must process the
154 * tasks assigned to it until the ClassLoadingTask state is FINISHED
155 * @exception ClassNotFoundException if there is no chance the class can
156 * be loaded from the current repository class loaders.
157 */
158 public static boolean beginLoadTask(ClassLoadingTask task,
159 UnifiedLoaderRepository3 repository)
160 throws ClassNotFoundException
161 {
162 boolean trace = log.isTraceEnabled();
163 if( trace )
164 log.trace("Begin beginLoadTask, task="+task);
165
166 // Try the cache before anything else.
167 Class cls = repository.loadClassFromCache(task.classname);
168 if( cls != null )
169 {
170 task.loadedClass = cls;
171 task.state = ClassLoadingTask.FINISHED;
172 if( trace )
173 log.trace("End beginLoadTask, loadClassFromCache, classname: "+task.classname);
174 return true;
175 }
176
177 // Next get the set of class loaders from the packages map
178 Set pkgSet = repository.getPackageClassLoaders(task.classname);
179 if( pkgSet == null || pkgSet.size() == 0 )
180 {
181 if (task.stopOrder == Integer.MAX_VALUE)
182 {
183 /* If there are no class loaders in the repository capable of handling
184 the request ask the class loader itself in the event that its parent(s)
185 can load the class.
186 */
187 try
188 {
189 cls = repository.loadClassFromClassLoader(task.classname, false,
190 task.requestingClassLoader);
191 }
192 catch(LinkageError e)
193 {
194 if( trace )
195 log.trace("End beginLoadTask, LinkageError for task: "+task, e);
196 throw e;
197 }
198 if( cls != null )
199 {
200 task.loadedClass = cls;
201 task.state = ClassLoadingTask.FINISHED;
202 if( trace )
203 log.trace("End beginLoadTask, loadClassFromClassLoader");
204 return true;
205 }
206 }
207
208 // Else, fail the load
209 if( trace )
210 log.trace("End beginLoadTask, ClassNotFoundException");
211 String msg = "No ClassLoaders found for: "+task.classname;
212 throw new ClassNotFoundException(msg);
213 }
214
215 /* A class loading task for each ClassLoader is needed. There can be
216 multiple class loaders for a pkg due to the pkg being spread out over
217 multiple jars, or duplicate classes due to versioning/patches, or
218 just bad packaging.
219
220 In the case of a non-scoped deployment of multiple classes which
221 will provide a PkgClassLoader to define the ordering, we simply
222 choose an ordering based on the order the UCL3s were added to the
223 repository. At most one of the candidate UCL3s will load the class
224 in order to avoid ClassCastExceptions or LinkageErrors due to the
225 strong Java type system/security model.
226
227 TODO: A simple ordering mechanism exists, but this probably needs
228 to be augmented.
229 */
230 Iterator iter = pkgSet.iterator();
231 RepositoryClassLoader theUCL = null;
232 int order = Integer.MAX_VALUE;
233 while( iter.hasNext() )
234 {
235 Object next = iter.next();
236 int uclOrder;
237 RepositoryClassLoader ucl;
238 // This may be either a PkgClassLoader or a UCL3
239 if( next instanceof RepositoryClassLoader )
240 {
241 ucl = (RepositoryClassLoader) next;
242 uclOrder = ucl.getAddedOrder();
243 }
244 else
245 {
246 PkgClassLoader pkgUcl = (PkgClassLoader) next;
247 ucl = pkgUcl.ucl;
248 uclOrder = pkgUcl.order;
249 }
250
251 // If we have a stop order check it
252 if (task.stopOrder != Integer.MAX_VALUE && task.stopOrder <= uclOrder)
253 break;
254
255 // Validate that the ucl has the class as a resource
256 String classRsrcName = task.classname.replace('.', '/') + ".class";
257 URL url = null;
258 if( sm != null )
259 {
260 ResourceAction action = new ResourceAction(ucl, classRsrcName);
261 url = (URL) AccessController.doPrivileged(action);
262 }
263 else
264 {
265 url = ucl.getResourceLocally(classRsrcName);
266 }
267
268 if( url != null && uclOrder < order )
269 {
270 if( trace && theUCL != null )
271 log.trace("Replacing UCL: "+theUCL+" with UCL:"+ucl);
272 theUCL = ucl;
273 order = uclOrder;
274 }
275 else if(trace)
276 {
277 if(url == null)
278 log.trace("No resource found for: "+classRsrcName);
279 else
280 log.trace("Ignoring class loader based on order: "+ucl);
281 }
282 }
283 if( theUCL == null && task.stopOrder == Integer.MAX_VALUE)
284 {
285 /* If there are no class loaders in the repository capable of handling
286 the request ask the class loader itself in the event that its parent(s)
287 can load the class. But not if we have a stopOrder.
288 */
289 try
290 {
291 cls = repository.loadClassFromClassLoader(task.classname, false,
292 task.requestingClassLoader);
293 }
294 catch(LinkageError e)
295 {
296 if( trace )
297 log.trace("End beginLoadTask, LinkageError for task: "+task, e);
298 throw e;
299 }
300 if( cls != null )
301 {
302 task.loadedClass = cls;
303 task.state = ClassLoadingTask.FINISHED;
304 if( trace )
305 log.trace("End beginLoadTask, loadClassFromClassLoader");
306 return true;
307 }
308
309 // Else, fail the load
310 if( trace )
311 log.trace("End beginLoadTask, ClassNotFoundException");
312 String msg = "No ClassLoaders found for: "+task.classname;
313 throw new ClassNotFoundException(msg);
314 }
315
316 if (theUCL == null)
317 {
318 if( trace )
319 log.trace("End beginLoadTask, ClassNotFoundException");
320 String msg = "No ClassLoaders found for: "+task.classname;
321 throw new ClassNotFoundException(msg);
322 }
323
324 scheduleTask(task, theUCL, order, false, trace);
325 task.state = ClassLoadingTask.FOUND_CLASS_LOADER;
326 if( trace )
327 log.trace("End beginLoadTask, task="+task);
328
329 return false;
330 }
331
332 /** Called by threads owning a UCL3.loadLock from within UCL3.loadClass to
333 * process ThreadTasks assigned to them. This is the mechanism by which we
334 * avoid deadlock due to a given loadClass request requiring multiple UCLs
335 * to be involved. Any thread active in loadClass with the monitor held
336 * processes class loading tasks that must be handled by its UCL3. The
337 * active set of threads loading classes form a pool of cooperating threads.
338 */
339 public static void nextTask(Thread t, ClassLoadingTask task,
340 UnifiedLoaderRepository3 repository)
341 throws InterruptedException
342 {
343 boolean trace = log.isTraceEnabled();
344 List taskList = (List) loadTasksByThread.get(t);
345 synchronized( taskList )
346 {
347 // There may not be any ThreadTasks
348 while( taskList.size() == 0 && task.threadTaskCount != 0 )
349 {
350 /* There are no more tasks for the calling thread to execute, so the
351 calling thread must wait until the task.threadTaskCount reaches 0
352 */
353 if( trace )
354 log.trace("Begin nextTask(WAIT_ON_EVENT), task="+task);
355 try
356 {
357 task.state = ClassLoadingTask.WAIT_ON_EVENT;
358 taskList.wait();
359 }
360 catch(InterruptedException e)
361 {
362 if( trace )
363 log.trace("nextTask(WAIT_ON_EVENT), interrupted, task="+task, e);
364 // Abort this task attempt
365 throw e;
366 }
367 if( trace )
368 log.trace("nextTask(WAIT_ON_EVENT), notified, task="+task);
369 }
370
371 if( trace )
372 log.trace("Continue nextTask("+taskList.size()+"), task="+task);
373
374 // See if the task is complete
375 if( task.threadTaskCount == 0 )
376 {
377 task.state = ClassLoadingTask.FINISHED;
378 log.trace("End nextTask(FINISHED), task="+task);
379 return;
380 }
381 }
382
383 ThreadTask threadTask = (ThreadTask) taskList.remove(0);
384 ClassLoadingTask loadTask = threadTask.getLoadTask();
385 if( trace )
386 log.trace("Begin nextTask("+taskList.size()+"), loadTask="+loadTask);
387
388 RepositoryClassLoader ucl3 = threadTask.ucl;
389 try
390 {
391 if( threadTask.t == null )
392 {
393 /* This is a task that has been reassigned back to the original
394 requesting thread ClassLoadingTask, so a new ThreadTask must
395 be scheduled.
396 */
397 if( trace )
398 log.trace("Rescheduling threadTask="+threadTask);
399 scheduleTask(loadTask, ucl3, threadTask.order, true, trace);
400 }
401 else
402 {
403 if( trace )
404 log.trace("Running threadTask="+threadTask);
405 // Load the class using this thread
406 threadTask.run();
407 }
408 }
409 catch(Throwable e)
410 {
411 boolean retry = e instanceof ClassCircularityError
412 || e.getClass().equals(LinkageError.class);
413 int numCCE = loadTask.incNumCCE();
414 if( retry && numCCE <= 10 )
415 {
416 /* Reschedule this task after all existing tasks to allow the
417 current load tasks which are conflicting to complete.
418 */
419 try
420 {
421 if( trace )
422 log.trace("Run failed with exception", e);
423 // Reschedule and update the loadTask.threadTaskCount
424 scheduleTask(loadTask, ucl3, Integer.MAX_VALUE, true, trace);
425 }
426 catch(Throwable ex)
427 {
428 loadTask.setLoadError(ex);
429 log.warn("Failed to reschedule task after LinkageError", ex);
430 }
431 if( trace )
432 log.trace("Post LinkageError state, loadTask="+loadTask);
433 }
434 else
435 {
436 loadTask.setLoadError(e);
437 if( trace )
438 log.trace("Run failed with exception, loadTask="+loadTask, e);
439 }
440 }
441 finally
442 {
443 // We must release the loadLock acquired in beginLoadTask
444 if( threadTask.releaseInNextTask == true )
445 {
446 if( trace )
447 log.trace("Releasing loadLock and ownership of UCL: "+threadTask.ucl);
448 synchronized( registrationLock )
449 {
450 loadClassThreads.remove(threadTask.ucl);
451 }
452 synchronized( threadTask.ucl )
453 {
454 ucl3.release();
455 ucl3.notifyAll();
456 }
457 }
458 }
459
460 // If the ThreadTasks are complete mark the ClassLoadingTask finished
461 if( loadTask.threadTaskCount == 0 )
462 {
463 Class loadedClass = threadTask.getLoadedClass();
464 if( loadedClass != null )
465 {
466 ClassLoader loader = loadedClass.getClassLoader();
467 ClassLoader wrapper = repository.getWrappingClassLoader(loader);
468 if (wrapper != null)
469 loader=wrapper;
470 // Place the loaded class into the repositry cache
471 repository.cacheLoadedClass(threadTask.getClassname(),
472 loadedClass, loader);
473 }
474 /*
475 synchronized( loadTask )
476 {
477 if( trace )
478 log.trace("Notifying task of thread completion, loadTask:"+loadTask);
479 loadTask.state = ClassLoadingTask.FINISHED;
480 loadTask.notify();
481 }
482 */
483 List loadTaskThreadTasks = (List) loadTasksByThread.get(loadTask.requestingThread);
484 synchronized( loadTaskThreadTasks )
485 {
486 if( trace )
487 log.trace("Notifying task of thread completion, loadTask:"+loadTask);
488 loadTask.state = ClassLoadingTask.FINISHED;
489 loadTaskThreadTasks.notify();
490 }
491 }
492 if( trace )
493 log.trace("End nextTask("+taskList.size()+"), loadTask="+loadTask);
494 }
495
496 /** Complete a ClassLoadingTask. This is called by UCL3.loadClass to indicate
497 * that the thread is existing the loadClass method.
498 */
499 public static void endLoadTask(ClassLoadingTask task)
500 {
501 boolean trace = log.isTraceEnabled();
502 if( trace )
503 log.trace("Begin endLoadTask, task="+task);
504
505 // Unregister as the owning thread and notify any waiting threads
506 synchronized( registrationLock )
507 {
508 loadClassThreads.remove(task.requestingClassLoader);
509 registrationLock.notifyAll();
510 }
511
512 // Any ThreadTasks associated with this thread must be reassigned
513 List taskList = (List) loadTasksByThread.get(task.requestingThread);
514 int size = taskList != null ? taskList.size() : 0;
515 synchronized( taskList )
516 {
517 for(int i = 0; i < size; i ++)
518 {
519 ThreadTask threadTask = (ThreadTask) taskList.remove(0);
520 ClassLoadingTask loadTask = threadTask.getLoadTask();
521 /* Synchronize on loadTask and reassign the thread task back to the
522 requesting thread of loadTask. We need to synchronize on loadTask
523 to ensure that the transfer of this task back to loadTask.requestingThread
524 is atomic wrt loadTask.requestingThread checking its task list.
525 synchronized( loadTask )
526 {
527 if( trace )
528 log.trace("Reassigning task: "+threadTask+", to: "+loadTask.requestingThread);
529 threadTask.t = null;
530 // Insert the task into the front of requestingThread task list
531 List toTaskList = (List) loadTasksByThread.get(loadTask.requestingThread);
532 toTaskList.add(0, threadTask);
533 loadTask.state = ClassLoadingTask.NEXT_EVENT;
534 loadTask.notify();
535 }
536 */
537 if( trace )
538 log.trace("Reassigning task: "+threadTask+", to: "+loadTask.requestingThread);
539 threadTask.t = null;
540 // Insert the task into the front of requestingThread task list
541 List toTaskList = (List) loadTasksByThread.get(loadTask.requestingThread);
542 synchronized( toTaskList )
543 {
544 toTaskList.add(0, threadTask);
545 loadTask.state = ClassLoadingTask.NEXT_EVENT;
546 toTaskList.notify();
547 }
548 }
549 }
550 }
551
552 /** Invoked to create a ThreadTask to assign a thread to the task of
553 * loading the class of ClassLoadingTask.
554 *
555 * @param task the orginating UCL3.loadClass task for which the thread
556 * @param ucl the UCL3 the ThreadTask will call loadClassLocally on
557 * @param order the heirachical ordering of the task
558 * @param reschedule a boolean indicating if this task is being rescheduled
559 * with another UCL3
560 * @param trace the Logger trace level flag
561 * @throws ClassNotFoundException
562 */
563 static private void scheduleTask(ClassLoadingTask task, RepositoryClassLoader ucl,
564 int order, boolean reschedule, boolean trace) throws ClassNotFoundException
565 {
566 Thread t = null;
567 boolean releaseInNextTask = false;
568 ThreadTask subtask = null;
569 List taskList = null;
570 synchronized( registrationLock )
571 {
572 // Find the thread that owns the ucl
573 t = (Thread) loadClassThreads.get(ucl);
574 if( t == null )
575 {
576 /* There is no thread in the UCL.loadClass yet that has registered
577 as the owning thread. We must attempt to acquire the loadLock
578 and if we cannot, wait until the thread entering UCL.loadClass
579 gets to the registerLoaderThread call. By the time we are
580 notified, the thread coule in fact have exited loadClass, so
581 we either assign the task to the thread, or take ownership of
582 the UCL.
583 */
584 while( t == null && ucl.attempt(1) == false )
585 {
586 if( trace )
587 log.trace("Waiting for owner of UCL: "+ucl);
588 try
589 {
590 registrationLock.wait();
591 }
592 catch(InterruptedException e)
593 {
594 String msg = "Interrupted waiting for registration notify,"
595 + " classame: "+task.classname;
596 throw new ClassNotFoundException(msg);
597 }
598
599 t = (Thread) loadClassThreads.get(ucl);
600 if( trace )
601 log.trace("Notified that UCL owner is: "+t);
602 }
603
604 // Get the thread registered as owning the UCL.loadClass lock
605 t = (Thread) loadClassThreads.get(ucl);
606 if( t == null )
607 {
608 // There is no such thread, register as the owner
609 releaseInNextTask = true;
610 t = task.requestingThread;
611 Object prevThread = loadClassThreads.put(ucl, t);
612 if( trace )
613 {
614 log.trace("scheduleTask, taking ownership of ucl="+ucl
615 +", t="+t+", prevT="+prevThread);
616 }
617 }
618 }
619
620 // Now that we have the UCL owner thread, create and assign the task
621 subtask = task.newThreadTask(ucl, t, order, reschedule,
622 releaseInNextTask);
623 // Add the task to the owning thread
624 taskList = (List) loadTasksByThread.get(t);
625 synchronized( taskList )
626 {
627 taskList.add(subtask);
628 // Order the tasks by either the heirarchial order, or the repository order
629 Collections.sort(taskList, ClassLoadingTask.taskComparator);
630 taskList.notify();
631 }
632 }
633
634 if( trace )
635 log.trace("scheduleTask("+taskList.size()+"), created subtask: "+subtask);
636 }
637 }